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 user token. const prvHash = "a867434cbc213adfbe78a02bed7082a6bd99c883" type registeredClaims struct { jwt.RegisteredClaims Prv string `json:"prv,omitempty"` } // Mint signs an HS256 token whose iss is the full URL of the minting endpoint. // The returned jti is the token's own jti claim. func Mint(secret, sub, issuerURL string, ttl time.Duration) (string, string, error) { if strings.TrimSpace(secret) == "" { return "", "", fmt.Errorf("bouncer: jwt secret is empty") } jti, err := newJTI() if err != nil { return "", "", err } now := time.Now() claims := registeredClaims{ RegisteredClaims: jwt.RegisteredClaims{ Issuer: issuerURL, Subject: sub, ExpiresAt: jwt.NewNumericDate(now.Add(ttl)), NotBefore: jwt.NewNumericDate(now), IssuedAt: jwt.NewNumericDate(now), ID: jti, }, Prv: prvHash, } 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 }