132 lines
3.7 KiB
Go
132 lines
3.7 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/shorebird-server/internal/api/middleware"
|
|
"github.com/shorebird-server/internal/auth"
|
|
"github.com/shorebird-server/internal/db"
|
|
"github.com/shorebird-server/internal/models"
|
|
)
|
|
|
|
// AuthHandler handles authentication endpoints.
|
|
type AuthHandler struct {
|
|
DB *db.DB
|
|
AuthService *auth.Service
|
|
}
|
|
|
|
// Login handles POST /auth/token
|
|
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
|
|
var req models.AuthTokenRequest
|
|
if err := decodeJSON(r, &req); err != nil {
|
|
respondError(w, http.StatusBadRequest, "Invalid request body", nil)
|
|
return
|
|
}
|
|
|
|
user, err := h.DB.GetUserByEmail(r.Context(), req.Email)
|
|
if err != nil {
|
|
respondError(w, http.StatusUnauthorized, "Invalid email or password", nil)
|
|
return
|
|
}
|
|
|
|
if err := auth.CheckPassword(user.PasswordHash, req.Password); err != nil {
|
|
respondError(w, http.StatusUnauthorized, "Invalid email or password", nil)
|
|
return
|
|
}
|
|
|
|
token, err := h.AuthService.GenerateToken(user.ID, user.Email)
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, "Failed to generate token", nil)
|
|
return
|
|
}
|
|
|
|
refreshToken, err := auth.GenerateRefreshToken()
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, "Failed to generate refresh token", nil)
|
|
return
|
|
}
|
|
|
|
respondJSON(w, http.StatusOK, models.AuthTokenResponse{
|
|
Token: token,
|
|
RefreshToken: refreshToken,
|
|
Email: user.Email,
|
|
})
|
|
}
|
|
|
|
// Refresh handles POST /auth/refresh
|
|
func (h *AuthHandler) Refresh(w http.ResponseWriter, r *http.Request) {
|
|
// For simplicity, re-validate the current token and issue a new one.
|
|
// In production, you'd maintain a refresh token table.
|
|
claims := middleware.GetClaims(r)
|
|
if claims == nil {
|
|
respondError(w, http.StatusUnauthorized, "Invalid token", nil)
|
|
return
|
|
}
|
|
|
|
token, err := h.AuthService.GenerateToken(claims.UserID, claims.Email)
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, "Failed to generate token", nil)
|
|
return
|
|
}
|
|
|
|
refreshToken, err := auth.GenerateRefreshToken()
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, "Failed to generate refresh token", nil)
|
|
return
|
|
}
|
|
|
|
respondJSON(w, http.StatusOK, models.AuthTokenResponse{
|
|
Token: token,
|
|
RefreshToken: refreshToken,
|
|
Email: claims.Email,
|
|
})
|
|
}
|
|
|
|
// Register handles POST /auth/register (self-hosted convenience endpoint)
|
|
func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
|
|
var req struct {
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
Name string `json:"name"`
|
|
}
|
|
if err := decodeJSON(r, &req); err != nil {
|
|
respondError(w, http.StatusBadRequest, "Invalid request body", nil)
|
|
return
|
|
}
|
|
|
|
passwordHash, err := auth.HashPassword(req.Password)
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, "Failed to hash password", nil)
|
|
return
|
|
}
|
|
|
|
userID, err := h.DB.CreateUser(r.Context(), req.Email, req.Name, passwordHash)
|
|
if err != nil {
|
|
respondError(w, http.StatusConflict, "User already exists", strPtr(err.Error()))
|
|
return
|
|
}
|
|
|
|
// Create a default organization for the user
|
|
orgID, err := h.DB.CreateOrganization(r.Context(), req.Name+"'s Org", "team")
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, "Failed to create organization", nil)
|
|
return
|
|
}
|
|
|
|
if err := h.DB.AddUserToOrganization(r.Context(), userID, orgID, "admin"); err != nil {
|
|
respondError(w, http.StatusInternalServerError, "Failed to add user to organization", nil)
|
|
return
|
|
}
|
|
|
|
token, err := h.AuthService.GenerateToken(userID, req.Email)
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, "Failed to generate token", nil)
|
|
return
|
|
}
|
|
|
|
respondJSON(w, http.StatusCreated, models.AuthTokenResponse{
|
|
Token: token,
|
|
Email: req.Email,
|
|
})
|
|
}
|