This restructures dbcore (now the db package) and jwtcore (now the jwt package) to use a single struct. There is now a state package, which contains a struct with the full application state. After this, instead of initializing the API routes directly in the main function, the state object gets passed, and the API routes get initialized with their accompanying code. One fix done to reduce memory usage and increase speed is that the validator object is now persistent across requests, instead of recreating it each time. This should speed things up slightly, and improve memory usage. One additional chore done is that the database models have been moved to be a seperate file from the DB initialization itself.
107 lines
2.4 KiB
Go
107 lines
2.4 KiB
Go
package jwt
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"time"
|
|
|
|
"git.terah.dev/imterah/hermes/backend/api/db"
|
|
"github.com/golang-jwt/jwt/v5"
|
|
)
|
|
|
|
var (
|
|
DevelopmentModeTimings = time.Duration(60*24) * time.Minute
|
|
NormalModeTimings = time.Duration(3) * time.Minute
|
|
)
|
|
|
|
type JWTCore struct {
|
|
Key []byte
|
|
Database *db.DB
|
|
TimeMultiplier time.Duration
|
|
}
|
|
|
|
func New(key []byte, database *db.DB, timeMultiplier time.Duration) *JWTCore {
|
|
jwtCore := &JWTCore{
|
|
Key: key,
|
|
Database: database,
|
|
TimeMultiplier: timeMultiplier,
|
|
}
|
|
|
|
return jwtCore
|
|
}
|
|
|
|
func (jwtCore *JWTCore) Parse(tokenString string, options ...jwt.ParserOption) (*jwt.Token, error) {
|
|
return jwt.Parse(tokenString, jwtCore.jwtKeyCallback, options...)
|
|
}
|
|
|
|
func (jwtCore *JWTCore) GetUserFromJWT(token string) (*db.User, error) {
|
|
if jwtCore.Database == nil {
|
|
return nil, fmt.Errorf("database is not initialized")
|
|
}
|
|
|
|
parsedJWT, err := jwtCore.Parse(token)
|
|
|
|
if err != nil {
|
|
if errors.Is(err, jwt.ErrTokenExpired) {
|
|
return nil, fmt.Errorf("token is expired")
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
audience, err := parsedJWT.Claims.GetAudience()
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(audience) < 1 {
|
|
return nil, fmt.Errorf("audience is too small")
|
|
}
|
|
|
|
uid, err := strconv.Atoi(audience[0])
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
user := &db.User{}
|
|
userRequest := jwtCore.Database.DB.Preload("Permissions").Where("id = ?", uint(uid)).Find(&user)
|
|
|
|
if userRequest.Error != nil {
|
|
return user, fmt.Errorf("failed to find if user exists or not: %s", userRequest.Error.Error())
|
|
}
|
|
|
|
userExists := userRequest.RowsAffected > 0
|
|
|
|
if !userExists {
|
|
return user, fmt.Errorf("user does not exist")
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func (jwtCore *JWTCore) Generate(uid uint) (string, error) {
|
|
currentJWTTime := jwt.NewNumericDate(time.Now())
|
|
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
|
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(jwtCore.TimeMultiplier)),
|
|
IssuedAt: currentJWTTime,
|
|
NotBefore: currentJWTTime,
|
|
// Convert the user ID to a string, and then set it as the audience parameters only value (there's only 1 user per key)
|
|
Audience: []string{strconv.Itoa(int(uid))},
|
|
})
|
|
|
|
signedToken, err := token.SignedString(jwtCore.Key)
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return signedToken, nil
|
|
}
|
|
|
|
func (jwtCore *JWTCore) jwtKeyCallback(*jwt.Token) (any, error) {
|
|
return jwtCore.Key, nil
|
|
}
|