Skip to content
Snippets Groups Projects
jwt.go 5.5 KiB
Newer Older
Jano Hendriks's avatar
Jano Hendriks committed
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)
}

Jano Hendriks's avatar
Jano Hendriks committed
// GenerateJWT takes the payload and generates a signed JWT using the provided secret
func GenerateJWT(payload JsonWebToken, secretKey string) (string, error) {
Jano Hendriks's avatar
Jano Hendriks committed
	// 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))
Jano Hendriks's avatar
Jano Hendriks committed
	if err != nil {
		return "", err
	}

	return tokenString, nil
}

func getJsonWebTokenFromTokenClaims(token *jwt.Token, checkValidity bool) (JsonWebToken, error) {
Jano Hendriks's avatar
Jano Hendriks committed
	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) {
Jano Hendriks's avatar
Jano Hendriks committed
		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
	}

Jano Hendriks's avatar
Jano Hendriks committed
	// 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)
Jano Hendriks's avatar
Jano Hendriks committed
	}

	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
}