mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 07:41:16 +00:00
- Finish Paddle-to-Stripe billing migration; drop paddle/stripe libs - Renumber migrations; add 00012 memberships+manage_token, 00013 location_type_capacity - Add tests: admin, auth, bookings customer service - Remove graphify-out artifacts and stale env files Authored By: TDvorak <[email protected]> Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
122 lines
2.9 KiB
Go
122 lines
2.9 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/MicahParks/keyfunc/v3"
|
|
"github.com/golang-jwt/jwt/v5"
|
|
)
|
|
|
|
type Verifier struct {
|
|
jwks keyfunc.Keyfunc
|
|
expectedIssuer string
|
|
enabled bool
|
|
localSecret []byte
|
|
cancel context.CancelFunc
|
|
}
|
|
|
|
func NewVerifier(neonAuthURL string, localJWTSecret string) (*Verifier, error) {
|
|
trimmed := strings.TrimRight(strings.TrimSpace(neonAuthURL), "/")
|
|
if trimmed == "" {
|
|
secret := strings.TrimSpace(localJWTSecret)
|
|
return &Verifier{
|
|
enabled: secret != "",
|
|
localSecret: []byte(secret),
|
|
}, nil
|
|
}
|
|
|
|
parsed, err := url.Parse(trimmed)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parse neon auth url: %w", err)
|
|
}
|
|
|
|
expectedIssuer := fmt.Sprintf("%s://%s", parsed.Scheme, parsed.Host)
|
|
jwksURL := fmt.Sprintf("%s/.well-known/jwks.json", trimmed)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
jwks, err := keyfunc.NewDefaultCtx(ctx, []string{jwksURL})
|
|
if err != nil {
|
|
cancel()
|
|
return nil, fmt.Errorf("create jwks: %w", err)
|
|
}
|
|
|
|
return &Verifier{
|
|
jwks: jwks,
|
|
expectedIssuer: expectedIssuer,
|
|
enabled: true,
|
|
localSecret: []byte(strings.TrimSpace(localJWTSecret)),
|
|
cancel: cancel,
|
|
}, nil
|
|
}
|
|
|
|
func (v *Verifier) Enabled() bool {
|
|
return v.enabled
|
|
}
|
|
|
|
func (v *Verifier) Close() {
|
|
if v.cancel != nil {
|
|
v.cancel()
|
|
}
|
|
}
|
|
|
|
func (v *Verifier) verifyLocal(tokenString string) (jwt.MapClaims, error) {
|
|
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
|
}
|
|
return v.localSecret, nil
|
|
}, jwt.WithIssuer("bookra-auth"), jwt.WithAudience("bookra"), jwt.WithLeeway(15*time.Second))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
claims, ok := token.Claims.(jwt.MapClaims)
|
|
if !ok || !token.Valid {
|
|
return nil, errors.New("invalid token claims")
|
|
}
|
|
if tokenType, _ := claims["type"].(string); tokenType != "access" {
|
|
return nil, errors.New("invalid token type")
|
|
}
|
|
return claims, nil
|
|
}
|
|
|
|
func (v *Verifier) Verify(tokenString string) (jwt.MapClaims, error) {
|
|
if !v.enabled {
|
|
return nil, errors.New("neon auth verifier is disabled")
|
|
}
|
|
|
|
if len(v.localSecret) > 0 {
|
|
claims, err := v.verifyLocal(tokenString)
|
|
if err == nil {
|
|
return claims, nil
|
|
}
|
|
if v.jwks == nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if v.jwks == nil {
|
|
return nil, errors.New("no token verification mechanism configured")
|
|
}
|
|
|
|
token, err := jwt.Parse(tokenString, v.jwks.Keyfunc,
|
|
jwt.WithIssuer(v.expectedIssuer),
|
|
jwt.WithValidMethods([]string{"EdDSA"}),
|
|
jwt.WithAudience(v.expectedIssuer),
|
|
jwt.WithLeeway(15*time.Second),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
claims, ok := token.Claims.(jwt.MapClaims)
|
|
if !ok || !token.Valid {
|
|
return nil, errors.New("invalid token claims")
|
|
}
|
|
return claims, nil
|
|
}
|