Files
moteva/server/internal/handler/user_context_middleware.go
T
root a39e18950c refactor(auth): require bearer token only, drop legacy token header and cookie
Authenticate requests solely from the Authorization: Bearer header on the
server; stop accepting the legacy `token` header and `usertoken` cookie.
The frontend no longer sets auth cookies (only clears legacy ones), omits
credentials on all requests, and drops the redundant `token` header. Project
event SSE now streams over fetch so it can carry the Authorization header,
which EventSource cannot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 01:09:39 +08:00

67 lines
2.0 KiB
Go

package handler
import (
"context"
"net/http"
"strings"
"img_infinite_canvas/internal/domain/design"
)
type bearerAuthenticator interface {
UserIDFromBearer(ctx context.Context, authorization string) (string, bool)
}
func UserContextMiddleware(authenticator bearerAuthenticator) func(http.HandlerFunc) http.HandlerFunc {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
userID := r.Header.Get("X-User-Id")
authenticated := false
if authenticator != nil {
if tokenUserID, ok := userIDFromRequestAuth(r.Context(), r, authenticator); ok {
userID = tokenUserID
authenticated = true
}
}
if requiresAuthenticatedUser(r) && !authenticated {
writeAuthRequired(w)
return
}
ctx := design.ContextWithUserID(r.Context(), userID)
next(w, r.WithContext(ctx))
}
}
}
func userIDFromRequestAuth(ctx context.Context, r *http.Request, authenticator bearerAuthenticator) (string, bool) {
return authenticator.UserIDFromBearer(ctx, r.Header.Get("Authorization"))
}
func requiresAuthenticatedUser(r *http.Request) bool {
path := r.URL.Path
if !strings.HasPrefix(path, "/api/") {
return false
}
if strings.HasPrefix(path, "/api/auth/") || path == "/api/auth/options" || path == "/api/health" || path == "/api/dashboard" {
return false
}
return strings.HasPrefix(path, "/api/projects") ||
strings.HasPrefix(path, "/api/account") ||
strings.HasPrefix(path, "/api/assets") ||
strings.HasPrefix(path, "/api/generator/")
}
func writeAuthRequired(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"error":"login required","errorCode":"auth.login_required","code":401}`))
}
func LegacyUserContextMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
userID := r.Header.Get("X-User-Id")
ctx := design.ContextWithUserID(r.Context(), userID)
next(w, r.WithContext(ctx))
}
}