package security import ( "git.infra.nkode.tech/dkelly/nkode-core/config" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" "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 := ParseHexString(jwtSecret) if err != nil { log.Fatalf("error parsing jwt secret %v", err) } return jwtBytes } func NewAuthenticationTokens(username string, customerId uuid.UUID) (AuthenticationTokens, error) { accessClaims := NewAccessClaim(username, customerId) refreshClaims := jwt.RegisteredClaims{ Subject: username, Issuer: customerId.String(), 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 uuid.UUID) jwt.RegisteredClaims { // TODO: CHANGE ISSUER TO BE URL return jwt.RegisteredClaims{ Subject: username, Issuer: customerId.String(), 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 ParseResetNKodeClaim(token string) (*ResetNKodeClaims, error) { return parseJwt[*ResetNKodeClaims](token, &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, config.ErrInvalidJwt } claims, ok := token.Claims.(T) if !ok { return nil, config.ErrInvalidJwt } return claims, nil } func ClaimExpired(claims jwt.RegisteredClaims) error { if claims.ExpiresAt == nil { return config.ErrClaimExpOrNil } if claims.ExpiresAt.Time.After(time.Now()) { return nil } return config.ErrClaimExpOrNil } func ResetNKodeToken(userEmail string, customerId string) (string, error) { resetClaims := ResetNKodeClaims{ true, jwt.RegisteredClaims{ Subject: userEmail, Issuer: customerId, ExpiresAt: jwt.NewNumericDate(time.Now().Add(resetNKodeTokenExp)), }, } return EncodeAndSignClaims(resetClaims) }