package auth import ( "encoding/json" "github.com/golang-jwt/jwt/v4" "gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/date_utils" "gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors" "net/http" "time" ) type JsonWebToken struct { UserID string `json:"user_id"` ProviderID int64 `json:"provider_id,omitempty"` ExpiryDate time.Time `json:"expiry_date"` } // GenerateJWTWithSessionToken first signs the session token with the secret key, then takes the payload and generates a // signed JWT using the resulting signed session token func GenerateJWTWithSessionToken(payload JsonWebToken, secretKey string, sessionToken string) (string, error) { signedSessionToken := SignSessionTokenWithKey(secretKey, sessionToken) return GenerateJWT(payload, signedSessionToken) } // GenerateJWT takes the payload and generates a signed JWT using the provided secret func GenerateJWT(payload JsonWebToken, secretKey string) (string, error) { // Convert the JsonWebToken to a map[string]interface{} tokenBytes, err := json.Marshal(payload) if err != nil { return "", err } tokenMap := make(map[string]interface{}) err = json.Unmarshal(tokenBytes, &tokenMap) if err != nil { return "", err } // Create the signed token token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(tokenMap)) tokenString, err := token.SignedString([]byte(secretKey)) if err != nil { return "", err } return tokenString, nil } func getJsonWebTokenFromTokenClaims(token *jwt.Token, checkValidity bool) (JsonWebToken, error) { if token == nil { return JsonWebToken{}, errors.Error("could not get token from token string") } claims, ok := token.Claims.(jwt.MapClaims) if !ok || (checkValidity && token.Valid == false) { return JsonWebToken{}, errors.Error("invalid token") } // Convert the MapClaims to a JsonWebToken claimsBytes, err := json.Marshal(claims) if err != nil { return JsonWebToken{}, err } var jsonWebToken JsonWebToken err = json.Unmarshal(claimsBytes, &jsonWebToken) if err != nil { return JsonWebToken{}, err } return jsonWebToken, nil } // ValidateJWTWithSessionToken first signs the session token using the secret key, then parses the JWT and validates // that it is signed correctly func ValidateJWTWithSessionToken(jsonWebTokenString string, secretKey string, sessionToken string) (JsonWebToken, error) { // Sign the session token with the secret key - this prevents the JWT from being used by other sessions signedSecret := SignSessionTokenWithKey(secretKey, sessionToken) return ValidateJWT(jsonWebTokenString, signedSecret) } // ValidateJWT parses the JWT and validates that it is signed correctly func ValidateJWT(jsonWebTokenString string, secretKey string) (JsonWebToken, error) { // Validate the token, err := jwt.Parse(jsonWebTokenString, func(token *jwt.Token) (interface{}, error) { // Validate the signing method if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, errors.Errorf("unexpected signing method: %v", token.Header["alg"]) } return []byte(secretKey), nil }) if err != nil { return JsonWebToken{}, err } jsonWebToken, err := getJsonWebTokenFromTokenClaims(token, true) if err != nil { return JsonWebToken{}, err } // Validate the expiry date if jsonWebToken.ExpiryDate.Before(date_utils.CurrentDate()) { return jsonWebToken, errors.Error("token has expired") } return jsonWebToken, nil } // LoginWithPassword checks that the provided password is correct. If the password is correct, a signed JWT is returned // using the provided encryption key. func LoginWithPassword(password string, hashedPassword string, jsonWebToken JsonWebToken, jwtEncryptionKey string) (string, error) { if PasswordIsCorrect(password, hashedPassword) { return GenerateJWT(jsonWebToken, jwtEncryptionKey) } return "", errors.HTTPWithMsg(http.StatusBadRequest, "password is incorrect") } // LoginSessionWithPassword checks that the provided password is correct. If the password is correct, the session token // is signed using the secret key, and a JWT is returned using the signed session token func LoginSessionWithPassword(password string, hashedPassword string, jsonWebToken JsonWebToken, secretKey string, sessionToken string) (string, error) { if PasswordIsCorrect(password, hashedPassword) { return GenerateJWTWithSessionToken(jsonWebToken, secretKey, sessionToken) } return "", errors.HTTPWithMsg(http.StatusBadRequest, "password is incorrect") } // GetUserIDFromJWTWithoutValidation gets the userID from the jsonWebTokenString without validating the signature. // Successful execution of this function DOES NOT indicate that the JWT is valid in any way. func GetUserIDFromJWTWithoutValidation(jsonWebTokenString string) string { token, _, err := jwt.NewParser().ParseUnverified(jsonWebTokenString, jwt.MapClaims{}) if err != nil { return "" } jsonWebToken, err := getJsonWebTokenFromTokenClaims(token, false) if err != nil { return "" } return jsonWebToken.UserID } // GetUserAndProviderIDFromJWTWithoutValidation gets the userID and providerID from the jsonWebTokenString without validating the // signature. Successful execution of this function DOES NOT indicate that the JWT is valid in any way. func GetUserAndProviderIDFromJWTWithoutValidation(jsonWebTokenString string) (string, int64) { token, _, err := jwt.NewParser().ParseUnverified(jsonWebTokenString, jwt.MapClaims{}) if err != nil { return "", 0 } jsonWebToken, err := getJsonWebTokenFromTokenClaims(token, false) if err != nil { return "", 0 } return jsonWebToken.UserID, jsonWebToken.ProviderID }