SessionOrAPIKey authenticates a request via the console session cookie first, then falls back to a Bearer API key. Both resolve to a *domain.APIKeyAuth in the request context, so all /v1 handlers work for the browser console and for programmatic clients without any per-handler changes.
(apiKeyStore domain.APIKeyStore, resolver SessionResolver, allowedOrigins []string)
| 82 | // the request context, so all /v1 handlers work for the browser console and for |
| 83 | // programmatic clients without any per-handler changes. |
| 84 | func SessionOrAPIKey(apiKeyStore domain.APIKeyStore, resolver SessionResolver, allowedOrigins []string) func(http.Handler) http.Handler { |
| 85 | allowAll := len(allowedOrigins) == 1 && allowedOrigins[0] == "*" |
| 86 | allowed := make(map[string]bool, len(allowedOrigins)) |
| 87 | for _, o := range allowedOrigins { |
| 88 | allowed[o] = true |
| 89 | } |
| 90 | |
| 91 | return func(next http.Handler) http.Handler { |
| 92 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 93 | if resolver != nil { |
| 94 | if c, err := r.Cookie(SessionCookieName); err == nil && c.Value != "" { |
| 95 | if auth, err := resolver.ResolveSessionAuth(r.Context(), c.Value); err == nil && auth != nil { |
| 96 | if isStateChanging(r.Method) && !originAllowed(r, allowed, allowAll) { |
| 97 | writeError(w, http.StatusForbidden, "cross-origin request blocked") |
| 98 | return |
| 99 | } |
| 100 | next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), authContextKey, auth))) |
| 101 | return |
| 102 | } |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | authHeader := r.Header.Get("Authorization") |
| 107 | if authHeader == "" { |
| 108 | writeError(w, http.StatusUnauthorized, "missing authorization") |
| 109 | return |
| 110 | } |
| 111 | parts := strings.SplitN(authHeader, " ", 2) |
| 112 | if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") { |
| 113 | writeError(w, http.StatusUnauthorized, "invalid authorization header format") |
| 114 | return |
| 115 | } |
| 116 | auth, err := apiKeyStore.GetAuthByHash(r.Context(), HashAPIKey(parts[1])) |
| 117 | if err != nil { |
| 118 | writeError(w, http.StatusUnauthorized, "invalid API key") |
| 119 | return |
| 120 | } |
| 121 | go func() { _ = apiKeyStore.UpdateLastUsed(context.Background(), auth.KeyID) }() |
| 122 | next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), authContextKey, auth))) |
| 123 | }) |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | // RequireScope returns middleware that rejects requests whose API key lacks the given scope. |
| 128 | // Keys with the "admin" scope pass all scope checks. |
nothing calls this directly
no test coverage detected