/* The JWT claims contains: cid - Session UUUID uid - User ID iat - Issued at exp - Expires login - Username name - User name auth - Session authenticated perm - User permissions */ package jwtsession import ( // External "github.com/golang-jwt/jwt/v5" // Standard "encoding/hex" "fmt" "time" ) type Manager struct { secret []byte ExpireDays int Initialized bool } func NewManager(secret string, expireDays int) (mngr Manager, err error) { // {{{ mngr.secret, err = hex.DecodeString(secret) mngr.ExpireDays = expireDays mngr.Initialized = true return } // }}} func (mngr *Manager) NewToken(sessionID string, userID int, username, name string) (token string) { // {{{ // A new token is generated with the information. data := make(map[string]any) data["cid"] = sessionID data["uid"] = userID data["login"] = username data["name"] = name token = mngr.GenerateToken(data) return } // }}} func (mngr *Manager) GenerateToken(data map[string]any) (signedString string) { // {{{ // Create a new token object, specifying signing method and the claims // you would like it to contain. now := time.Now() data["iat"] = now.Unix() data["exp"] = now.Add(time.Hour * 24 * time.Duration(mngr.ExpireDays)).Unix() token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(data)) // Sign and get the complete encoded token as a string using the secret. signedString, _ = token.SignedString(mngr.secret) return } // }}} func (mngr *Manager) ParseToken(tokenString string) (jwt.MapClaims, error) { // {{{ // Parse takes the token string and a function for looking up the key. The latter is especially // useful if you use multiple keys for your application. The standard is to use 'kid' in the // head of the token to identify which key to use, but the parsed token (head and claims) is provided // to the callback, providing flexibility. token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) { // Don't forget to validate the alg is what you expect: if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key") return mngr.secret, nil }) if err != nil { return nil, err } if claims, ok := token.Claims.(jwt.MapClaims); ok { err = validateTokenTimestamps(claims) if err != nil { return nil, err } return claims, nil } else { return nil, err } } // }}} func (mngr *Manager) GetTokenClaims(tokenString string) (jwt.MapClaims, error) { // {{{ return nil, nil } // }}} func validateTokenTimestamps(claims jwt.MapClaims) error { // {{{ now := time.Now() if issuedAt, ok := claims["iat"].(float64); ok { if now.Unix() < int64(issuedAt) { return fmt.Errorf("Token is not valid yet") } } else { return fmt.Errorf("Token is missing iat") } if expires, ok := claims["exp"].(float64); ok { if now.Unix() > int64(expires) { return fmt.Errorf("Token has expired") } } return nil } // }}}