2022-01-22 19:54:41 +00:00
|
|
|
package token
|
|
|
|
|
|
|
|
import (
|
2022-11-12 18:24:37 +00:00
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/base64"
|
2022-01-22 19:54:41 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2022-05-24 07:20:33 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
|
2022-03-02 12:12:31 +00:00
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/golang-jwt/jwt"
|
|
|
|
"github.com/robertkrimen/otto"
|
|
|
|
|
2022-01-22 19:54:41 +00:00
|
|
|
"github.com/authorizerdev/authorizer/server/constants"
|
2023-10-13 02:41:55 +00:00
|
|
|
"github.com/authorizerdev/authorizer/server/cookie"
|
2022-02-28 15:56:49 +00:00
|
|
|
"github.com/authorizerdev/authorizer/server/crypto"
|
2022-01-22 19:54:41 +00:00
|
|
|
"github.com/authorizerdev/authorizer/server/db/models"
|
2022-05-27 17:50:38 +00:00
|
|
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
2022-05-30 06:24:16 +00:00
|
|
|
"github.com/authorizerdev/authorizer/server/parsers"
|
2022-03-02 12:12:31 +00:00
|
|
|
"github.com/authorizerdev/authorizer/server/utils"
|
2022-01-22 19:54:41 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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 {
|
2023-04-08 07:36:15 +00:00
|
|
|
FingerPrint string `json:"fingerprint"`
|
|
|
|
// Session Token
|
|
|
|
FingerPrintHash string `json:"fingerprint_hash"`
|
|
|
|
SessionTokenExpiresAt int64 `json:"expires_at"`
|
|
|
|
RefreshToken *JWTToken `json:"refresh_token"`
|
|
|
|
AccessToken *JWTToken `json:"access_token"`
|
|
|
|
IDToken *JWTToken `json:"id_token"`
|
2022-03-02 12:12:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SessionData
|
|
|
|
type SessionData struct {
|
2022-06-29 16:54:00 +00:00
|
|
|
Subject string `json:"sub"`
|
|
|
|
Roles []string `json:"roles"`
|
|
|
|
Scope []string `json:"scope"`
|
|
|
|
Nonce string `json:"nonce"`
|
|
|
|
IssuedAt int64 `json:"iat"`
|
|
|
|
ExpiresAt int64 `json:"exp"`
|
|
|
|
LoginMethod string `json:"login_method"`
|
2022-03-02 12:12:31 +00:00
|
|
|
}
|
|
|
|
|
2022-11-12 18:24:37 +00:00
|
|
|
// CreateAuthToken creates a new auth token when userlogs in
|
2023-07-31 11:12:11 +00:00
|
|
|
func CreateAuthToken(gc *gin.Context, user *models.User, roles, scope []string, loginMethod, nonce string, code string) (*Token, error) {
|
2022-05-30 06:24:16 +00:00
|
|
|
hostname := parsers.GetHost(gc)
|
2023-04-08 07:36:15 +00:00
|
|
|
_, fingerPrintHash, sessionTokenExpiresAt, err := CreateSessionToken(user, nonce, roles, scope, loginMethod)
|
2022-01-22 19:54:41 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-06-29 16:54:00 +00:00
|
|
|
accessToken, accessTokenExpiresAt, err := CreateAccessToken(user, roles, scope, hostname, nonce, loginMethod)
|
2022-01-22 19:54:41 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-11-12 18:24:37 +00:00
|
|
|
atHash := sha256.New()
|
|
|
|
atHash.Write([]byte(accessToken))
|
|
|
|
atHashBytes := atHash.Sum(nil)
|
|
|
|
// hashedToken := string(bs)
|
|
|
|
atHashDigest := atHashBytes[0 : len(atHashBytes)/2]
|
|
|
|
atHashString := base64.RawURLEncoding.EncodeToString(atHashDigest)
|
|
|
|
|
|
|
|
codeHashString := ""
|
|
|
|
if code != "" {
|
|
|
|
codeHash := sha256.New()
|
|
|
|
codeHash.Write([]byte(code))
|
|
|
|
codeHashBytes := codeHash.Sum(nil)
|
|
|
|
codeHashDigest := codeHashBytes[0 : len(codeHashBytes)/2]
|
|
|
|
codeHashString = base64.RawURLEncoding.EncodeToString(codeHashDigest)
|
|
|
|
}
|
|
|
|
|
|
|
|
idToken, idTokenExpiresAt, err := CreateIDToken(user, roles, hostname, nonce, atHashString, codeHashString, loginMethod)
|
2022-01-22 19:54:41 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-03-02 12:12:31 +00:00
|
|
|
res := &Token{
|
2023-04-08 07:36:15 +00:00
|
|
|
FingerPrint: nonce,
|
|
|
|
FingerPrintHash: fingerPrintHash,
|
|
|
|
SessionTokenExpiresAt: sessionTokenExpiresAt,
|
|
|
|
AccessToken: &JWTToken{Token: accessToken, ExpiresAt: accessTokenExpiresAt},
|
|
|
|
IDToken: &JWTToken{Token: idToken, ExpiresAt: idTokenExpiresAt},
|
2022-03-02 12:12:31 +00:00
|
|
|
}
|
|
|
|
if utils.StringSliceContains(scope, "offline_access") {
|
2022-06-29 16:54:00 +00:00
|
|
|
refreshToken, refreshTokenExpiresAt, err := CreateRefreshToken(user, roles, scope, hostname, nonce, loginMethod)
|
2022-03-02 12:12:31 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
res.RefreshToken = &JWTToken{Token: refreshToken, ExpiresAt: refreshTokenExpiresAt}
|
|
|
|
}
|
|
|
|
|
|
|
|
return res, nil
|
2022-01-22 19:54:41 +00:00
|
|
|
}
|
|
|
|
|
2022-11-12 18:24:37 +00:00
|
|
|
// CreateSessionToken creates a new session token
|
2023-07-31 11:12:11 +00:00
|
|
|
func CreateSessionToken(user *models.User, nonce string, roles, scope []string, loginMethod string) (*SessionData, string, int64, error) {
|
2023-04-08 07:36:15 +00:00
|
|
|
expiresAt := time.Now().AddDate(1, 0, 0).Unix()
|
2022-11-12 18:24:37 +00:00
|
|
|
fingerPrintMap := &SessionData{
|
|
|
|
Nonce: nonce,
|
|
|
|
Roles: roles,
|
|
|
|
Subject: user.ID,
|
|
|
|
Scope: scope,
|
|
|
|
LoginMethod: loginMethod,
|
|
|
|
IssuedAt: time.Now().Unix(),
|
2023-04-08 07:36:15 +00:00
|
|
|
ExpiresAt: expiresAt,
|
2022-11-12 18:24:37 +00:00
|
|
|
}
|
|
|
|
fingerPrintBytes, _ := json.Marshal(fingerPrintMap)
|
|
|
|
fingerPrintHash, err := crypto.EncryptAES(string(fingerPrintBytes))
|
|
|
|
if err != nil {
|
2023-04-08 07:36:15 +00:00
|
|
|
return nil, "", 0, err
|
2022-11-12 18:24:37 +00:00
|
|
|
}
|
|
|
|
|
2023-04-08 07:36:15 +00:00
|
|
|
return fingerPrintMap, fingerPrintHash, expiresAt, nil
|
2022-11-12 18:24:37 +00:00
|
|
|
}
|
|
|
|
|
2022-01-22 19:54:41 +00:00
|
|
|
// CreateRefreshToken util to create JWT token
|
2023-07-31 11:12:11 +00:00
|
|
|
func CreateRefreshToken(user *models.User, roles, scopes []string, hostname, nonce, loginMethod string) (string, int64, error) {
|
2022-01-22 19:54:41 +00:00
|
|
|
// expires in 1 year
|
|
|
|
expiryBound := time.Hour * 8760
|
|
|
|
expiresAt := time.Now().Add(expiryBound).Unix()
|
2022-05-30 05:30:00 +00:00
|
|
|
clientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID)
|
|
|
|
if err != nil {
|
|
|
|
return "", 0, err
|
|
|
|
}
|
2022-01-22 19:54:41 +00:00
|
|
|
customClaims := jwt.MapClaims{
|
2022-11-07 01:41:23 +00:00
|
|
|
"iss": hostname,
|
|
|
|
"aud": clientID,
|
|
|
|
"sub": user.ID,
|
|
|
|
"exp": expiresAt,
|
|
|
|
"iat": time.Now().Unix(),
|
|
|
|
"token_type": constants.TokenTypeRefreshToken,
|
|
|
|
"roles": roles,
|
2023-02-08 04:09:08 +00:00
|
|
|
"scope": scopes,
|
2022-11-07 01:41:23 +00:00
|
|
|
"nonce": nonce,
|
|
|
|
"login_method": loginMethod,
|
|
|
|
"allowed_roles": strings.Split(user.Roles, ","),
|
2022-01-22 19:54:41 +00:00
|
|
|
}
|
|
|
|
|
2022-02-12 10:24:23 +00:00
|
|
|
token, err := SignJWTToken(customClaims)
|
2022-01-22 19:54:41 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", 0, err
|
|
|
|
}
|
2022-03-02 12:12:31 +00:00
|
|
|
|
2022-01-22 19:54:41 +00:00
|
|
|
return token, expiresAt, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateAccessToken util to create JWT token, based on
|
|
|
|
// user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT
|
2023-07-31 11:12:11 +00:00
|
|
|
func CreateAccessToken(user *models.User, roles, scopes []string, hostName, nonce, loginMethod string) (string, int64, error) {
|
2022-05-30 05:30:00 +00:00
|
|
|
expireTime, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAccessTokenExpiryTime)
|
|
|
|
if err != nil {
|
|
|
|
return "", 0, err
|
|
|
|
}
|
|
|
|
expiryBound, err := utils.ParseDurationInSeconds(expireTime)
|
2022-03-25 12:21:20 +00:00
|
|
|
if err != nil {
|
2022-03-25 14:59:00 +00:00
|
|
|
expiryBound = time.Minute * 30
|
2022-03-25 12:21:20 +00:00
|
|
|
}
|
2022-03-02 12:12:31 +00:00
|
|
|
expiresAt := time.Now().Add(expiryBound).Unix()
|
2022-05-30 05:30:00 +00:00
|
|
|
clientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID)
|
|
|
|
if err != nil {
|
|
|
|
return "", 0, err
|
|
|
|
}
|
2022-03-02 12:12:31 +00:00
|
|
|
customClaims := jwt.MapClaims{
|
2022-11-07 01:41:23 +00:00
|
|
|
"iss": hostName,
|
|
|
|
"aud": clientID,
|
|
|
|
"nonce": nonce,
|
|
|
|
"sub": user.ID,
|
|
|
|
"exp": expiresAt,
|
|
|
|
"iat": time.Now().Unix(),
|
|
|
|
"token_type": constants.TokenTypeAccessToken,
|
2023-02-08 04:09:08 +00:00
|
|
|
"scope": scopes,
|
2022-11-07 01:41:23 +00:00
|
|
|
"roles": roles,
|
|
|
|
"login_method": loginMethod,
|
|
|
|
"allowed_roles": strings.Split(user.Roles, ","),
|
2022-03-02 12:12:31 +00:00
|
|
|
}
|
2023-02-08 04:09:08 +00:00
|
|
|
// check for the extra access token script
|
|
|
|
accessTokenScript, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyCustomAccessTokenScript)
|
|
|
|
if err != nil {
|
|
|
|
log.Debug("Failed to get custom access token script: ", err)
|
|
|
|
accessTokenScript = ""
|
|
|
|
}
|
|
|
|
if accessTokenScript != "" {
|
|
|
|
resUser := user.AsAPIUser()
|
|
|
|
userBytes, _ := json.Marshal(&resUser)
|
|
|
|
var userMap map[string]interface{}
|
|
|
|
json.Unmarshal(userBytes, &userMap)
|
|
|
|
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))
|
2022-03-02 12:12:31 +00:00
|
|
|
|
2023-02-08 04:09:08 +00:00
|
|
|
val, err := vm.Get("functionRes")
|
|
|
|
if err != nil {
|
|
|
|
log.Debug("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.Debug("error converting accessTokenScript response to map: ", err)
|
|
|
|
} else {
|
|
|
|
for k, v := range extraPayload {
|
|
|
|
customClaims[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-03-02 12:12:31 +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) {
|
|
|
|
// try to check in auth header for cookie
|
|
|
|
auth := gc.Request.Header.Get("Authorization")
|
|
|
|
if auth == "" {
|
|
|
|
return "", fmt.Errorf(`unauthorized`)
|
|
|
|
}
|
|
|
|
|
2022-03-24 08:01:56 +00:00
|
|
|
authSplit := strings.Split(auth, " ")
|
|
|
|
if len(authSplit) != 2 {
|
|
|
|
return "", fmt.Errorf(`unauthorized`)
|
|
|
|
}
|
|
|
|
|
|
|
|
if strings.ToLower(authSplit[0]) != "bearer" {
|
2022-03-02 12:12:31 +00:00
|
|
|
return "", fmt.Errorf(`not a bearer token`)
|
|
|
|
}
|
|
|
|
|
|
|
|
token := strings.TrimPrefix(auth, "Bearer ")
|
|
|
|
return token, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Function to validate access token for authorizer apis (profile, update_profile)
|
|
|
|
func ValidateAccessToken(gc *gin.Context, accessToken string) (map[string]interface{}, error) {
|
2022-05-31 07:41:54 +00:00
|
|
|
res := make(map[string]interface{})
|
2022-03-02 12:12:31 +00:00
|
|
|
|
|
|
|
if accessToken == "" {
|
|
|
|
return res, fmt.Errorf(`unauthorized`)
|
|
|
|
}
|
|
|
|
|
2022-06-11 13:40:39 +00:00
|
|
|
res, err := ParseJWTToken(accessToken)
|
|
|
|
if err != nil {
|
|
|
|
return res, err
|
2022-03-02 12:12:31 +00:00
|
|
|
}
|
|
|
|
|
2022-06-11 13:40:39 +00:00
|
|
|
userID := res["sub"].(string)
|
2022-06-11 18:57:21 +00:00
|
|
|
nonce := res["nonce"].(string)
|
2022-06-29 16:54:00 +00:00
|
|
|
loginMethod := res["login_method"]
|
|
|
|
sessionKey := userID
|
|
|
|
if loginMethod != nil && loginMethod != "" {
|
|
|
|
sessionKey = loginMethod.(string) + ":" + userID
|
|
|
|
}
|
|
|
|
|
|
|
|
token, err := memorystore.Provider.GetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+nonce)
|
2022-06-11 13:40:39 +00:00
|
|
|
if nonce == "" || err != nil {
|
|
|
|
return res, fmt.Errorf(`unauthorized`)
|
|
|
|
}
|
2022-03-02 12:12:31 +00:00
|
|
|
|
2022-06-11 18:57:21 +00:00
|
|
|
if token != accessToken {
|
|
|
|
return res, fmt.Errorf(`unauthorized`)
|
|
|
|
}
|
|
|
|
|
2022-05-30 06:24:16 +00:00
|
|
|
hostname := parsers.GetHost(gc)
|
2022-06-11 13:40:39 +00:00
|
|
|
if ok, err := ValidateJWTClaims(res, hostname, nonce, userID); !ok || err != nil {
|
2022-03-02 12:12:31 +00:00
|
|
|
return res, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if res["token_type"] != constants.TokenTypeAccessToken {
|
|
|
|
return res, fmt.Errorf(`unauthorized: invalid token type`)
|
|
|
|
}
|
|
|
|
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
2022-03-08 09:26:46 +00:00
|
|
|
// Function to validate refreshToken
|
|
|
|
func ValidateRefreshToken(gc *gin.Context, refreshToken string) (map[string]interface{}, error) {
|
2022-05-31 07:41:54 +00:00
|
|
|
res := make(map[string]interface{})
|
2022-03-08 09:26:46 +00:00
|
|
|
|
|
|
|
if refreshToken == "" {
|
|
|
|
return res, fmt.Errorf(`unauthorized`)
|
|
|
|
}
|
|
|
|
|
2022-06-11 13:40:39 +00:00
|
|
|
res, err := ParseJWTToken(refreshToken)
|
|
|
|
if err != nil {
|
|
|
|
return res, err
|
2022-03-08 09:26:46 +00:00
|
|
|
}
|
|
|
|
|
2022-06-11 13:40:39 +00:00
|
|
|
userID := res["sub"].(string)
|
2022-06-11 18:57:21 +00:00
|
|
|
nonce := res["nonce"].(string)
|
2022-06-29 16:54:00 +00:00
|
|
|
loginMethod := res["login_method"]
|
|
|
|
sessionKey := userID
|
|
|
|
if loginMethod != nil && loginMethod != "" {
|
|
|
|
sessionKey = loginMethod.(string) + ":" + userID
|
|
|
|
}
|
|
|
|
token, err := memorystore.Provider.GetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+nonce)
|
2022-06-11 13:40:39 +00:00
|
|
|
if nonce == "" || err != nil {
|
|
|
|
return res, fmt.Errorf(`unauthorized`)
|
|
|
|
}
|
2022-03-08 09:26:46 +00:00
|
|
|
|
2022-06-11 18:57:21 +00:00
|
|
|
if token != refreshToken {
|
|
|
|
return res, fmt.Errorf(`unauthorized`)
|
|
|
|
}
|
|
|
|
|
2022-05-30 06:24:16 +00:00
|
|
|
hostname := parsers.GetHost(gc)
|
2022-06-11 13:40:39 +00:00
|
|
|
if ok, err := ValidateJWTClaims(res, hostname, nonce, userID); !ok || err != nil {
|
2022-03-08 09:26:46 +00:00
|
|
|
return res, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if res["token_type"] != constants.TokenTypeRefreshToken {
|
|
|
|
return res, fmt.Errorf(`unauthorized: invalid token type`)
|
|
|
|
}
|
|
|
|
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
2022-03-02 12:12:31 +00:00
|
|
|
func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionData, error) {
|
|
|
|
if encryptedSession == "" {
|
|
|
|
return nil, fmt.Errorf(`unauthorized`)
|
|
|
|
}
|
|
|
|
|
|
|
|
decryptedFingerPrint, err := crypto.DecryptAES(encryptedSession)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var res SessionData
|
|
|
|
err = json.Unmarshal([]byte(decryptedFingerPrint), &res)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-06-29 04:24:12 +00:00
|
|
|
sessionStoreKey := res.Subject
|
2022-06-29 16:54:00 +00:00
|
|
|
if res.LoginMethod != "" {
|
|
|
|
sessionStoreKey = res.LoginMethod + ":" + res.Subject
|
2022-06-29 04:24:12 +00:00
|
|
|
}
|
|
|
|
token, err := memorystore.Provider.GetUserSession(sessionStoreKey, constants.TokenTypeSessionToken+"_"+res.Nonce)
|
2022-06-11 18:57:21 +00:00
|
|
|
if token == "" || err != nil {
|
2023-12-26 15:38:12 +00:00
|
|
|
log.Debugf("invalid browser session: %v, key: %s", err, sessionStoreKey+":"+constants.TokenTypeSessionToken+"_"+res.Nonce)
|
2022-06-11 13:40:39 +00:00
|
|
|
return nil, fmt.Errorf(`unauthorized`)
|
2022-03-02 12:12:31 +00:00
|
|
|
}
|
|
|
|
|
2022-06-11 18:57:21 +00:00
|
|
|
if encryptedSession != token {
|
2022-06-11 13:40:39 +00:00
|
|
|
return nil, fmt.Errorf(`unauthorized: invalid nonce`)
|
2022-03-02 12:12:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if res.ExpiresAt < time.Now().Unix() {
|
|
|
|
return nil, fmt.Errorf(`unauthorized: token expired`)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &res, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateIDToken util to create JWT token, based on
|
|
|
|
// user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT
|
2022-10-23 15:38:08 +00:00
|
|
|
// For response_type (code) / authorization_code grant nonce should be empty
|
|
|
|
// for implicit flow it should be present to verify with actual state
|
2023-07-31 11:12:11 +00:00
|
|
|
func CreateIDToken(user *models.User, roles []string, hostname, nonce, atHash, cHash, loginMethod string) (string, int64, error) {
|
2022-05-30 05:30:00 +00:00
|
|
|
expireTime, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAccessTokenExpiryTime)
|
|
|
|
if err != nil {
|
|
|
|
return "", 0, err
|
|
|
|
}
|
|
|
|
expiryBound, err := utils.ParseDurationInSeconds(expireTime)
|
2022-03-25 12:21:20 +00:00
|
|
|
if err != nil {
|
2022-03-25 14:59:00 +00:00
|
|
|
expiryBound = time.Minute * 30
|
2022-03-25 12:21:20 +00:00
|
|
|
}
|
2022-01-22 19:54:41 +00:00
|
|
|
expiresAt := time.Now().Add(expiryBound).Unix()
|
|
|
|
resUser := user.AsAPIUser()
|
|
|
|
userBytes, _ := json.Marshal(&resUser)
|
|
|
|
var userMap map[string]interface{}
|
|
|
|
json.Unmarshal(userBytes, &userMap)
|
2022-05-30 05:30:00 +00:00
|
|
|
claimKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim)
|
|
|
|
if err != nil {
|
|
|
|
claimKey = "roles"
|
|
|
|
}
|
|
|
|
|
|
|
|
clientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID)
|
|
|
|
if err != nil {
|
|
|
|
return "", 0, err
|
|
|
|
}
|
2022-11-12 18:24:37 +00:00
|
|
|
|
2022-01-22 19:54:41 +00:00
|
|
|
customClaims := jwt.MapClaims{
|
2022-11-12 18:24:37 +00:00
|
|
|
"iss": hostname,
|
|
|
|
"aud": clientID,
|
2022-02-22 05:36:47 +00:00
|
|
|
"sub": user.ID,
|
2022-01-22 19:54:41 +00:00
|
|
|
"exp": expiresAt,
|
|
|
|
"iat": time.Now().Unix(),
|
2022-03-02 12:12:31 +00:00
|
|
|
"token_type": constants.TokenTypeIdentityToken,
|
2022-01-22 19:54:41 +00:00
|
|
|
"allowed_roles": strings.Split(user.Roles, ","),
|
2022-06-29 16:54:00 +00:00
|
|
|
"login_method": loginMethod,
|
2022-01-22 19:54:41 +00:00
|
|
|
claimKey: roles,
|
|
|
|
}
|
2022-11-12 18:24:37 +00:00
|
|
|
// split nonce to see if its authorization code grant method
|
|
|
|
if cHash != "" {
|
|
|
|
customClaims["at_hash"] = atHash
|
|
|
|
customClaims["c_hash"] = cHash
|
|
|
|
} else {
|
|
|
|
customClaims["nonce"] = nonce
|
|
|
|
customClaims["at_hash"] = atHash
|
|
|
|
}
|
2022-01-22 19:54:41 +00:00
|
|
|
for k, v := range userMap {
|
|
|
|
if k != "roles" {
|
|
|
|
customClaims[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// check for the extra access token script
|
2022-05-30 05:30:00 +00:00
|
|
|
accessTokenScript, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyCustomAccessTokenScript)
|
|
|
|
if err != nil {
|
|
|
|
log.Debug("Failed to get custom access token script: ", err)
|
|
|
|
accessTokenScript = ""
|
|
|
|
}
|
2022-01-22 19:54:41 +00:00
|
|
|
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 {
|
2022-05-25 07:00:22 +00:00
|
|
|
log.Debug("error getting custom access token script: ", err)
|
2022-01-22 19:54:41 +00:00
|
|
|
} else {
|
|
|
|
extraPayload := make(map[string]interface{})
|
|
|
|
err = json.Unmarshal([]byte(fmt.Sprintf("%s", val)), &extraPayload)
|
|
|
|
if err != nil {
|
2022-05-25 07:00:22 +00:00
|
|
|
log.Debug("error converting accessTokenScript response to map: ", err)
|
2022-01-22 19:54:41 +00:00
|
|
|
} else {
|
|
|
|
for k, v := range extraPayload {
|
|
|
|
customClaims[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-12 10:24:23 +00:00
|
|
|
token, err := SignJWTToken(customClaims)
|
2022-01-22 19:54:41 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return token, expiresAt, nil
|
|
|
|
}
|
|
|
|
|
2022-03-02 12:12:31 +00:00
|
|
|
// GetIDToken returns the id token from the request header
|
|
|
|
func GetIDToken(gc *gin.Context) (string, error) {
|
|
|
|
// try to check in auth header for cookie
|
|
|
|
auth := gc.Request.Header.Get("Authorization")
|
|
|
|
if auth == "" {
|
2022-01-22 19:54:41 +00:00
|
|
|
return "", fmt.Errorf(`unauthorized`)
|
|
|
|
}
|
|
|
|
|
2022-03-24 08:01:56 +00:00
|
|
|
authSplit := strings.Split(auth, " ")
|
|
|
|
if len(authSplit) != 2 {
|
|
|
|
return "", fmt.Errorf(`unauthorized`)
|
|
|
|
}
|
|
|
|
|
|
|
|
if strings.ToLower(authSplit[0]) != "bearer" {
|
2022-03-02 12:12:31 +00:00
|
|
|
return "", fmt.Errorf(`not a bearer token`)
|
2022-01-22 19:54:41 +00:00
|
|
|
}
|
|
|
|
|
2022-03-02 12:12:31 +00:00
|
|
|
token := strings.TrimPrefix(auth, "Bearer ")
|
|
|
|
return token, nil
|
2022-01-22 19:54:41 +00:00
|
|
|
}
|
2023-10-13 02:41:55 +00:00
|
|
|
|
2023-12-14 16:42:03 +00:00
|
|
|
// SessionOrAccessTokenData is a struct to hold session or access token data
|
|
|
|
type SessionOrAccessTokenData struct {
|
|
|
|
UserID string
|
|
|
|
LoginMethod string
|
|
|
|
Nonce string
|
|
|
|
}
|
|
|
|
|
2023-10-13 02:41:55 +00:00
|
|
|
// GetUserIDFromSessionOrAccessToken returns the user id from the session or access token
|
2023-12-14 16:42:03 +00:00
|
|
|
func GetUserIDFromSessionOrAccessToken(gc *gin.Context) (*SessionOrAccessTokenData, error) {
|
2023-10-13 02:41:55 +00:00
|
|
|
// First try to get the user id from the session
|
|
|
|
isSession := true
|
|
|
|
token, err := cookie.GetSession(gc)
|
|
|
|
if err != nil || token == "" {
|
|
|
|
log.Debug("Failed to get session token: ", err)
|
|
|
|
isSession = false
|
|
|
|
token, err = GetAccessToken(gc)
|
|
|
|
if err != nil || token == "" {
|
|
|
|
log.Debug("Failed to get access token: ", err)
|
2023-12-14 16:42:03 +00:00
|
|
|
return nil, fmt.Errorf(`unauthorized`)
|
2023-10-13 02:41:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if isSession {
|
|
|
|
claims, err := ValidateBrowserSession(gc, token)
|
|
|
|
if err != nil {
|
|
|
|
log.Debug("Failed to validate session token: ", err)
|
2023-12-14 16:42:03 +00:00
|
|
|
return nil, fmt.Errorf(`unauthorized`)
|
2023-10-13 02:41:55 +00:00
|
|
|
}
|
2023-12-14 16:42:03 +00:00
|
|
|
return &SessionOrAccessTokenData{
|
|
|
|
UserID: claims.Subject,
|
|
|
|
LoginMethod: claims.LoginMethod,
|
|
|
|
Nonce: claims.Nonce,
|
|
|
|
}, nil
|
2023-10-13 02:41:55 +00:00
|
|
|
}
|
|
|
|
// If not session, then validate the access token
|
|
|
|
claims, err := ValidateAccessToken(gc, token)
|
|
|
|
if err != nil {
|
|
|
|
log.Debug("Failed to validate access token: ", err)
|
2023-12-14 16:42:03 +00:00
|
|
|
return nil, fmt.Errorf(`unauthorized`)
|
2023-10-13 02:41:55 +00:00
|
|
|
}
|
2023-12-14 16:42:03 +00:00
|
|
|
return &SessionOrAccessTokenData{
|
|
|
|
UserID: claims["sub"].(string),
|
|
|
|
LoginMethod: claims["login_method"].(string),
|
|
|
|
Nonce: claims["nonce"].(string),
|
|
|
|
}, nil
|
2023-10-13 02:41:55 +00:00
|
|
|
}
|