128 lines
3.3 KiB
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 ""
|
|
}
|