add OAuth authentication with JWT tokens and API key support
This commit is contained in:
127
portal/internal/api/middleware/auth.go
Normal file
127
portal/internal/api/middleware/auth.go
Normal file
@@ -0,0 +1,127 @@
|
||||
// Package middleware provides HTTP middleware for the API
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/omixlab/mosis-portal/internal/auth"
|
||||
"github.com/omixlab/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 ""
|
||||
}
|
||||
Reference in New Issue
Block a user