feat: integration v2 and API key PART 1/3
This commit is contained in:
1 parent
7e9bd9ba30
commit
844842fdae
40 files changed
+3782
-64
No files matched your search
@@ -0,0 +1,100 @@
|
||||
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/v2/jwt"
|
||||
)
|
||||
|
||||
const bearerScheme = "bearer "
|
||||
|
||||
type apiKeyIdContextKey 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 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 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)
|
||||
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)
|
||||
}
|
||||
Reference in new issue
Block a user