71 lines
2.3 KiB
Go
71 lines
2.3 KiB
Go
// Package userid extracts the calling user identifier from the trusted
|
|
// `X-User-ID` header injected by gateway and exposes it through the request
|
|
// context.
|
|
//
|
|
// Backend trusts the header value because the network segment between gateway
|
|
// and backend is the trust boundary (see `ARCHITECTURE.md` §15). The
|
|
// middleware therefore only validates the syntactic shape (UUID) and rejects
|
|
// malformed or absent values with the standard `400 invalid_request` envelope.
|
|
package userid
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"galaxy/backend/internal/server/httperr"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// Header is the canonical case-correct header name carrying the trusted user
|
|
// id forwarded by gateway.
|
|
const Header = "X-User-ID"
|
|
|
|
// userIDContextKey is the unexported context key used to store the parsed
|
|
// user id. The unexported value type prevents accidental collisions with
|
|
// keys defined in unrelated packages.
|
|
type userIDContextKey struct{}
|
|
|
|
// Middleware returns the gin middleware that requires a syntactically valid
|
|
// `X-User-ID` header on every authenticated user request and stores the
|
|
// parsed UUID on the request context.
|
|
func Middleware() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
raw := strings.TrimSpace(c.GetHeader(Header))
|
|
if raw == "" {
|
|
httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, Header+" header is required")
|
|
return
|
|
}
|
|
|
|
userID, err := uuid.Parse(raw)
|
|
if err != nil {
|
|
httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, Header+" header must be a valid UUID")
|
|
return
|
|
}
|
|
|
|
c.Request = c.Request.WithContext(WithValue(c.Request.Context(), userID))
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// FromContext returns the user id stored on ctx by Middleware. The boolean
|
|
// reports whether a value was found.
|
|
func FromContext(ctx context.Context) (uuid.UUID, bool) {
|
|
if ctx == nil {
|
|
return uuid.Nil, false
|
|
}
|
|
value, ok := ctx.Value(userIDContextKey{}).(uuid.UUID)
|
|
if !ok {
|
|
return uuid.Nil, false
|
|
}
|
|
return value, true
|
|
}
|
|
|
|
// WithValue stores userID on ctx under the package-private context key.
|
|
// Exposed for tests that need to build a context outside the middleware.
|
|
func WithValue(ctx context.Context, userID uuid.UUID) context.Context {
|
|
return context.WithValue(ctx, userIDContextKey{}, userID)
|
|
}
|