hermes/backend/api/jwtcore/jwt.go
imterah 538c5b6c51
All checks were successful
Release code / build (push) Successful in 11m41s
chore: Adds "day-one"/v2.0.1 bug fixes.
This fixes database error reporting, as well as majorly fixes users
not being able to authenticate to the API if you used the automated
migration setup, as the password would remain in hex encoding.

We now decode the hexadecimal and then change it to the far more
compact base64 encoding before adding it to the database. This should
fix login, and not cause 500 Interal Server Errors anymore.

Sorry folks!
2024-12-27 00:10:13 -05:00

117 lines
2.3 KiB
Go

package jwtcore
import (
"encoding/base64"
"errors"
"fmt"
"os"
"strconv"
"time"
"git.terah.dev/imterah/hermes/api/dbcore"
"github.com/golang-jwt/jwt/v5"
)
var (
JWTKey []byte
developmentMode bool
)
func SetupJWT() error {
var err error
jwtDataString := os.Getenv("HERMES_JWT_SECRET")
if jwtDataString == "" {
return fmt.Errorf("JWT secret isn't set (missing HERMES_JWT_SECRET)")
}
if os.Getenv("HERMES_JWT_BASE64_ENCODED") != "" {
JWTKey, err = base64.StdEncoding.DecodeString(jwtDataString)
if err != nil {
return fmt.Errorf("failed to decode base64 JWT: %s", err.Error())
}
} else {
JWTKey = []byte(jwtDataString)
}
if os.Getenv("HERMES_DEVELOPMENT_MODE") != "" {
developmentMode = true
}
return nil
}
func Parse(tokenString string, options ...jwt.ParserOption) (*jwt.Token, error) {
return jwt.Parse(tokenString, JWTKeyCallback, options...)
}
func GetUserFromJWT(token string) (*dbcore.User, error) {
parsedJWT, err := 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 := &dbcore.User{}
userRequest := dbcore.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 Generate(uid uint) (string, error) {
timeMultiplier := 3
if developmentMode {
timeMultiplier = 60 * 24
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(timeMultiplier) * time.Minute)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Audience: []string{strconv.Itoa(int(uid))},
})
signedToken, err := token.SignedString(JWTKey)
if err != nil {
return "", err
}
return signedToken, nil
}
func JWTKeyCallback(*jwt.Token) (interface{}, error) {
return JWTKey, nil
}