enki b3204ea07a
Some checks are pending
CI Pipeline / Run Tests (push) Waiting to run
CI Pipeline / Lint Code (push) Waiting to run
CI Pipeline / Security Scan (push) Waiting to run
CI Pipeline / Build Docker Images (push) Blocked by required conditions
CI Pipeline / E2E Tests (push) Blocked by required conditions
first commit
2025-08-18 00:40:15 -07:00

116 lines
3.1 KiB
Go

package middleware
import (
"context"
"fmt"
"net/http"
"strings"
"git.sovbit.dev/enki/torrentGateway/internal/auth"
)
// UserContextKey is the key for storing user info in request context
type UserContextKey string
const UserKey UserContextKey = "user"
// AuthMiddleware provides authentication middleware
type AuthMiddleware struct {
nostrAuth *auth.NostrAuth
}
// NewAuthMiddleware creates a new authentication middleware
func NewAuthMiddleware(nostrAuth *auth.NostrAuth) *AuthMiddleware {
return &AuthMiddleware{
nostrAuth: nostrAuth,
}
}
// RequireAuth middleware that requires valid authentication
func (am *AuthMiddleware) RequireAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
pubkey, err := am.extractAndValidateAuth(r)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Add user to context
ctx := context.WithValue(r.Context(), UserKey, pubkey)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// OptionalAuth middleware that extracts auth if present but doesn't require it
func (am *AuthMiddleware) OptionalAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
pubkey, _ := am.extractAndValidateAuth(r)
if pubkey != "" {
ctx := context.WithValue(r.Context(), UserKey, pubkey)
r = r.WithContext(ctx)
}
next.ServeHTTP(w, r)
})
}
// extractAndValidateAuth extracts and validates authentication from request
func (am *AuthMiddleware) extractAndValidateAuth(r *http.Request) (string, error) {
// Try to get session token from Authorization header
authHeader := r.Header.Get("Authorization")
var token string
if authHeader != "" {
if strings.HasPrefix(authHeader, "Bearer ") {
token = strings.TrimPrefix(authHeader, "Bearer ")
}
}
// If not in header, try cookie
if token == "" {
if cookie, err := r.Cookie("session_token"); err == nil {
token = cookie.Value
}
}
if token == "" {
return "", fmt.Errorf("no session token found")
}
// Validate session token
pubkey, err := am.nostrAuth.ValidateSession(token)
if err != nil {
return "", fmt.Errorf("invalid session: %w", err)
}
return pubkey, nil
}
// GetUserFromContext extracts user pubkey from request context
func GetUserFromContext(ctx context.Context) string {
if pubkey, ok := ctx.Value(UserKey).(string); ok {
return pubkey
}
return ""
}
// IsAuthenticated checks if the request has valid authentication
func IsAuthenticated(ctx context.Context) bool {
return GetUserFromContext(ctx) != ""
}
// CORS middleware for handling cross-origin requests
func CORS(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")
w.Header().Set("Access-Control-Allow-Credentials", "true")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}