authorizer/server/token/auth_token.go

210 lines
5.7 KiB
Go
Raw Normal View History

package token
import (
"encoding/json"
"errors"
"fmt"
"log"
"os"
"strings"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
2022-02-28 15:56:49 +00:00
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
"github.com/google/uuid"
"github.com/robertkrimen/otto"
)
// JWTToken is a struct to hold JWT token and its expiration time
type JWTToken struct {
Token string `json:"token"`
ExpiresAt int64 `json:"expires_at"`
}
// Token object to hold the finger print and refresh token information
type Token struct {
FingerPrint string `json:"fingerprint"`
FingerPrintHash string `json:"fingerprint_hash"`
RefreshToken *JWTToken `json:"refresh_token"`
AccessToken *JWTToken `json:"access_token"`
}
// CreateAuthToken creates a new auth token when userlogs in
func CreateAuthToken(user models.User, roles []string) (*Token, error) {
fingerprint := uuid.NewString()
2022-02-28 15:56:49 +00:00
fingerPrintHashBytes, err := crypto.EncryptAES([]byte(fingerprint))
if err != nil {
return nil, err
}
refreshToken, refreshTokenExpiresAt, err := CreateRefreshToken(user, roles)
if err != nil {
return nil, err
}
accessToken, accessTokenExpiresAt, err := CreateAccessToken(user, roles)
if err != nil {
return nil, err
}
return &Token{
FingerPrint: fingerprint,
FingerPrintHash: string(fingerPrintHashBytes),
RefreshToken: &JWTToken{Token: refreshToken, ExpiresAt: refreshTokenExpiresAt},
AccessToken: &JWTToken{Token: accessToken, ExpiresAt: accessTokenExpiresAt},
}, nil
}
// CreateRefreshToken util to create JWT token
func CreateRefreshToken(user models.User, roles []string) (string, int64, error) {
// expires in 1 year
expiryBound := time.Hour * 8760
expiresAt := time.Now().Add(expiryBound).Unix()
customClaims := jwt.MapClaims{
2022-02-22 05:36:47 +00:00
"iss": "",
"aud": "",
"sub": user.ID,
"exp": expiresAt,
"iat": time.Now().Unix(),
"token_type": constants.TokenTypeRefreshToken,
"roles": roles,
"id": user.ID,
}
2022-02-12 10:24:23 +00:00
token, err := SignJWTToken(customClaims)
if err != nil {
return "", 0, err
}
return token, expiresAt, nil
}
// CreateAccessToken util to create JWT token, based on
// user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT
func CreateAccessToken(user models.User, roles []string) (string, int64, error) {
expiryBound := time.Minute * 30
expiresAt := time.Now().Add(expiryBound).Unix()
resUser := user.AsAPIUser()
userBytes, _ := json.Marshal(&resUser)
var userMap map[string]interface{}
json.Unmarshal(userBytes, &userMap)
2022-02-28 02:25:01 +00:00
claimKey := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim)
customClaims := jwt.MapClaims{
2022-02-22 05:36:47 +00:00
"iss": "",
2022-02-28 02:25:01 +00:00
"aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID),
2022-02-22 05:36:47 +00:00
"nonce": "",
"sub": user.ID,
"exp": expiresAt,
"iat": time.Now().Unix(),
"token_type": constants.TokenTypeAccessToken,
"allowed_roles": strings.Split(user.Roles, ","),
claimKey: roles,
}
for k, v := range userMap {
if k != "roles" {
customClaims[k] = v
}
}
// check for the extra access token script
accessTokenScript := os.Getenv(constants.EnvKeyCustomAccessTokenScript)
if accessTokenScript != "" {
vm := otto.New()
claimBytes, _ := json.Marshal(customClaims)
vm.Run(fmt.Sprintf(`
var user = %s;
var tokenPayload = %s;
var customFunction = %s;
var functionRes = JSON.stringify(customFunction(user, tokenPayload));
`, string(userBytes), string(claimBytes), accessTokenScript))
val, err := vm.Get("functionRes")
if err != nil {
log.Println("error getting custom access token script:", err)
} else {
extraPayload := make(map[string]interface{})
err = json.Unmarshal([]byte(fmt.Sprintf("%s", val)), &extraPayload)
if err != nil {
log.Println("error converting accessTokenScript response to map:", err)
} else {
for k, v := range extraPayload {
customClaims[k] = v
}
}
}
}
2022-02-12 10:24:23 +00:00
token, err := SignJWTToken(customClaims)
if err != nil {
return "", 0, err
}
return token, expiresAt, nil
}
// GetAccessToken returns the access token from the request (either from header or cookie)
func GetAccessToken(gc *gin.Context) (string, error) {
token, err := cookie.GetAccessTokenCookie(gc)
if err != nil || token == "" {
// try to check in auth header for cookie
auth := gc.Request.Header.Get("Authorization")
if auth == "" {
return "", fmt.Errorf(`unauthorized`)
}
token = strings.TrimPrefix(auth, "Bearer ")
}
return token, nil
}
// GetRefreshToken returns the refresh token from cookie / request query url
func GetRefreshToken(gc *gin.Context) (string, error) {
token, err := cookie.GetRefreshTokenCookie(gc)
if err != nil || token == "" {
return "", fmt.Errorf(`unauthorized`)
}
return token, nil
}
// GetFingerPrint returns the finger print from cookie
func GetFingerPrint(gc *gin.Context) (string, error) {
fingerPrint, err := cookie.GetFingerPrintCookie(gc)
if err != nil || fingerPrint == "" {
return "", fmt.Errorf(`no finger print`)
}
return fingerPrint, nil
}
func ValidateAccessToken(gc *gin.Context) (map[string]interface{}, error) {
token, err := GetAccessToken(gc)
if err != nil {
return nil, err
}
2022-02-12 10:24:23 +00:00
claims, err := ParseJWTToken(token)
if err != nil {
return nil, err
}
// also validate if there is user session present with access token
sessions := sessionstore.GetUserSessions(claims["id"].(string))
if len(sessions) == 0 {
return nil, errors.New("unauthorized")
}
return claims, nil
}