package middleware import ( "net/http" "strings" "haixun-backend/internal/config" "haixun-backend/internal/library/authctx" app "haixun-backend/internal/library/errors" "haixun-backend/internal/library/errors/code" authusecase "haixun-backend/internal/model/auth/domain/usecase" "haixun-backend/internal/response" "github.com/zeromicro/go-zero/rest" ) const MemberAuthorizationHeader = "X-Member-Authorization" // Auth validates member JWT from Authorization: Bearer . // Use for all protected routes except AI endpoints that reserve Authorization for provider tokens. func Auth(tokens authusecase.TokenUseCase, cfg config.AuthConf, next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { actor, err := resolveActor(r, tokens, cfg, r.Header.Get("Authorization"), "missing bearer token") if err != nil { response.Write(r.Context(), w, nil, err) return } next(w, r.WithContext(authctx.WithActor(r.Context(), actor))) } } // MemberAuth validates member JWT from X-Member-Authorization. // AI routes keep Authorization for provider API keys. func MemberAuth(tokens authusecase.TokenUseCase, cfg config.AuthConf, next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { actor, err := resolveActor(r, tokens, cfg, r.Header.Get(MemberAuthorizationHeader), "missing member bearer token") if err != nil { response.Write(r.Context(), w, nil, err) return } next(w, r.WithContext(authctx.WithActor(r.Context(), actor))) } } // RestAuth wraps Auth as a go-zero route middleware. func RestAuth(tokens authusecase.TokenUseCase, cfg config.AuthConf) rest.Middleware { return func(next http.HandlerFunc) http.HandlerFunc { return Auth(tokens, cfg, next) } } // RestMemberAuth wraps MemberAuth as a go-zero route middleware. func RestMemberAuth(tokens authusecase.TokenUseCase, cfg config.AuthConf) rest.Middleware { return func(next http.HandlerFunc) http.HandlerFunc { return MemberAuth(tokens, cfg, next) } } func resolveActor( r *http.Request, tokens authusecase.TokenUseCase, cfg config.AuthConf, authorizationHeader string, missingMessage string, ) (authctx.Actor, error) { if cfg.DevHeaderFallback { tenantID := strings.TrimSpace(r.Header.Get("X-Tenant-ID")) uid := strings.TrimSpace(r.Header.Get("X-UID")) if tenantID != "" && uid != "" { return authctx.Actor{TenantID: tenantID, UID: uid}, nil } } raw := bearerToken(authorizationHeader) if raw == "" { return authctx.Actor{}, app.For(code.Auth).AuthUnauthorized(missingMessage) } if tokens == nil { return authctx.Actor{}, app.For(code.Auth).SysNotImplemented("token usecase is not configured") } claims, err := tokens.ParseAccessToken(r.Context(), raw) if err != nil { return authctx.Actor{}, err } return authctx.Actor{TenantID: claims.TenantID, UID: claims.UID, JTI: claims.JTI}, nil } func BearerToken(header string) string { return bearerToken(header) } func bearerToken(header string) string { const prefix = "Bearer " if !strings.HasPrefix(header, prefix) { return "" } return strings.TrimSpace(strings.TrimPrefix(header, prefix)) }