Files
Spanglish/photo-api/internal/httpapi/access.go
T
MichilisandCursor c9a600b6d6 Add photo galleries API and frontend for public and admin viewing.
Introduces the Go photo-api service, nginx/systemd deploy wiring, and Next.js gallery/lightbox pages so event photos can be managed and browsed.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-25 17:32:23 +00:00

56 lines
1.8 KiB
Go

package httpapi
import (
"net/http"
"git.azzamo.net/Michilis/Spanglish/photo-api/internal/auth"
"git.azzamo.net/Michilis/Spanglish/photo-api/internal/store"
)
// accessDenial describes why a viewer may not see a gallery. Non-public
// galleries return 404 (not 403) so their existence is not probeable — the
// exception is ticket mode, whose 401/403 lets the frontend prompt login
// or explain the attendee requirement (its existence is already public via
// the event).
type accessDenial struct {
status int
msg string
}
// authorize is the single access-control decision point (PLAN.md §7); it is
// called from both the gallery-view handler and the file handler so every
// byte served re-checks.
func (s *Server) authorize(r *http.Request, g store.Gallery, user *auth.User, token string) *accessDenial {
if user != nil && user.IsAdmin() {
return nil
}
switch g.Visibility {
case store.VisibilityPublic:
return nil
case store.VisibilityLink:
if token != "" && token == g.ShareToken {
return nil
}
return &accessDenial{http.StatusNotFound, "Gallery not found"}
case store.VisibilityTicket:
// The share token is honored as an escape hatch (e.g. attendee +1s
// without accounts, at the admin's discretion).
if token != "" && token == g.ShareToken {
return nil
}
if user == nil {
return &accessDenial{http.StatusUnauthorized, "Authentication required"}
}
if g.EventID == "" {
return &accessDenial{http.StatusForbidden, "This gallery is only available to event attendees"}
}
ok, err := s.db.HasPaidTicket(r.Context(), user.ID, g.EventID)
if err != nil || !ok {
return &accessDenial{http.StatusForbidden, "This gallery is only available to event attendees"}
}
return nil
default: // private
return &accessDenial{http.StatusNotFound, "Gallery not found"}
}
}