119 lines
3.2 KiB
Go
119 lines
3.2 KiB
Go
package middleware
|
|
|
|
import (
|
|
"RenewCMS/api/pkg/apperr"
|
|
"RenewCMS/internal/domain/apikey"
|
|
"RenewCMS/internal/infrastructure/useCases"
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-chi/jwtauth/v5"
|
|
"github.com/lestrrat-go/jwx/v3/jwt"
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
token, err := sessionTokenFor(authenticated)
|
|
if err != nil {
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
ctx := jwtauth.NewContext(r.Context(), token, 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 sessionTokenFor(authenticated useCases.AuthenticatedAPIKey) (jwt.Token, error) {
|
|
token := jwt.New()
|
|
if err := token.Set("user_id", float64(authenticated.User.ID)); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := token.Set("token_version", float64(authenticated.User.TokenVersion)); err != nil {
|
|
return nil, err
|
|
}
|
|
return token, nil
|
|
}
|
|
|
|
func apiKeyRateLimitKey(r *http.Request) string {
|
|
id, _ := apiKeyIdFrom(r)
|
|
return strconv.FormatUint(uint64(id), 10)
|
|
}
|