authkit initial

This commit is contained in:
juancwu 2026-04-26 01:36:53 +00:00
commit 134393fbca
43 changed files with 5188 additions and 1 deletions

69
jwt.go Normal file
View file

@ -0,0 +1,69 @@
package authkit
import (
"git.juancwu.dev/juancwu/errx"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
)
// accessClaims is the JWT shape issued by IssueJWT. The session_version
// field carries the User.SessionVersion at issue time so AuthenticateJWT
// can detect global revocations (logout-everywhere, password change).
type accessClaims struct {
jwt.RegisteredClaims
SessionVersion int `json:"sv"`
Method string `json:"m"`
}
func (a *Auth) signAccessToken(userID uuid.UUID, sessionVersion int) (string, error) {
const op = "authkit.signAccessToken"
now := a.now()
claims := accessClaims{
RegisteredClaims: jwt.RegisteredClaims{
Subject: userID.String(),
Issuer: a.cfg.JWTIssuer,
Audience: jwt.ClaimStrings{a.cfg.JWTAudience},
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(now.Add(a.cfg.AccessTokenTTL)),
ID: uuid.NewString(),
},
SessionVersion: sessionVersion,
Method: string(AuthMethodJWT),
}
tok := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signed, err := tok.SignedString(a.cfg.JWTSecret)
if err != nil {
return "", errx.Wrap(op, err)
}
return signed, nil
}
// parseAccessToken validates the signature and returns the parsed claims.
func (a *Auth) parseAccessToken(token string) (*accessClaims, error) {
const op = "authkit.parseAccessToken"
opts := []jwt.ParserOption{
jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}),
jwt.WithExpirationRequired(),
jwt.WithIssuedAt(),
jwt.WithTimeFunc(a.cfg.Clock),
}
if a.cfg.JWTIssuer != "" {
opts = append(opts, jwt.WithIssuer(a.cfg.JWTIssuer))
}
if a.cfg.JWTAudience != "" {
opts = append(opts, jwt.WithAudience(a.cfg.JWTAudience))
}
parser := jwt.NewParser(opts...)
parsed, err := parser.ParseWithClaims(token, &accessClaims{}, func(t *jwt.Token) (any, error) {
return a.cfg.JWTSecret, nil
})
if err != nil {
return nil, errx.Wrap(op, ErrTokenInvalid)
}
claims, ok := parsed.Claims.(*accessClaims)
if !ok || !parsed.Valid {
return nil, errx.Wrap(op, ErrTokenInvalid)
}
return claims, nil
}