107 lines
3.0 KiB
Go
107 lines
3.0 KiB
Go
package middleware
|
|
|
|
import (
|
|
"RenewCMS/api/pkg/apperr"
|
|
"RenewCMS/api/pkg/authtoken"
|
|
"RenewCMS/internal/domain/apikey"
|
|
"RenewCMS/internal/infrastructure/useCases"
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const bearerScheme = "bearer "
|
|
|
|
type apiKeyIdContextKey struct{}
|
|
|
|
type apiKeyNameContextKey struct{}
|
|
|
|
func bearerAPIKey(r *http.Request) (string, bool) {
|
|
header := r.Header.Get("Authorization")
|
|
if len(header) < len(bearerScheme) || !strings.EqualFold(header[:len(bearerScheme)], bearerScheme) {
|
|
return "", false
|
|
}
|
|
|
|
credential := strings.TrimSpace(header[len(bearerScheme):])
|
|
return credential, strings.HasPrefix(credential, apikey.RawKeyPrefix)
|
|
}
|
|
|
|
func apiKeyIdFrom(r *http.Request) (uint32, bool) {
|
|
id, ok := r.Context().Value(apiKeyIdContextKey{}).(uint32)
|
|
return id, ok
|
|
}
|
|
|
|
func apiKeyNameFrom(r *http.Request) string {
|
|
name, _ := r.Context().Value(apiKeyNameContextKey{}).(string)
|
|
return name
|
|
}
|
|
|
|
func RequireSession(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if _, isKey := bearerAPIKey(r); isKey {
|
|
http.Error(w, apperr.SessionRequiredErrorMessage, http.StatusForbidden)
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
func RejectAPIKey(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if _, authenticatedByKey := apiKeyIdFrom(r); authenticatedByKey {
|
|
http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized)
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
func APIKeyAuth(authenticate *useCases.AuthenticateAPIKeyUseCase, maxRequests int, window time.Duration) func(http.Handler) http.Handler {
|
|
limiter := newKeyedRateLimiter(maxRequests, window, apiKeyRateLimitKey)
|
|
|
|
return func(next http.Handler) http.Handler {
|
|
limited := limiter(next)
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
rawKey, isKey := bearerAPIKey(r)
|
|
if !isKey {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
authenticated, err := authenticate.AuthenticateAPIKey(rawKey)
|
|
if err != nil {
|
|
writeAPIKeyError(w, err)
|
|
return
|
|
}
|
|
|
|
ctx := authtoken.NewContext(r.Context(), sessionClaimsFor(authenticated), nil)
|
|
ctx = context.WithValue(ctx, apiKeyIdContextKey{}, authenticated.Key.ID)
|
|
ctx = context.WithValue(ctx, apiKeyNameContextKey{}, authenticated.Key.Name)
|
|
limited.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|
|
}
|
|
|
|
func writeAPIKeyError(w http.ResponseWriter, err error) {
|
|
if errors.Is(err, apikey.ErrAPIKeyInvalid) {
|
|
http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized)
|
|
return
|
|
}
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
}
|
|
|
|
func sessionClaimsFor(authenticated useCases.AuthenticatedAPIKey) authtoken.Claims {
|
|
return authtoken.Claims{
|
|
"user_id": float64(authenticated.User.ID),
|
|
"token_version": float64(authenticated.User.TokenVersion),
|
|
}
|
|
}
|
|
|
|
func apiKeyRateLimitKey(r *http.Request) string {
|
|
id, _ := apiKeyIdFrom(r)
|
|
return strconv.FormatUint(uint64(id), 10)
|
|
}
|