package logic import ( "errors" "strings" "time" "github.com/golang-jwt/jwt/v5" ) const TypeAdmin = "admin" var ( ErrInvalidToken = errors.New("invalid admin token") ErrWrongType = errors.New("token type mismatch") ) // Claims matches Java JwtTokenProvider admin tokens (popadmin.wyrs365.com). type Claims struct { AdminID int64 `json:"adminId"` Role string `json:"role"` Type string `json:"type"` Permissions string `json:"permissions"` AllowedSites string `json:"allowedSites"` SiteRealm string `json:"siteRealm"` jwt.RegisteredClaims } // TokenService validates HS256 admin JWTs issued by the Java / popadmin login. type TokenService struct { secret []byte } func NewTokenService(secret string) *TokenService { sec := []byte(strings.TrimSpace(secret)) if len(sec) < 32 { padded := make([]byte, 32) copy(padded, sec) sec = padded } return &TokenService{secret: sec} } func (s *TokenService) Parse(tokenStr string) (*Claims, error) { if s == nil || len(s.secret) == 0 { return nil, ErrInvalidToken } t, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(t *jwt.Token) (interface{}, error) { // Java JwtTokenProvider signs with HS512; local CreateToken / tests use HS256. switch t.Method { case jwt.SigningMethodHS256, jwt.SigningMethodHS512: return s.secret, nil default: return nil, ErrInvalidToken } }) if err != nil { return nil, ErrInvalidToken } claims, ok := t.Claims.(*Claims) if !ok || !t.Valid { return nil, ErrInvalidToken } if claims.Type != "" && claims.Type != TypeAdmin { return nil, ErrWrongType } return claims, nil } // CreateToken is for tests and local smoke only (production tokens come from Java login). func (s *TokenService) CreateToken(adminID int64, username, role, permissions string) (string, error) { now := time.Now() claims := Claims{ AdminID: adminID, Role: role, Type: TypeAdmin, Permissions: permissions, RegisteredClaims: jwt.RegisteredClaims{ Subject: username, IssuedAt: jwt.NewNumericDate(now), ExpiresAt: jwt.NewNumericDate(now.Add(24 * time.Hour)), }, } tok := jwt.NewWithClaims(jwt.SigningMethodHS256, &claims) return tok.SignedString(s.secret) }