Files
shorebird-server/internal/api/handlers/auth.go
T
2026-06-12 04:22:58 +08:00

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,
})
}