From f26e6c10b3affe82abf9ef0737af19754a03d69c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Sun, 4 May 2025 08:48:06 +0200 Subject: [PATCH] Initial commit --- go.mod | 5 +++ go.sum | 2 ++ pkg.go | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 pkg.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..19a1ee7 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module jwtsession + +go 1.24.2 + +require github.com/golang-jwt/jwt/v5 v5.2.2 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..daf1a90 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= diff --git a/pkg.go b/pkg.go new file mode 100644 index 0000000..abeb026 --- /dev/null +++ b/pkg.go @@ -0,0 +1,111 @@ +/* +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 +} // }}}