86 lines
2.7 KiB
Go
86 lines
2.7 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 аутентификацией.
|
|
// Токен передаётся в 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"))
|
|
|
|
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
|
|
}
|