101 lines
3.2 KiB
Go
101 lines
3.2 KiB
Go
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
|
||
}
|