jwtsession/pkg.go

139 lines
3.5 KiB
Go

/*
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
"crypto"
"fmt"
"time"
)
type KeyType int
const (
KeyEd25519 KeyType = iota
KeyEcDSA
)
type Manager struct {
privKey crypto.PrivateKey
PubKey crypto.PublicKey
ExpireDays int
Initialized bool
}
func NewManager(keyType KeyType, private, public string, expireDays int) (mngr Manager, err error) { // {{{
var errPriv, errPub error
switch keyType {
case KeyEcDSA:
mngr.privKey, errPriv = jwt.ParseECPrivateKeyFromPEM([]byte(private))
mngr.PubKey, errPub = jwt.ParseECPublicKeyFromPEM([]byte(public))
case KeyEd25519:
mngr.privKey, errPriv = jwt.ParseEdPrivateKeyFromPEM([]byte(private))
mngr.PubKey, errPub = jwt.ParseEdPublicKeyFromPEM([]byte(public))
}
if errPriv != nil {
err = errPriv
return
}
if errPub != nil {
err = errPub
return
}
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.SigningMethodEd25519{}, jwt.MapClaims(data))
// Sign and get the complete encoded token as a string using the secret.
signedString, _ = token.SignedString(mngr.privKey)
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.SigningMethodEd25519); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return mngr.PubKey, 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
} // }}}