package auth import ( "encoding/json" "fmt" "io" "net/http" "github.com/gin-gonic/gin" "shared-album/internal/httpx" ) type Handler struct { svc *Service } func NewHandler(svc *Service) *Handler { return &Handler{svc: svc} } type registerRequest struct { Email string `json:"email" binding:"required"` Password string `json:"password" binding:"required"` Name string `json:"name" binding:"required"` } func (h *Handler) Register(c *gin.Context) { var req registerRequest if err := c.ShouldBindJSON(&req); err != nil { httpx.RespondError(c, httpx.BadRequest("invalid_request", "email, password and name are required")) return } u, pair, err := h.svc.Register(c.Request.Context(), req.Email, req.Password, req.Name) if err != nil { httpx.RespondError(c, err) return } c.JSON(http.StatusCreated, gin.H{"user": u.Public(), "tokens": pair}) } type loginRequest struct { Email string `json:"email" binding:"required"` Password string `json:"password" binding:"required"` } func (h *Handler) Login(c *gin.Context) { var req loginRequest if err := c.ShouldBindJSON(&req); err != nil { httpx.RespondError(c, httpx.BadRequest("invalid_request", "email and password are required")) return } u, pair, err := h.svc.Login(c.Request.Context(), req.Email, req.Password) if err != nil { httpx.RespondError(c, err) return } c.JSON(http.StatusOK, gin.H{"user": u.Public(), "tokens": pair}) } type refreshRequest struct { RefreshToken string `json:"refresh_token" binding:"required"` } func (h *Handler) Refresh(c *gin.Context) { var req refreshRequest if err := c.ShouldBindJSON(&req); err != nil { httpx.RespondError(c, httpx.BadRequest("invalid_request", "refresh_token is required")) return } pair, err := h.svc.Refresh(c.Request.Context(), req.RefreshToken) if err != nil { httpx.RespondError(c, err) return } c.JSON(http.StatusOK, gin.H{"tokens": pair}) } func (h *Handler) Logout(c *gin.Context) { var req refreshRequest _ = c.ShouldBindJSON(&req) if req.RefreshToken != "" { if err := h.svc.Logout(c.Request.Context(), req.RefreshToken); err != nil { httpx.RespondError(c, err) return } } c.Status(http.StatusNoContent) } func (h *Handler) Me(c *gin.Context) { u, err := h.svc.store.GetUserByID(c.Request.Context(), UserID(c)) if err != nil { httpx.RespondError(c, httpx.Unauthorized("unauthorized", "user not found")) return } prefs := u.Preferences if prefs == nil { prefs = map[string]any{} } c.JSON(http.StatusOK, gin.H{"user": u.Public(), "preferences": prefs}) } const ( maxVisibleAttrs = 500 maxLabelEntries = 500 maxPreferenceKeyLen = 128 maxPreferenceValueLen = 128 ) type photoInfoPreferences struct { Visible []string `json:"visible"` Labels map[string]string `json:"labels,omitempty"` } type preferencesPayload struct { PhotoInfo *photoInfoPreferences `json:"photo_info,omitempty"` } func (p preferencesPayload) validate() error { pi := p.PhotoInfo if pi == nil { return nil } if len(pi.Visible) > maxVisibleAttrs { return fmt.Errorf("too many visible attributes") } for _, k := range pi.Visible { if k == "" || len(k) > maxPreferenceKeyLen { return fmt.Errorf("invalid visible attribute key") } } if len(pi.Labels) > maxLabelEntries { return fmt.Errorf("too many label overrides") } for k, v := range pi.Labels { if k == "" || len(k) > maxPreferenceKeyLen { return fmt.Errorf("invalid label key") } if len(v) > maxPreferenceValueLen { return fmt.Errorf("label value too long") } } return nil } func (h *Handler) UpdatePreferences(c *gin.Context) { body, err := io.ReadAll(io.LimitReader(c.Request.Body, 1<<20)) if err != nil { httpx.RespondError(c, httpx.BadRequest("invalid_preferences", "could not read request body")) return } var payload preferencesPayload if err := json.Unmarshal(body, &payload); err != nil { httpx.RespondError(c, httpx.BadRequest("invalid_preferences", "preferences must be a JSON object")) return } if err := payload.validate(); err != nil { httpx.RespondError(c, httpx.BadRequest("invalid_preferences", err.Error())) return } raw, err := json.Marshal(payload) if err != nil { httpx.RespondError(c, httpx.BadRequest("invalid_preferences", "could not encode preferences")) return } if err := h.svc.store.UpdatePreferences(c.Request.Context(), UserID(c), raw); err != nil { httpx.RespondError(c, err) return } c.JSON(http.StatusOK, gin.H{"preferences": payload}) }