Files
MosisService/portal/internal/api/middleware/auth.go

128 lines
3.3 KiB
Go

// Package middleware provides HTTP middleware for the API
package middleware
import (
"context"
"net/http"
"strings"
"omixlab.com/mosis-portal/internal/auth"
"omixlab.com/mosis-portal/internal/database"
)
type contextKey string
const (
DeveloperContextKey contextKey = "developer"
ClaimsContextKey contextKey = "claims"
)
// AuthMiddleware handles JWT and API key authentication
type AuthMiddleware struct {
jwtManager *auth.JWTManager
db *database.DB
}
// NewAuthMiddleware creates a new auth middleware
func NewAuthMiddleware(jwtManager *auth.JWTManager, db *database.DB) *AuthMiddleware {
return &AuthMiddleware{
jwtManager: jwtManager,
db: db,
}
}
// RequireAuth requires a valid JWT or API key
func (m *AuthMiddleware) RequireAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Try Bearer token first
authHeader := r.Header.Get("Authorization")
if strings.HasPrefix(authHeader, "Bearer ") {
token := strings.TrimPrefix(authHeader, "Bearer ")
claims, err := m.jwtManager.ValidateAccessToken(token)
if err != nil {
http.Error(w, "Invalid or expired token", http.StatusUnauthorized)
return
}
// Add claims to context
ctx := context.WithValue(r.Context(), ClaimsContextKey, claims)
next.ServeHTTP(w, r.WithContext(ctx))
return
}
// Try API key
apiKey := r.Header.Get("X-API-Key")
if apiKey != "" {
developer, err := m.db.ValidateAPIKey(r.Context(), apiKey)
if err != nil {
http.Error(w, "Invalid API key", http.StatusUnauthorized)
return
}
// Add developer to context
ctx := context.WithValue(r.Context(), DeveloperContextKey, developer)
next.ServeHTTP(w, r.WithContext(ctx))
return
}
http.Error(w, "Authorization required", http.StatusUnauthorized)
})
}
// OptionalAuth adds developer info to context if authenticated, but doesn't require it
func (m *AuthMiddleware) OptionalAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Try Bearer token
authHeader := r.Header.Get("Authorization")
if strings.HasPrefix(authHeader, "Bearer ") {
token := strings.TrimPrefix(authHeader, "Bearer ")
claims, err := m.jwtManager.ValidateAccessToken(token)
if err == nil {
ctx := context.WithValue(r.Context(), ClaimsContextKey, claims)
next.ServeHTTP(w, r.WithContext(ctx))
return
}
}
// Try API key
apiKey := r.Header.Get("X-API-Key")
if apiKey != "" {
developer, err := m.db.ValidateAPIKey(r.Context(), apiKey)
if err == nil {
ctx := context.WithValue(r.Context(), DeveloperContextKey, developer)
next.ServeHTTP(w, r.WithContext(ctx))
return
}
}
// Continue without authentication
next.ServeHTTP(w, r)
})
}
// GetClaims retrieves JWT claims from context
func GetClaims(ctx context.Context) *auth.Claims {
claims, ok := ctx.Value(ClaimsContextKey).(*auth.Claims)
if !ok {
return nil
}
return claims
}
// GetDeveloperID retrieves the developer ID from context (from JWT or API key)
func GetDeveloperID(ctx context.Context) string {
// First check JWT claims
claims := GetClaims(ctx)
if claims != nil {
return claims.Subject
}
// Then check developer from API key
developer, ok := ctx.Value(DeveloperContextKey).(*database.Developer)
if ok && developer != nil {
return developer.ID
}
return ""
}