package auth import ( "encoding/json" "errors" "net/http" "strings" ) type Handler struct { service *Service } func NewHandler(service *Service) *Handler { return &Handler{service: service} } func (h *Handler) RegisterRoutes(mux *http.ServeMux) { mux.HandleFunc("POST /api/auth/register", h.Register) mux.HandleFunc("POST /api/auth/login", h.Login) mux.HandleFunc("POST /api/auth/logout", h.Logout) mux.HandleFunc("GET /api/auth/me", h.Me) } type credentialsRequest struct { Email string `json:"email"` Password string `json:"password"` Name string `json:"name"` } func (h *Handler) Register(w http.ResponseWriter, r *http.Request) { var request credentialsRequest if !decodeJSON(w, r, &request) { return } user, err := h.service.Register(r.Context(), request.Email, request.Password, request.Name) if err != nil { if errors.Is(err, ErrEmailTaken) { writeJSON(w, http.StatusConflict, map[string]string{"error": "email is already registered"}) return } writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()}) return } h.service.SetSession(w, user) writeJSON(w, http.StatusCreated, map[string]User{"user": user}) } func (h *Handler) Login(w http.ResponseWriter, r *http.Request) { var request credentialsRequest if !decodeJSON(w, r, &request) { return } user, err := h.service.Login(r.Context(), request.Email, request.Password) if err != nil { if errors.Is(err, ErrInvalidCredentials) { writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid email or password"}) return } writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "could not sign in"}) return } h.service.SetSession(w, user) writeJSON(w, http.StatusOK, map[string]User{"user": user}) } func (h *Handler) Logout(w http.ResponseWriter, _ *http.Request) { h.service.ClearSession(w) writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) } func (h *Handler) Me(w http.ResponseWriter, r *http.Request) { user, err := h.service.UserFromRequest(r.Context(), r) if err != nil { writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "authentication required"}) return } writeJSON(w, http.StatusOK, map[string]User{"user": user}) } func decodeJSON(w http.ResponseWriter, r *http.Request, target any) bool { if !strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") { writeJSON(w, http.StatusUnsupportedMediaType, map[string]string{"error": "content type must be application/json"}) return false } decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)) decoder.DisallowUnknownFields() if err := decoder.Decode(target); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON body"}) return false } return true }