package api import ( "context" "fmt" "net/http" "strings" "fission-console/internal/auth" ) // ctxKeyNS — ключ для K8s namespace пользователя в context.Context. type ctxKeyNS struct{} // ctxKeyIdentity — ключ для UserIdentity пользователя в context.Context. type ctxKeyIdentity struct{} // authTokenFromRequest извлекает Bearer токен из X-Auth-Token или Authorization заголовка. func authTokenFromRequest(r *http.Request) string { token := strings.TrimSpace(r.Header.Get("X-Auth-Token")) if token != "" { return token } authz := strings.TrimSpace(r.Header.Get("Authorization")) if authz == "" { return "" } const bearerPrefix = "Bearer " if len(authz) >= len(bearerPrefix) && strings.EqualFold(authz[:len(bearerPrefix)], bearerPrefix) { return strings.TrimSpace(authz[len(bearerPrefix):]) } return "" } // userNS возвращает namespace пользователя из контекста запроса. func (s *Server) userNS(r *http.Request) string { if ns, ok := r.Context().Value(ctxKeyNS{}).(string); ok && ns != "" { return ns } return s.ns } // normalizeEnv приводит название стенда к допустимому значению. func normalizeEnv(env string) string { switch strings.TrimSpace(strings.ToLower(env)) { case "prod", "dev", "test": return strings.TrimSpace(strings.ToLower(env)) } return "test" } // authMiddleware оборачивает handler аутентификацией. // // В testMode: заголовок X-Test-Sub позволяет задать sub напрямую (без токена). // Иначе: токен передаётся в s.authenticator.Authenticate — детали скрыты за интерфейсом. func (s *Server) authMiddleware(h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var identity auth.UserIdentity env := normalizeEnv(r.Header.Get("X-Auth-Env")) if s.testMode { if sub := strings.TrimSpace(r.Header.Get("X-Test-Sub")); sub != "" { identity = auth.UserIdentity{Sub: sub} ctx := s.contextWithIdentity(r.Context(), identity) if err := s.nsManager.EnsureUserNS(ctx, auth.NamespaceForSub(sub)); err != nil { writeJSONError(w, http.StatusBadGateway, fmt.Sprintf("ensure namespace: %v", err)) return } h(w, r.WithContext(ctx)) return } } token := authTokenFromRequest(r) var err error identity, err = s.authenticator.Authenticate(r.Context(), token, env) if err != nil { writeJSONError(w, http.StatusUnauthorized, "unauthorized") return } ns := auth.NamespaceForSub(identity.Sub) ctx := s.contextWithIdentity(r.Context(), identity) if err := s.nsManager.EnsureUserNS(ctx, ns); err != nil { writeJSONError(w, http.StatusBadGateway, fmt.Sprintf("ensure namespace: %v", err)) return } h(w, r.WithContext(ctx)) } } // contextWithIdentity кладёт UserIdentity и вычисленный namespace в контекст. func (s *Server) contextWithIdentity(ctx context.Context, identity auth.UserIdentity) context.Context { ns := auth.NamespaceForSub(identity.Sub) ctx = context.WithValue(ctx, ctxKeyNS{}, ns) ctx = context.WithValue(ctx, ctxKeyIdentity{}, identity) return ctx }