// Package routes provides HTTP route handlers for the Felt tournament engine. package routes import ( "encoding/json" "net/http" "github.com/felt-app/felt/internal/auth" "github.com/felt-app/felt/internal/server/middleware" ) // AuthHandler handles authentication routes. type AuthHandler struct { authService *auth.AuthService } // NewAuthHandler creates a new auth route handler. func NewAuthHandler(authService *auth.AuthService) *AuthHandler { return &AuthHandler{authService: authService} } // loginRequest is the request body for POST /api/v1/auth/login. type loginRequest struct { PIN string `json:"pin"` } // loginResponse is the response body for POST /api/v1/auth/login. type loginResponse struct { Token string `json:"token"` Operator auth.Operator `json:"operator"` } // HandleLogin handles POST /api/v1/auth/login. // Authenticates an operator by PIN and returns a JWT token. func (h *AuthHandler) HandleLogin(w http.ResponseWriter, r *http.Request) { var req loginRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"}) return } if req.PIN == "" { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "pin is required"}) return } token, operator, err := h.authService.Login(r.Context(), req.PIN) if err != nil { switch err { case auth.ErrInvalidPIN: writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid PIN"}) case auth.ErrTooManyAttempts: writeJSON(w, http.StatusTooManyRequests, map[string]string{"error": "too many failed attempts, please wait"}) case auth.ErrOperatorLocked: writeJSON(w, http.StatusTooManyRequests, map[string]string{"error": "account locked, please wait 30 minutes"}) default: writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "internal server error"}) } return } writeJSON(w, http.StatusOK, loginResponse{ Token: token, Operator: operator, }) } // meResponse is the response body for GET /api/v1/auth/me. type meResponse struct { OperatorID string `json:"operator_id"` Role string `json:"role"` } // HandleMe handles GET /api/v1/auth/me. // Returns the current operator from JWT claims. func (h *AuthHandler) HandleMe(w http.ResponseWriter, r *http.Request) { operatorID := middleware.OperatorID(r) role := middleware.OperatorRole(r) if operatorID == "" { writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "not authenticated"}) return } writeJSON(w, http.StatusOK, meResponse{ OperatorID: operatorID, Role: role, }) } // HandleLogout handles POST /api/v1/auth/logout. // JWT is stateless so this is client-side only, but the endpoint exists for // audit logging purposes. func (h *AuthHandler) HandleLogout(w http.ResponseWriter, r *http.Request) { // Log the logout action for audit trail // The actual logout happens client-side by discarding the token writeJSON(w, http.StatusOK, map[string]string{"status": "logged out"}) } // writeJSON is defined in templates.go (shared helper for the routes package)