package core import ( "github.com/golang-jwt/jwt/v5" "go-nkode/util" "log" "os" "time" ) type AuthenticationTokens struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` } type ResetNKodeClaims struct { Reset bool `json:"reset"` jwt.RegisteredClaims } const ( accessTokenExp = 5 * time.Minute refreshTokenExp = 30 * 24 * time.Hour resetNKodeTokenExp = 5 * time.Minute ) var secret = getJwtSecret() func getJwtSecret() []byte { jwtSecret := os.Getenv("JWT_SECRET") if jwtSecret == "" { log.Fatal("No JWT_SECRET found") } jwtBytes, err := util.ParseHexString(jwtSecret) if err != nil { log.Fatalf("error parsing jwt secret %v", err) } return jwtBytes } func NewAuthenticationTokens(username string, customerId CustomerId) (AuthenticationTokens, error) { accessClaims := NewAccessClaim(username, customerId) refreshClaims := jwt.RegisteredClaims{ Subject: username, Issuer: CustomerIdToString(customerId), ExpiresAt: jwt.NewNumericDate(time.Now().Add(refreshTokenExp)), } accessJwt, err := EncodeAndSignClaims(accessClaims) if err != nil { return AuthenticationTokens{}, err } refreshJwt, err := EncodeAndSignClaims(refreshClaims) if err != nil { return AuthenticationTokens{}, err } return AuthenticationTokens{ AccessToken: accessJwt, RefreshToken: refreshJwt, }, nil } func NewAccessClaim(username string, customerId CustomerId) jwt.RegisteredClaims { return jwt.RegisteredClaims{ Subject: username, Issuer: CustomerIdToString(customerId), ExpiresAt: jwt.NewNumericDate(time.Now().Add(accessTokenExp)), } } func EncodeAndSignClaims(claims jwt.Claims) (string, error) { token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(secret) } func ParseRegisteredClaimToken(token string) (*jwt.RegisteredClaims, error) { return parseJwt[*jwt.RegisteredClaims](token, &jwt.RegisteredClaims{}) } func ParseRestNKodeToken(resetNKodeToken string) (*ResetNKodeClaims, error) { return parseJwt[*ResetNKodeClaims](resetNKodeToken, &ResetNKodeClaims{}) } func parseJwt[T *ResetNKodeClaims | *jwt.RegisteredClaims](tokenStr string, claim jwt.Claims) (T, error) { token, err := jwt.ParseWithClaims(tokenStr, claim, func(token *jwt.Token) (interface{}, error) { return secret, nil }) if err != nil { log.Printf("error parsing refresh token: %v", err) return nil, ErrInvalidJwt } claims, ok := token.Claims.(T) if !ok { return nil, ErrInvalidJwt } return claims, nil } func ClaimExpired(claims jwt.RegisteredClaims) error { if claims.ExpiresAt == nil { return ErrClaimExpOrNil } if claims.ExpiresAt.Time.After(time.Now()) { return nil } return ErrClaimExpOrNil } func ResetNKodeToken(userEmail UserEmail, customerId CustomerId) (string, error) { resetClaims := ResetNKodeClaims{ true, jwt.RegisteredClaims{ Subject: string(userEmail), Issuer: CustomerIdToString(customerId), ExpiresAt: jwt.NewNumericDate(time.Now().Add(resetNKodeTokenExp)), }, } return EncodeAndSignClaims(resetClaims) }