116 lines
3.7 KiB
Go
116 lines
3.7 KiB
Go
package system
|
|
|
|
import (
|
|
"errors"
|
|
"kra/internal/biz/system"
|
|
"time"
|
|
|
|
jwt "github.com/golang-jwt/jwt/v5"
|
|
)
|
|
|
|
// tokenAudience is the KRA administrator audience claim. Claims, audience,
|
|
// issuer and password-version fields are this application's security
|
|
// protocol, so they stay beside the only issuer that signs them.
|
|
const tokenAudience = "KRA"
|
|
|
|
var (
|
|
errEmptySigningKey = errors.New("empty JWT signing key")
|
|
errInvalidClaims = errors.New("invalid token claims")
|
|
)
|
|
|
|
type tokenClaims struct {
|
|
UUID string
|
|
ID uint
|
|
Username string
|
|
NickName string
|
|
AuthorityID uint `json:"AuthorityId"`
|
|
BufferTime int64
|
|
UserType string
|
|
MustChangePwd bool `json:"mustChangePwd"`
|
|
PasswordVersion int64 `json:"passwordVersion,omitempty"`
|
|
jwt.RegisteredClaims
|
|
}
|
|
|
|
func generateToken(secret, issuer string, expires, buffer time.Duration, userID, authorityID uint, uuid, username, nickname string, mustChange bool, passwordVersion int64) (string, *tokenClaims, error) {
|
|
if err := validateSigningOptions(secret, expires, buffer); err != nil {
|
|
return "", nil, err
|
|
}
|
|
now := time.Now()
|
|
claims := &tokenClaims{UUID: uuid, ID: userID, Username: username, NickName: nickname, AuthorityID: authorityID, BufferTime: int64(buffer / time.Second), UserType: "admin", MustChangePwd: mustChange, PasswordVersion: passwordVersion, RegisteredClaims: jwt.RegisteredClaims{Audience: jwt.ClaimStrings{tokenAudience}, Issuer: issuer, IssuedAt: jwt.NewNumericDate(now), NotBefore: jwt.NewNumericDate(now.Add(-time.Second)), ExpiresAt: jwt.NewNumericDate(now.Add(expires))}}
|
|
token, err := signTokenUnchecked(secret, claims)
|
|
return token, claims, err
|
|
}
|
|
|
|
func signToken(secret string, claims *tokenClaims) (string, error) {
|
|
if secret == "" {
|
|
return "", errEmptySigningKey
|
|
}
|
|
if claims == nil {
|
|
return "", errInvalidClaims
|
|
}
|
|
return signTokenUnchecked(secret, claims)
|
|
}
|
|
|
|
func signTokenUnchecked(secret string, claims *tokenClaims) (string, error) {
|
|
return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(secret))
|
|
}
|
|
|
|
// parseTokenWithIssuer validates a KRA admin token and maps driver errors to
|
|
// the biz token errors callers above already branch on.
|
|
func parseTokenWithIssuer(tokenString, secret, issuer string) (*tokenClaims, error) {
|
|
if secret == "" {
|
|
return nil, errEmptySigningKey
|
|
}
|
|
options := []jwt.ParserOption{
|
|
jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}),
|
|
jwt.WithAudience(tokenAudience),
|
|
jwt.WithExpirationRequired(),
|
|
jwt.WithNotBeforeRequired(),
|
|
jwt.WithLeeway(time.Second),
|
|
}
|
|
if issuer != "" {
|
|
options = append(options, jwt.WithIssuer(issuer))
|
|
}
|
|
token, err := jwt.ParseWithClaims(tokenString, &tokenClaims{}, func(token *jwt.Token) (any, error) {
|
|
if token.Method != jwt.SigningMethodHS256 {
|
|
return nil, errors.New("unexpected signing method")
|
|
}
|
|
return []byte(secret), nil
|
|
}, options...)
|
|
if err != nil {
|
|
switch {
|
|
case errors.Is(err, jwt.ErrTokenExpired):
|
|
return nil, system.ErrTokenExpired
|
|
case errors.Is(err, jwt.ErrTokenMalformed):
|
|
return nil, system.ErrTokenMalformed
|
|
case errors.Is(err, jwt.ErrTokenSignatureInvalid):
|
|
return nil, system.ErrTokenSignatureInvalid
|
|
case errors.Is(err, jwt.ErrTokenNotValidYet):
|
|
return nil, system.ErrTokenNotValidYet
|
|
default:
|
|
return nil, system.ErrTokenInvalid
|
|
}
|
|
}
|
|
if !token.Valid {
|
|
return nil, system.ErrTokenInvalid
|
|
}
|
|
claims, ok := token.Claims.(*tokenClaims)
|
|
if !ok {
|
|
return nil, system.ErrTokenInvalid
|
|
}
|
|
return claims, nil
|
|
}
|
|
|
|
func validateSigningOptions(secret string, expires, buffer time.Duration) error {
|
|
if secret == "" {
|
|
return errEmptySigningKey
|
|
}
|
|
if expires <= 0 {
|
|
return errors.New("JWT expiration must be positive")
|
|
}
|
|
if buffer < 0 {
|
|
return errors.New("JWT buffer must not be negative")
|
|
}
|
|
return nil
|
|
}
|