package auth import ( "net/http" "strings" "bookra/apps/backend/internal/db" "bookra/apps/backend/internal/domain" "github.com/gin-gonic/gin" ) const principalContextKey = "principal" // DemoPrincipal is the auto-authenticated user in demo mode var DemoPrincipal = domain.Principal{ Subject: "demo-owner", Email: "demo@bookra.dev", Name: "Demo User", Role: "owner", } func RequireAuth(verifier *Verifier, repo db.Repository, demoMode bool) gin.HandlerFunc { return func(c *gin.Context) { // In demo mode, auto-authenticate as the demo user if demoMode { c.Set(principalContextKey, DemoPrincipal) c.Next() return } if verifier == nil || !verifier.Enabled() { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "auth_not_configured"}) return } header := c.GetHeader("Authorization") tokenString, ok := strings.CutPrefix(header, "Bearer ") if !ok || strings.TrimSpace(tokenString) == "" { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing_bearer_token"}) return } claims, err := verifier.Verify(strings.TrimSpace(tokenString)) if err != nil { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid_token"}) return } subject, _ := claims["sub"].(string) email, _ := claims["email"].(string) name, _ := claims["name"].(string) if name == "" { name, _ = claims["display_name"].(string) } role, _ := claims["role"].(string) if role == "" { role = "authenticated" } if strings.TrimSpace(subject) == "" { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid_token_subject"}) return } if repo != nil { if err := repo.EnsureUserIdentity(c.Request.Context(), subject, email, name); err != nil { c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "identity_sync_failed"}) return } } c.Set(principalContextKey, domain.Principal{ Subject: subject, Email: email, Name: name, Role: role, }) c.Next() } } func PrincipalFromContext(c *gin.Context) domain.Principal { value, ok := c.Get(principalContextKey) if !ok { return domain.Principal{} } principal, _ := value.(domain.Principal) return principal }