Files
summercms/modules/bouncer/mint.go
Jakub Zych 5e50b166ef refactor(10.2-01): nest framework packages under modules
- Move remaining beach packages and embedded admin assets\n- Rewrite framework, example, build, and gate paths
2026-09-28 02:21:02 +02:00

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
}