feat(07-01): add JWT mint, refresh, and blacklist primitives

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jakub Zych
2026-09-22 13:34:58 +02:00
parent 251f3cc4a0
commit cad445a235
6 changed files with 348 additions and 11 deletions

57
bouncer/mint.go Normal file
View File

@@ -0,0 +1,57 @@
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
}