- Audience-aware mint, verify, refresh, and backend guard keep frontend tokens compatible - Cabana mounts raw admin login, list schema, and record list behind admin.jwt.secret - Framework migration seeds Winter backend users and developer/publisher roles
79 lines
2.1 KiB
Go
79 lines
2.1 KiB
Go
package bouncer
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
)
|
|
|
|
// prvHash is sha1("Golem15\User\Models\User"), the lock-subject PHP jwt-auth
|
|
// stamps on every frontend user token. Backend tokens do not carry it.
|
|
const prvHash = "a867434cbc213adfbe78a02bed7082a6bd99c883"
|
|
|
|
const (
|
|
// AudienceUser is the frontend jwt-guard audience.
|
|
AudienceUser = "user"
|
|
// AudienceBackend is the admin jwt-guard audience.
|
|
AudienceBackend = "backend"
|
|
)
|
|
|
|
type registeredClaims struct {
|
|
jwt.RegisteredClaims
|
|
Prv string `json:"prv,omitempty"`
|
|
}
|
|
|
|
// Mint signs a frontend-audience HS256 token. iss is the minting endpoint URL.
|
|
// The returned jti is the token's own jti claim.
|
|
func Mint(secret, sub, issuerURL string, ttl time.Duration) (string, string, error) {
|
|
return MintAudience(secret, sub, issuerURL, ttl, AudienceUser)
|
|
}
|
|
|
|
// MintAudience signs an HS256 token for audience. Frontend tokens keep the PHP
|
|
// prv lock-subject; backend tokens omit it.
|
|
func MintAudience(secret, sub, issuerURL string, ttl time.Duration, audience string) (string, string, error) {
|
|
if strings.TrimSpace(secret) == "" {
|
|
return "", "", fmt.Errorf("bouncer: jwt secret is empty")
|
|
}
|
|
if strings.TrimSpace(audience) == "" {
|
|
return "", "", fmt.Errorf("bouncer: jwt audience is empty")
|
|
}
|
|
jti, err := newJTI()
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
now := time.Now()
|
|
prv := ""
|
|
if audience == AudienceUser {
|
|
prv = prvHash
|
|
}
|
|
claims := registeredClaims{
|
|
RegisteredClaims: jwt.RegisteredClaims{
|
|
Issuer: issuerURL,
|
|
Subject: sub,
|
|
Audience: jwt.ClaimStrings{audience},
|
|
ExpiresAt: jwt.NewNumericDate(now.Add(ttl)),
|
|
NotBefore: jwt.NewNumericDate(now),
|
|
IssuedAt: jwt.NewNumericDate(now),
|
|
ID: jti,
|
|
},
|
|
Prv: prv,
|
|
}
|
|
signed, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(secret))
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
return signed, jti, nil
|
|
}
|
|
|
|
func newJTI() (string, error) {
|
|
buf := make([]byte, 16)
|
|
if _, err := rand.Read(buf); err != nil {
|
|
return "", err
|
|
}
|
|
return hex.EncodeToString(buf), nil
|
|
}
|