fix: auth flow

This commit is contained in:
Lakhan Samani 2022-03-02 17:42:31 +05:30
parent 5399ea8f32
commit f0f2e0b6c8
47 changed files with 786 additions and 972 deletions

View File

@ -5,4 +5,6 @@ const (
TokenTypeRefreshToken = "refresh_token" TokenTypeRefreshToken = "refresh_token"
// TokenTypeAccessToken is the access_token token type // TokenTypeAccessToken is the access_token token type
TokenTypeAccessToken = "access_token" TokenTypeAccessToken = "access_token"
// TokenTypeIdentityToken is the identity_token token type
TokenTypeIdentityToken = "id_token"
) )

View File

@ -2,7 +2,6 @@ package cookie
import ( import (
"net/http" "net/http"
"net/url"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
@ -10,8 +9,8 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// SetSessionCookie sets the session cookie in the response // SetSession sets the session cookie in the response
func SetSessionCookie(gc *gin.Context, sessionID string) { func SetSession(gc *gin.Context, sessionID string) {
secure := true secure := true
httpOnly := true httpOnly := true
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
@ -26,89 +25,16 @@ func SetSessionCookie(gc *gin.Context, sessionID string) {
gc.SetSameSite(http.SameSiteNoneMode) gc.SetSameSite(http.SameSiteNoneMode)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", sessionID, year, "/", host, secure, httpOnly) gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", sessionID, year, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session.domain", sessionID, year, "/", domain, secure, httpOnly) gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_domain", sessionID, year, "/", domain, secure, httpOnly)
// Fallback cookie for anomaly getection on browsers that dont support the sameSite=None attribute. // Fallback cookie for anomaly getection on browsers that dont support the sameSite=None attribute.
gc.SetSameSite(http.SameSiteDefaultMode) gc.SetSameSite(http.SameSiteDefaultMode)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_compat", sessionID, year, "/", host, secure, httpOnly) gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_compat", sessionID, year, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session.domain_compat", sessionID, year, "/", domain, secure, httpOnly) gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_domain_compat", sessionID, year, "/", domain, secure, httpOnly)
} }
// SetCookie sets the cookie in the response. It sets 4 cookies // DeleteSession sets session cookies to expire
// 1 COOKIE_NAME.access_token jwt token for the host (temp.abc.com) func DeleteSession(gc *gin.Context) {
// 2 COOKIE_NAME.access_token.domain jwt token for the domain (abc.com).
// 3 COOKIE_NAME.fingerprint fingerprint hash for the refresh token verification.
// 4 COOKIE_NAME.refresh_token refresh token
// Note all sites don't allow 2nd type of cookie
func SetCookie(gc *gin.Context, accessToken, refreshToken, fingerprintHash string) {
secure := true
httpOnly := true
hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname)
domain := utils.GetDomainName(hostname)
if domain != "localhost" {
domain = "." + domain
}
year := 60 * 60 * 24 * 365
thirtyMin := 60 * 30
gc.SetSameSite(http.SameSiteNoneMode)
// set cookie for host
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", accessToken, thirtyMin, "/", host, secure, httpOnly)
// in case of subdomain, set cookie for domain
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token.domain", accessToken, thirtyMin, "/", domain, secure, httpOnly)
// set finger print cookie (this should be accessed via cookie only)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", fingerprintHash, year, "/", host, secure, httpOnly)
// set refresh token cookie (this should be accessed via cookie only)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, year, "/", host, secure, httpOnly)
}
// GetAccessTokenCookie to get access token cookie from the request
func GetAccessTokenCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".access_token")
if err != nil {
cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".access_token.domain")
if err != nil {
return "", err
}
}
return cookie.Value, nil
}
// GetRefreshTokenCookie to get refresh token cookie
func GetRefreshTokenCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".refresh_token")
if err != nil {
return "", err
}
return cookie.Value, nil
}
// GetFingerPrintCookie to get fingerprint cookie
func GetFingerPrintCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".fingerprint")
if err != nil {
return "", err
}
// cookie escapes special characters like $
// hence we need to unescape before comparing
decodedValue, err := url.QueryUnescape(cookie.Value)
if err != nil {
return "", err
}
return decodedValue, nil
}
// DeleteCookie sets response cookies to expire
func DeleteCookie(gc *gin.Context) {
secure := true secure := true
httpOnly := true httpOnly := true
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
@ -119,8 +45,32 @@ func DeleteCookie(gc *gin.Context) {
} }
gc.SetSameSite(http.SameSiteNoneMode) gc.SetSameSite(http.SameSiteNoneMode)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", "", -1, "/", host, secure, httpOnly) gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", "", -1, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token.domain", "", -1, "/", domain, secure, httpOnly) gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_domain", "", -1, "/", domain, secure, httpOnly)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", "", -1, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", "", -1, "/", host, secure, httpOnly) gc.SetSameSite(http.SameSiteDefaultMode)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_compat", "", -1, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session.domain_compat", "", -1, "/", domain, secure, httpOnly)
}
// GetSession gets the session cookie from context
func GetSession(gc *gin.Context) (string, error) {
var cookie *http.Cookie
var err error
cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + "_session")
if err != nil {
cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + "_session_domain")
if err != nil {
cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + "_session_compat")
if err != nil {
cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + "_session_domain_compat")
}
if err != nil {
return "", err
}
}
}
return cookie.Value, nil
} }

View File

@ -3,71 +3,40 @@ package crypto
import ( import (
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/rand"
"io"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
) )
// EncryptAES encrypts data using AES algorithm var bytes = []byte{35, 46, 57, 24, 85, 35, 24, 74, 87, 35, 88, 98, 66, 32, 14, 05}
func EncryptAES(text []byte) ([]byte, error) {
// EncryptAES method is to encrypt or hide any classified text
func EncryptAES(text string) (string, error) {
key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)) key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
c, err := aes.NewCipher(key) block, err := aes.NewCipher(key)
var res []byte
if err != nil { if err != nil {
return res, err return "", err
} }
plainText := []byte(text)
// gcm or Galois/Counter Mode, is a mode of operation cfb := cipher.NewCFBEncrypter(block, bytes)
// for symmetric key cryptographic block ciphers cipherText := make([]byte, len(plainText))
// - https://en.wikipedia.org/wiki/Galois/Counter_Mode cfb.XORKeyStream(cipherText, plainText)
gcm, err := cipher.NewGCM(c) return EncryptB64(string(cipherText)), nil
if err != nil {
return res, err
}
// creates a new byte array the size of the nonce
// which must be passed to Seal
nonce := make([]byte, gcm.NonceSize())
// populates our nonce with a cryptographically secure
// random sequence
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return res, err
}
// here we encrypt our text using the Seal function
// Seal encrypts and authenticates plaintext, authenticates the
// additional data and appends the result to dst, returning the updated
// slice. The nonce must be NonceSize() bytes long and unique for all
// time, for a given key.
return gcm.Seal(nonce, nonce, text, nil), nil
} }
// DecryptAES decrypts data using AES algorithm // DecryptAES method is to extract back the encrypted text
func DecryptAES(ciphertext []byte) ([]byte, error) { func DecryptAES(text string) (string, error) {
key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)) key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
c, err := aes.NewCipher(key) block, err := aes.NewCipher(key)
var res []byte
if err != nil { if err != nil {
return res, err return "", err
} }
cipherText, err := DecryptB64(text)
gcm, err := cipher.NewGCM(c)
if err != nil { if err != nil {
return res, err return "", err
} }
cfb := cipher.NewCFBDecrypter(block, bytes)
nonceSize := gcm.NonceSize() plainText := make([]byte, len(cipherText))
if len(ciphertext) < nonceSize { cfb.XORKeyStream(plainText, []byte(cipherText))
return res, err return string(plainText), nil
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return res, err
}
return plaintext, nil
} }

View File

@ -94,7 +94,7 @@ func EncryptEnvData(data envstore.Store) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
encryptedConfig, err := EncryptAES(configData) encryptedConfig, err := EncryptAES(string(configData))
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -12,6 +12,7 @@ type VerificationRequest struct {
CreatedAt int64 `json:"created_at" bson:"created_at"` CreatedAt int64 `json:"created_at" bson:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at"` UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email" bson:"email"` Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email" bson:"email"`
Nonce string `gorm:"type:char(36)" json:"nonce" bson:"nonce"`
} }
func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest { func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest {

View File

@ -56,7 +56,10 @@ func NewProvider() (*provider, error) {
return nil, err return nil, err
} }
sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{}) err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{})
if err != nil {
return nil, err
}
return &provider{ return &provider{
db: sqlDB, db: sqlDB,
}, nil }, nil

View File

@ -31,6 +31,10 @@ func addEmailTemplate(a string, b map[string]interface{}, templateName string) s
// SendMail function to send mail // SendMail function to send mail
func SendMail(to []string, Subject, bodyMessage string) error { func SendMail(to []string, Subject, bodyMessage string) error {
// dont trigger email sending in case of test
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnv) == "test" {
return nil
}
m := gomail.NewMessage() m := gomail.NewMessage()
m.SetHeader("From", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySenderEmail)) m.SetHeader("From", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySenderEmail))
m.SetHeader("To", to...) m.SetHeader("To", to...)

View File

@ -33,17 +33,13 @@ func GetEnvData() (envstore.Store, error) {
} }
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey) envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData)
decryptedConfigs, err := crypto.DecryptAES(env.EnvData)
if err != nil { if err != nil {
return result, err return result, err
} }
decryptedConfigs, err := crypto.DecryptAES([]byte(b64DecryptedConfig)) err = json.Unmarshal([]byte(decryptedConfigs), &result)
if err != nil {
return result, err
}
err = json.Unmarshal(decryptedConfigs, &result)
if err != nil { if err != nil {
return result, err return result, err
} }
@ -85,12 +81,8 @@ func PersistEnv() error {
} }
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey) envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData)
if err != nil {
return err
}
decryptedConfigs, err := crypto.DecryptAES([]byte(b64DecryptedConfig)) decryptedConfigs, err := crypto.DecryptAES(env.EnvData)
if err != nil { if err != nil {
return err return err
} }
@ -98,7 +90,7 @@ func PersistEnv() error {
// temp store variable // temp store variable
var storeData envstore.Store var storeData envstore.Store
err = json.Unmarshal(decryptedConfigs, &storeData) err = json.Unmarshal([]byte(decryptedConfigs), &storeData)
if err != nil { if err != nil {
return err return err
} }

View File

@ -45,8 +45,10 @@ type DirectiveRoot struct {
type ComplexityRoot struct { type ComplexityRoot struct {
AuthResponse struct { AuthResponse struct {
AccessToken func(childComplexity int) int AccessToken func(childComplexity int) int
ExpiresAt func(childComplexity int) int ExpiresIn func(childComplexity int) int
IDToken func(childComplexity int) int
Message func(childComplexity int) int Message func(childComplexity int) int
RefreshToken func(childComplexity int) int
User func(childComplexity int) int User func(childComplexity int) int
} }
@ -134,7 +136,6 @@ type ComplexityRoot struct {
Query struct { Query struct {
AdminSession func(childComplexity int) int AdminSession func(childComplexity int) int
Env func(childComplexity int) int Env func(childComplexity int) int
IsValidJwt func(childComplexity int, params *model.IsValidJWTQueryInput) int
Meta func(childComplexity int) int Meta func(childComplexity int) int
Profile func(childComplexity int) int Profile func(childComplexity int) int
Session func(childComplexity int, params *model.SessionQueryInput) int Session func(childComplexity int, params *model.SessionQueryInput) int
@ -171,11 +172,6 @@ type ComplexityRoot struct {
Users func(childComplexity int) int Users func(childComplexity int) int
} }
ValidJWTResponse struct {
Message func(childComplexity int) int
Valid func(childComplexity int) int
}
VerificationRequest struct { VerificationRequest struct {
CreatedAt func(childComplexity int) int CreatedAt func(childComplexity int) int
Email func(childComplexity int) int Email func(childComplexity int) int
@ -212,7 +208,6 @@ type MutationResolver interface {
type QueryResolver interface { type QueryResolver interface {
Meta(ctx context.Context) (*model.Meta, error) Meta(ctx context.Context) (*model.Meta, error)
Session(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error) Session(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error)
IsValidJwt(ctx context.Context, params *model.IsValidJWTQueryInput) (*model.ValidJWTResponse, error)
Profile(ctx context.Context) (*model.User, error) Profile(ctx context.Context) (*model.User, error)
Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error) Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error)
VerificationRequests(ctx context.Context, params *model.PaginatedInput) (*model.VerificationRequests, error) VerificationRequests(ctx context.Context, params *model.PaginatedInput) (*model.VerificationRequests, error)
@ -242,12 +237,19 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.AuthResponse.AccessToken(childComplexity), true return e.complexity.AuthResponse.AccessToken(childComplexity), true
case "AuthResponse.expires_at": case "AuthResponse.expires_in":
if e.complexity.AuthResponse.ExpiresAt == nil { if e.complexity.AuthResponse.ExpiresIn == nil {
break break
} }
return e.complexity.AuthResponse.ExpiresAt(childComplexity), true return e.complexity.AuthResponse.ExpiresIn(childComplexity), true
case "AuthResponse.id_token":
if e.complexity.AuthResponse.IDToken == nil {
break
}
return e.complexity.AuthResponse.IDToken(childComplexity), true
case "AuthResponse.message": case "AuthResponse.message":
if e.complexity.AuthResponse.Message == nil { if e.complexity.AuthResponse.Message == nil {
@ -256,6 +258,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.AuthResponse.Message(childComplexity), true return e.complexity.AuthResponse.Message(childComplexity), true
case "AuthResponse.refresh_token":
if e.complexity.AuthResponse.RefreshToken == nil {
break
}
return e.complexity.AuthResponse.RefreshToken(childComplexity), true
case "AuthResponse.user": case "AuthResponse.user":
if e.complexity.AuthResponse.User == nil { if e.complexity.AuthResponse.User == nil {
break break
@ -804,18 +813,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Query.Env(childComplexity), true return e.complexity.Query.Env(childComplexity), true
case "Query.is_valid_jwt":
if e.complexity.Query.IsValidJwt == nil {
break
}
args, err := ec.field_Query_is_valid_jwt_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Query.IsValidJwt(childComplexity, args["params"].(*model.IsValidJWTQueryInput)), true
case "Query.meta": case "Query.meta":
if e.complexity.Query.Meta == nil { if e.complexity.Query.Meta == nil {
break break
@ -1006,20 +1003,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Users.Users(childComplexity), true return e.complexity.Users.Users(childComplexity), true
case "ValidJWTResponse.message":
if e.complexity.ValidJWTResponse.Message == nil {
break
}
return e.complexity.ValidJWTResponse.Message(childComplexity), true
case "ValidJWTResponse.valid":
if e.complexity.ValidJWTResponse.Valid == nil {
break
}
return e.complexity.ValidJWTResponse.Valid(childComplexity), true
case "VerificationRequest.created_at": case "VerificationRequest.created_at":
if e.complexity.VerificationRequest.CreatedAt == nil { if e.complexity.VerificationRequest.CreatedAt == nil {
break break
@ -1221,7 +1204,9 @@ type Error {
type AuthResponse { type AuthResponse {
message: String! message: String!
access_token: String access_token: String
expires_at: Int64 id_token: String
refresh_token: String
expires_in: Int64
user: User user: User
} }
@ -1229,11 +1214,6 @@ type Response {
message: String! message: String!
} }
type ValidJWTResponse {
valid: Boolean!
message: String!
}
type Env { type Env {
ADMIN_SECRET: String ADMIN_SECRET: String
DATABASE_NAME: String! DATABASE_NAME: String!
@ -1337,6 +1317,7 @@ input LoginInput {
email: String! email: String!
password: String! password: String!
roles: [String!] roles: [String!]
scope: [String!]
} }
input VerifyEmailInput { input VerifyEmailInput {
@ -1395,15 +1376,12 @@ input DeleteUserInput {
input MagicLinkLoginInput { input MagicLinkLoginInput {
email: String! email: String!
roles: [String!] roles: [String!]
scope: [String!]
} }
input SessionQueryInput { input SessionQueryInput {
roles: [String!] roles: [String!]
} scope: [String!]
input IsValidJWTQueryInput {
jwt: String
roles: [String!]
} }
input PaginationInput { input PaginationInput {
@ -1437,7 +1415,6 @@ type Mutation {
type Query { type Query {
meta: Meta! meta: Meta!
session(params: SessionQueryInput): AuthResponse! session(params: SessionQueryInput): AuthResponse!
is_valid_jwt(params: IsValidJWTQueryInput): ValidJWTResponse!
profile: User! profile: User!
# admin only apis # admin only apis
_users(params: PaginatedInput): Users! _users(params: PaginatedInput): Users!
@ -1693,21 +1670,6 @@ func (ec *executionContext) field_Query__verification_requests_args(ctx context.
return args, nil return args, nil
} }
func (ec *executionContext) field_Query_is_valid_jwt_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 *model.IsValidJWTQueryInput
if tmp, ok := rawArgs["params"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params"))
arg0, err = ec.unmarshalOIsValidJWTQueryInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐIsValidJWTQueryInput(ctx, tmp)
if err != nil {
return nil, err
}
}
args["params"] = arg0
return args, nil
}
func (ec *executionContext) field_Query_session_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { func (ec *executionContext) field_Query_session_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error var err error
args := map[string]interface{}{} args := map[string]interface{}{}
@ -1828,7 +1790,7 @@ func (ec *executionContext) _AuthResponse_access_token(ctx context.Context, fiel
return ec.marshalOString2ᚖstring(ctx, field.Selections, res) return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
} }
func (ec *executionContext) _AuthResponse_expires_at(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) { func (ec *executionContext) _AuthResponse_id_token(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r)) ec.Error(ctx, ec.Recover(ctx, r))
@ -1846,7 +1808,71 @@ func (ec *executionContext) _AuthResponse_expires_at(ctx context.Context, field
ctx = graphql.WithFieldContext(ctx, fc) ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children ctx = rctx // use context from middleware stack in children
return obj.ExpiresAt, nil return obj.IDToken, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _AuthResponse_refresh_token(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "AuthResponse",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.RefreshToken, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _AuthResponse_expires_in(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "AuthResponse",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.ExpiresIn, nil
}) })
if err != nil { if err != nil {
ec.Error(ctx, err) ec.Error(ctx, err)
@ -4274,48 +4300,6 @@ func (ec *executionContext) _Query_session(ctx context.Context, field graphql.Co
return ec.marshalNAuthResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAuthResponse(ctx, field.Selections, res) return ec.marshalNAuthResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAuthResponse(ctx, field.Selections, res)
} }
func (ec *executionContext) _Query_is_valid_jwt(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Query",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Query_is_valid_jwt_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
fc.Args = args
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Query().IsValidJwt(rctx, args["params"].(*model.IsValidJWTQueryInput))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*model.ValidJWTResponse)
fc.Result = res
return ec.marshalNValidJWTResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidJWTResponse(ctx, field.Selections, res)
}
func (ec *executionContext) _Query_profile(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Query_profile(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -5240,76 +5224,6 @@ func (ec *executionContext) _Users_users(ctx context.Context, field graphql.Coll
return ec.marshalNUser2ᚕᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUserᚄ(ctx, field.Selections, res) return ec.marshalNUser2ᚕᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUserᚄ(ctx, field.Selections, res)
} }
func (ec *executionContext) _ValidJWTResponse_valid(ctx context.Context, field graphql.CollectedField, obj *model.ValidJWTResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "ValidJWTResponse",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Valid, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _ValidJWTResponse_message(ctx context.Context, field graphql.CollectedField, obj *model.ValidJWTResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "ValidJWTResponse",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Message, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(string)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _VerificationRequest_id(ctx context.Context, field graphql.CollectedField, obj *model.VerificationRequest) (ret graphql.Marshaler) { func (ec *executionContext) _VerificationRequest_id(ctx context.Context, field graphql.CollectedField, obj *model.VerificationRequest) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -6821,37 +6735,6 @@ func (ec *executionContext) unmarshalInputForgotPasswordInput(ctx context.Contex
return it, nil return it, nil
} }
func (ec *executionContext) unmarshalInputIsValidJWTQueryInput(ctx context.Context, obj interface{}) (model.IsValidJWTQueryInput, error) {
var it model.IsValidJWTQueryInput
asMap := map[string]interface{}{}
for k, v := range obj.(map[string]interface{}) {
asMap[k] = v
}
for k, v := range asMap {
switch k {
case "jwt":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("jwt"))
it.Jwt, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "roles":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("roles"))
it.Roles, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj interface{}) (model.LoginInput, error) { func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj interface{}) (model.LoginInput, error) {
var it model.LoginInput var it model.LoginInput
asMap := map[string]interface{}{} asMap := map[string]interface{}{}
@ -6885,6 +6768,14 @@ func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj in
if err != nil { if err != nil {
return it, err return it, err
} }
case "scope":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("scope"))
it.Scope, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v)
if err != nil {
return it, err
}
} }
} }
@ -6916,6 +6807,14 @@ func (ec *executionContext) unmarshalInputMagicLinkLoginInput(ctx context.Contex
if err != nil { if err != nil {
return it, err return it, err
} }
case "scope":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("scope"))
it.Scope, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v)
if err != nil {
return it, err
}
} }
} }
@ -7063,6 +6962,14 @@ func (ec *executionContext) unmarshalInputSessionQueryInput(ctx context.Context,
if err != nil { if err != nil {
return it, err return it, err
} }
case "scope":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("scope"))
it.Scope, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v)
if err != nil {
return it, err
}
} }
} }
@ -7730,8 +7637,12 @@ func (ec *executionContext) _AuthResponse(ctx context.Context, sel ast.Selection
} }
case "access_token": case "access_token":
out.Values[i] = ec._AuthResponse_access_token(ctx, field, obj) out.Values[i] = ec._AuthResponse_access_token(ctx, field, obj)
case "expires_at": case "id_token":
out.Values[i] = ec._AuthResponse_expires_at(ctx, field, obj) out.Values[i] = ec._AuthResponse_id_token(ctx, field, obj)
case "refresh_token":
out.Values[i] = ec._AuthResponse_refresh_token(ctx, field, obj)
case "expires_in":
out.Values[i] = ec._AuthResponse_expires_in(ctx, field, obj)
case "user": case "user":
out.Values[i] = ec._AuthResponse_user(ctx, field, obj) out.Values[i] = ec._AuthResponse_user(ctx, field, obj)
default: default:
@ -8136,20 +8047,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
} }
return res return res
}) })
case "is_valid_jwt":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Query_is_valid_jwt(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "profile": case "profile":
field := field field := field
out.Concurrently(i, func() (res graphql.Marshaler) { out.Concurrently(i, func() (res graphql.Marshaler) {
@ -8365,38 +8262,6 @@ func (ec *executionContext) _Users(ctx context.Context, sel ast.SelectionSet, ob
return out return out
} }
var validJWTResponseImplementors = []string{"ValidJWTResponse"}
func (ec *executionContext) _ValidJWTResponse(ctx context.Context, sel ast.SelectionSet, obj *model.ValidJWTResponse) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, validJWTResponseImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("ValidJWTResponse")
case "valid":
out.Values[i] = ec._ValidJWTResponse_valid(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "message":
out.Values[i] = ec._ValidJWTResponse_message(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var verificationRequestImplementors = []string{"VerificationRequest"} var verificationRequestImplementors = []string{"VerificationRequest"}
func (ec *executionContext) _VerificationRequest(ctx context.Context, sel ast.SelectionSet, obj *model.VerificationRequest) graphql.Marshaler { func (ec *executionContext) _VerificationRequest(ctx context.Context, sel ast.SelectionSet, obj *model.VerificationRequest) graphql.Marshaler {
@ -9012,20 +8877,6 @@ func (ec *executionContext) marshalNUsers2ᚖgithubᚗcomᚋauthorizerdevᚋauth
return ec._Users(ctx, sel, v) return ec._Users(ctx, sel, v)
} }
func (ec *executionContext) marshalNValidJWTResponse2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidJWTResponse(ctx context.Context, sel ast.SelectionSet, v model.ValidJWTResponse) graphql.Marshaler {
return ec._ValidJWTResponse(ctx, sel, &v)
}
func (ec *executionContext) marshalNValidJWTResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidJWTResponse(ctx context.Context, sel ast.SelectionSet, v *model.ValidJWTResponse) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._ValidJWTResponse(ctx, sel, v)
}
func (ec *executionContext) marshalNVerificationRequest2ᚕᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐVerificationRequestᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.VerificationRequest) graphql.Marshaler { func (ec *executionContext) marshalNVerificationRequest2ᚕᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐVerificationRequestᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.VerificationRequest) graphql.Marshaler {
ret := make(graphql.Array, len(v)) ret := make(graphql.Array, len(v))
var wg sync.WaitGroup var wg sync.WaitGroup
@ -9395,14 +9246,6 @@ func (ec *executionContext) marshalOInt642ᚖint64(ctx context.Context, sel ast.
return graphql.MarshalInt64(*v) return graphql.MarshalInt64(*v)
} }
func (ec *executionContext) unmarshalOIsValidJWTQueryInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐIsValidJWTQueryInput(ctx context.Context, v interface{}) (*model.IsValidJWTQueryInput, error) {
if v == nil {
return nil, nil
}
res, err := ec.unmarshalInputIsValidJWTQueryInput(ctx, v)
return &res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalOPaginatedInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐPaginatedInput(ctx context.Context, v interface{}) (*model.PaginatedInput, error) { func (ec *executionContext) unmarshalOPaginatedInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐPaginatedInput(ctx context.Context, v interface{}) (*model.PaginatedInput, error) {
if v == nil { if v == nil {
return nil, nil return nil, nil

View File

@ -13,7 +13,9 @@ type AdminSignupInput struct {
type AuthResponse struct { type AuthResponse struct {
Message string `json:"message"` Message string `json:"message"`
AccessToken *string `json:"access_token"` AccessToken *string `json:"access_token"`
ExpiresAt *int64 `json:"expires_at"` IDToken *string `json:"id_token"`
RefreshToken *string `json:"refresh_token"`
ExpiresIn *int64 `json:"expires_in"`
User *User `json:"user"` User *User `json:"user"`
} }
@ -70,20 +72,17 @@ type ForgotPasswordInput struct {
Email string `json:"email"` Email string `json:"email"`
} }
type IsValidJWTQueryInput struct {
Jwt *string `json:"jwt"`
Roles []string `json:"roles"`
}
type LoginInput struct { type LoginInput struct {
Email string `json:"email"` Email string `json:"email"`
Password string `json:"password"` Password string `json:"password"`
Roles []string `json:"roles"` Roles []string `json:"roles"`
Scope []string `json:"scope"`
} }
type MagicLinkLoginInput struct { type MagicLinkLoginInput struct {
Email string `json:"email"` Email string `json:"email"`
Roles []string `json:"roles"` Roles []string `json:"roles"`
Scope []string `json:"scope"`
} }
type Meta struct { type Meta struct {
@ -130,6 +129,7 @@ type Response struct {
type SessionQueryInput struct { type SessionQueryInput struct {
Roles []string `json:"roles"` Roles []string `json:"roles"`
Scope []string `json:"scope"`
} }
type SignUpInput struct { type SignUpInput struct {
@ -238,11 +238,6 @@ type Users struct {
Users []*User `json:"users"` Users []*User `json:"users"`
} }
type ValidJWTResponse struct {
Valid bool `json:"valid"`
Message string `json:"message"`
}
type VerificationRequest struct { type VerificationRequest struct {
ID string `json:"id"` ID string `json:"id"`
Identifier *string `json:"identifier"` Identifier *string `json:"identifier"`

View File

@ -72,7 +72,9 @@ type Error {
type AuthResponse { type AuthResponse {
message: String! message: String!
access_token: String access_token: String
expires_at: Int64 id_token: String
refresh_token: String
expires_in: Int64
user: User user: User
} }
@ -80,11 +82,6 @@ type Response {
message: String! message: String!
} }
type ValidJWTResponse {
valid: Boolean!
message: String!
}
type Env { type Env {
ADMIN_SECRET: String ADMIN_SECRET: String
DATABASE_NAME: String! DATABASE_NAME: String!
@ -188,6 +185,7 @@ input LoginInput {
email: String! email: String!
password: String! password: String!
roles: [String!] roles: [String!]
scope: [String!]
} }
input VerifyEmailInput { input VerifyEmailInput {
@ -246,15 +244,12 @@ input DeleteUserInput {
input MagicLinkLoginInput { input MagicLinkLoginInput {
email: String! email: String!
roles: [String!] roles: [String!]
scope: [String!]
} }
input SessionQueryInput { input SessionQueryInput {
roles: [String!] roles: [String!]
} scope: [String!]
input IsValidJWTQueryInput {
jwt: String
roles: [String!]
} }
input PaginationInput { input PaginationInput {
@ -288,7 +283,6 @@ type Mutation {
type Query { type Query {
meta: Meta! meta: Meta!
session(params: SessionQueryInput): AuthResponse! session(params: SessionQueryInput): AuthResponse!
is_valid_jwt(params: IsValidJWTQueryInput): ValidJWTResponse!
profile: User! profile: User!
# admin only apis # admin only apis
_users(params: PaginatedInput): Users! _users(params: PaginatedInput): Users!

View File

@ -79,10 +79,6 @@ func (r *queryResolver) Session(ctx context.Context, params *model.SessionQueryI
return resolvers.SessionResolver(ctx, params) return resolvers.SessionResolver(ctx, params)
} }
func (r *queryResolver) IsValidJwt(ctx context.Context, params *model.IsValidJWTQueryInput) (*model.ValidJWTResponse, error) {
return resolvers.IsValidJwtResolver(ctx, params)
}
func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) { func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) {
return resolvers.ProfileResolver(ctx) return resolvers.ProfileResolver(ctx)
} }

View File

@ -144,11 +144,13 @@ func OAuthCallbackHandler() gin.HandlerFunc {
} }
} }
authToken, _ := token.CreateAuthToken(user, inputRoles) // TODO use query param
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token) scope := []string{"openid", "email", "profile"}
cookie.SetCookie(c, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash) authToken, _ := token.CreateAuthToken(c, user, inputRoles, scope)
utils.SaveSessionInDB(user.ID, c)
sessionstore.SetState(authToken.FingerPrint, user.ID)
cookie.SetSession(c, authToken.FingerPrintHash)
go utils.SaveSessionInDB(c, user.ID)
c.Redirect(http.StatusTemporaryRedirect, redirectURL) c.Redirect(http.StatusTemporaryRedirect, redirectURL)
} }
} }
@ -227,7 +229,7 @@ func processGithubUserInfo(code string) (models.User, error) {
GivenName: &firstName, GivenName: &firstName,
FamilyName: &lastName, FamilyName: &lastName,
Picture: &picture, Picture: &picture,
Email: userRawData["email"], Email: userRawData["sub"],
} }
return user, nil return user, nil
@ -260,7 +262,7 @@ func processFacebookUserInfo(code string) (models.User, error) {
userRawData := make(map[string]interface{}) userRawData := make(map[string]interface{})
json.Unmarshal(body, &userRawData) json.Unmarshal(body, &userRawData)
email := fmt.Sprintf("%v", userRawData["email"]) email := fmt.Sprintf("%v", userRawData["sub"])
picObject := userRawData["picture"].(map[string]interface{})["data"] picObject := userRawData["picture"].(map[string]interface{})["data"]
picDataObject := picObject.(map[string]interface{}) picDataObject := picObject.(map[string]interface{})

View File

@ -11,6 +11,7 @@ import (
"github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid"
) )
// VerifyEmailHandler handles the verify email route. // VerifyEmailHandler handles the verify email route.
@ -18,7 +19,7 @@ import (
func VerifyEmailHandler() gin.HandlerFunc { func VerifyEmailHandler() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
errorRes := gin.H{ errorRes := gin.H{
"message": "invalid token", "error": "invalid token",
} }
tokenInQuery := c.Query("token") tokenInQuery := c.Query("token")
if tokenInQuery == "" { if tokenInQuery == "" {
@ -33,13 +34,21 @@ func VerifyEmailHandler() gin.HandlerFunc {
} }
// verify if token exists in db // verify if token exists in db
claim, err := token.ParseJWTToken(tokenInQuery) hostname := utils.GetHost(c)
encryptedNonce, err := utils.EncryptNonce(verificationRequest.Nonce)
if err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
})
return
}
claim, err := token.ParseJWTToken(tokenInQuery, hostname, encryptedNonce, verificationRequest.Email)
if err != nil { if err != nil {
c.JSON(400, errorRes) c.JSON(400, errorRes)
return return
} }
user, err := db.Provider.GetUserByEmail(claim["email"].(string)) user, err := db.Provider.GetUserByEmail(claim["sub"].(string))
if err != nil { if err != nil {
c.JSON(400, gin.H{ c.JSON(400, gin.H{
"message": err.Error(), "message": err.Error(),
@ -57,16 +66,19 @@ func VerifyEmailHandler() gin.HandlerFunc {
db.Provider.DeleteVerificationRequest(verificationRequest) db.Provider.DeleteVerificationRequest(verificationRequest)
roles := strings.Split(user.Roles, ",") roles := strings.Split(user.Roles, ",")
authToken, err := token.CreateAuthToken(user, roles) scope := []string{"openid", "email", "profile"}
nonce := uuid.New().String()
_, authToken, err := token.CreateSessionToken(user, nonce, roles, scope)
if err != nil { if err != nil {
c.JSON(400, gin.H{ c.JSON(400, gin.H{
"message": err.Error(), "message": err.Error(),
}) })
return return
} }
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token) sessionstore.SetState(authToken, nonce+"@"+user.ID)
cookie.SetCookie(c, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash) cookie.SetSession(c, authToken)
utils.SaveSessionInDB(user.ID, c)
go utils.SaveSessionInDB(c, user.ID)
c.Redirect(http.StatusTemporaryRedirect, claim["redirect_url"].(string)) c.Redirect(http.StatusTemporaryRedirect, claim["redirect_url"].(string))
} }

View File

@ -29,7 +29,7 @@ func DeleteUserResolver(ctx context.Context, params model.DeleteUserInput) (*mod
return res, err return res, err
} }
sessionstore.DeleteAllUserSession(fmt.Sprintf("%x", user.ID)) go sessionstore.DeleteAllUserSession(fmt.Sprintf("%x", user.ID))
err = db.Provider.DeleteUser(user) err = db.Provider.DeleteUser(user)
if err != nil { if err != nil {

View File

@ -39,7 +39,11 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
} }
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname) nonce, nonceHash, err := utils.GenerateNonce()
if err != nil {
return res, err
}
verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname, nonceHash)
if err != nil { if err != nil {
log.Println(`error generating token`, err) log.Println(`error generating token`, err)
} }
@ -48,12 +52,11 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
Identifier: constants.VerificationTypeForgotPassword, Identifier: constants.VerificationTypeForgotPassword,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: params.Email, Email: params.Email,
Nonce: nonce,
}) })
// exec it as go routin so that we can reduce the api latency // exec it as go routin so that we can reduce the api latency
go func() { go email.SendForgotPasswordMail(params.Email, verificationToken, hostname)
email.SendForgotPasswordMail(params.Email, verificationToken, hostname)
}()
res = &model.Response{ res = &model.Response{
Message: `Please check your inbox! We have sent a password reset link.`, Message: `Please check your inbox! We have sent a password reset link.`,

View File

@ -1,52 +0,0 @@
package resolvers
import (
"context"
"errors"
"fmt"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/token"
tokenHelper "github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
)
// IsValidJwtResolver resolver to return if given jwt is valid
func IsValidJwtResolver(ctx context.Context, params *model.IsValidJWTQueryInput) (*model.ValidJWTResponse, error) {
gc, err := utils.GinContextFromContext(ctx)
token, err := token.GetAccessToken(gc)
if token == "" || err != nil {
if params != nil && *params.Jwt != "" {
token = *params.Jwt
} else {
return nil, errors.New("no jwt provided via cookie / header / params")
}
}
claims, err := tokenHelper.ParseJWTToken(token)
if err != nil {
return nil, err
}
claimRoleInterface := claims[envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim)].([]interface{})
claimRoles := []string{}
for _, v := range claimRoleInterface {
claimRoles = append(claimRoles, v.(string))
}
if params != nil && params.Roles != nil && len(params.Roles) > 0 {
for _, v := range params.Roles {
if !utils.StringSliceContains(claimRoles, v) {
return nil, fmt.Errorf(`unauthorized`)
}
}
}
return &model.ValidJWTResponse{
Valid: true,
Message: "Valid JWT",
}, nil
}

View File

@ -59,20 +59,36 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
roles = params.Roles roles = params.Roles
} }
authToken, err := token.CreateAuthToken(user, roles) scope := []string{"openid", "email", "profile"}
if params.Scope != nil && len(scope) > 0 {
scope = params.Scope
}
authToken, err := token.CreateAuthToken(gc, user, roles, scope)
if err != nil { if err != nil {
return res, err return res, err
} }
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
utils.SaveSessionInDB(user.ID, gc)
cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := int64(1800)
res = &model.AuthResponse{ res = &model.AuthResponse{
Message: `Logged in successfully`, Message: `Logged in successfully`,
AccessToken: &authToken.AccessToken.Token, AccessToken: &authToken.AccessToken.Token,
ExpiresAt: &authToken.AccessToken.ExpiresAt, IDToken: &authToken.IDToken.Token,
ExpiresIn: &expiresIn,
User: user.AsAPIUser(), User: user.AsAPIUser(),
} }
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
if authToken.RefreshToken != nil {
res.RefreshToken = &authToken.RefreshToken.Token
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
}
go utils.SaveSessionInDB(gc, user.ID)
return res, nil return res, nil
} }

View File

@ -7,7 +7,6 @@ import (
"github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/sessionstore" "github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
) )
@ -19,34 +18,21 @@ func LogoutResolver(ctx context.Context) (*model.Response, error) {
return res, err return res, err
} }
// get refresh token
refreshToken, err := token.GetRefreshToken(gc)
if err != nil {
return res, err
}
// get fingerprint hash // get fingerprint hash
fingerprintHash, err := token.GetFingerPrint(gc) fingerprintHash, err := cookie.GetSession(gc)
if err != nil { if err != nil {
return res, err return res, err
} }
decryptedFingerPrint, err := crypto.DecryptAES([]byte(fingerprintHash)) decryptedFingerPrint, err := crypto.DecryptAES(fingerprintHash)
if err != nil { if err != nil {
return res, err return res, err
} }
fingerPrint := string(decryptedFingerPrint) fingerPrint := string(decryptedFingerPrint)
// verify refresh token and fingerprint sessionstore.RemoveState(fingerPrint)
claims, err := token.ParseJWTToken(refreshToken) cookie.DeleteSession(gc)
if err != nil {
return res, err
}
userID := claims["id"].(string)
sessionstore.DeleteUserSession(userID, fingerPrint)
cookie.DeleteCookie(gc)
res = &model.Response{ res = &model.Response{
Message: "Logged out successfully", Message: "Logged out successfully",

View File

@ -109,8 +109,12 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) { if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
// insert verification request // insert verification request
nonce, nonceHash, err := utils.GenerateNonce()
if err != nil {
return res, err
}
verificationType := constants.VerificationTypeMagicLinkLogin verificationType := constants.VerificationTypeMagicLinkLogin
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname) verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname, nonceHash)
if err != nil { if err != nil {
log.Println(`error generating token`, err) log.Println(`error generating token`, err)
} }
@ -119,12 +123,11 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
Identifier: verificationType, Identifier: verificationType,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: params.Email, Email: params.Email,
Nonce: nonce,
}) })
// exec it as go routin so that we can reduce the api latency // exec it as go routin so that we can reduce the api latency
go func() { go email.SendVerificationMail(params.Email, verificationToken, hostname)
email.SendVerificationMail(params.Email, verificationToken, hostname)
}()
} }
res = &model.Response{ res = &model.Response{

View File

@ -2,7 +2,6 @@ package resolvers
import ( import (
"context" "context"
"fmt"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
@ -18,13 +17,17 @@ func ProfileResolver(ctx context.Context) (*model.User, error) {
return res, err return res, err
} }
claims, err := token.ValidateAccessToken(gc) accessToken, err := token.GetAccessToken(gc)
if err != nil { if err != nil {
return res, err return res, err
} }
userID := fmt.Sprintf("%v", claims["id"]) claims, err := token.ValidateAccessToken(gc, accessToken)
if err != nil {
return res, err
}
userID := claims["sub"].(string)
user, err := db.Provider.GetUserByID(userID) user, err := db.Provider.GetUserByID(userID)
if err != nil { if err != nil {
return res, err return res, err

View File

@ -44,7 +44,11 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
} }
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
verificationToken, err := token.CreateVerificationToken(params.Email, params.Identifier, hostname) nonce, nonceHash, err := utils.GenerateNonce()
if err != nil {
return res, err
}
verificationToken, err := token.CreateVerificationToken(params.Email, params.Identifier, hostname, nonceHash)
if err != nil { if err != nil {
log.Println(`error generating token`, err) log.Println(`error generating token`, err)
} }
@ -53,12 +57,11 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
Identifier: params.Identifier, Identifier: params.Identifier,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: params.Email, Email: params.Email,
Nonce: nonce,
}) })
// exec it as go routin so that we can reduce the api latency // exec it as go routin so that we can reduce the api latency
go func() { go email.SendVerificationMail(params.Email, verificationToken, hostname)
email.SendVerificationMail(params.Email, verificationToken, hostname)
}()
res = &model.Response{ res = &model.Response{
Message: `Verification email has been sent. Please check your inbox`, Message: `Verification email has been sent. Please check your inbox`,

View File

@ -12,11 +12,16 @@ import (
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
) )
// ResetPasswordResolver is a resolver for reset password mutation // ResetPasswordResolver is a resolver for reset password mutation
func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) { func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) {
var res *model.Response var res *model.Response
gc, err := utils.GinContextFromContext(ctx)
if err != nil {
return res, err
}
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) { if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
return res, fmt.Errorf(`basic authentication is disabled for this instance`) return res, fmt.Errorf(`basic authentication is disabled for this instance`)
} }
@ -31,12 +36,17 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput)
} }
// verify if token exists in db // verify if token exists in db
claim, err := token.ParseJWTToken(params.Token) hostname := utils.GetHost(gc)
encryptedNonce, err := utils.EncryptNonce(verificationRequest.Nonce)
if err != nil {
return res, err
}
claim, err := token.ParseJWTToken(params.Token, hostname, encryptedNonce, verificationRequest.Email)
if err != nil { if err != nil {
return res, fmt.Errorf(`invalid token`) return res, fmt.Errorf(`invalid token`)
} }
user, err := db.Provider.GetUserByEmail(claim["email"].(string)) user, err := db.Provider.GetUserByEmail(claim["sub"].(string))
if err != nil { if err != nil {
return res, err return res, err
} }

View File

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/sessionstore" "github.com/authorizerdev/authorizer/server/sessionstore"
@ -14,6 +13,7 @@ import (
) )
// SessionResolver is a resolver for session query // SessionResolver is a resolver for session query
// TODO allow validating with code and code verifier instead of cookie (PKCE flow)
func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error) { func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error) {
var res *model.AuthResponse var res *model.AuthResponse
@ -22,48 +22,27 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod
return res, err return res, err
} }
// get refresh token sessionToken, err := cookie.GetSession(gc)
refreshToken, err := token.GetRefreshToken(gc)
if err != nil { if err != nil {
return res, err return res, err
} }
// get fingerprint hash // get session from cookie
fingerprintHash, err := token.GetFingerPrint(gc) claims, err := token.ValidateBrowserSession(gc, sessionToken)
if err != nil { if err != nil {
return res, err return res, err
} }
userID := claims.Subject
decryptedFingerPrint, err := crypto.DecryptAES([]byte(fingerprintHash))
if err != nil {
return res, err
}
fingerPrint := string(decryptedFingerPrint)
// verify refresh token and fingerprint
claims, err := token.ParseJWTToken(refreshToken)
if err != nil {
return res, err
}
userID := claims["id"].(string)
persistedRefresh := sessionstore.GetUserSession(userID, fingerPrint)
if refreshToken != persistedRefresh {
return res, fmt.Errorf(`unauthorized`)
}
user, err := db.Provider.GetUserByID(userID) user, err := db.Provider.GetUserByID(userID)
if err != nil { if err != nil {
return res, err return res, err
} }
// refresh token has "roles" as claim // refresh token has "roles" as claim
claimRoleInterface := claims["roles"].([]interface{}) claimRoleInterface := claims.Roles
claimRoles := []string{} claimRoles := []string{}
for _, v := range claimRoleInterface { for _, v := range claimRoleInterface {
claimRoles = append(claimRoles, v.(string)) claimRoles = append(claimRoles, v)
} }
if params != nil && params.Roles != nil && len(params.Roles) > 0 { if params != nil && params.Roles != nil && len(params.Roles) > 0 {
@ -74,22 +53,35 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod
} }
} }
// delete older session scope := []string{"openid", "email", "profile"}
sessionstore.DeleteUserSession(userID, fingerPrint) if params != nil && params.Scope != nil && len(scope) > 0 {
scope = params.Scope
}
authToken, err := token.CreateAuthToken(user, claimRoles) authToken, err := token.CreateAuthToken(gc, user, claimRoles, scope)
if err != nil { if err != nil {
return res, err return res, err
} }
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
// rollover the session for security
sessionstore.RemoveState(sessionToken)
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := int64(1800)
res = &model.AuthResponse{ res = &model.AuthResponse{
Message: `Session token refreshed`, Message: `Session token refreshed`,
AccessToken: &authToken.AccessToken.Token, AccessToken: &authToken.AccessToken.Token,
ExpiresAt: &authToken.AccessToken.ExpiresAt, ExpiresIn: &expiresIn,
IDToken: &authToken.IDToken.Token,
User: user.AsAPIUser(), User: user.AsAPIUser(),
} }
if authToken.RefreshToken != nil {
res.RefreshToken = &authToken.RefreshToken.Token
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
}
return res, nil return res, nil
} }

View File

@ -123,41 +123,48 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) { if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
// insert verification request // insert verification request
verificationType := constants.VerificationTypeBasicAuthSignup nonce, nonceHash, err := utils.GenerateNonce()
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname)
if err != nil { if err != nil {
log.Println(`error generating token`, err) return res, err
}
verificationType := constants.VerificationTypeBasicAuthSignup
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname, nonceHash)
if err != nil {
return res, err
} }
db.Provider.AddVerificationRequest(models.VerificationRequest{ db.Provider.AddVerificationRequest(models.VerificationRequest{
Token: verificationToken, Token: verificationToken,
Identifier: verificationType, Identifier: verificationType,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: params.Email, Email: params.Email,
Nonce: nonce,
}) })
// exec it as go routin so that we can reduce the api latency // exec it as go routin so that we can reduce the api latency
go func() { go email.SendVerificationMail(params.Email, verificationToken, hostname)
email.SendVerificationMail(params.Email, verificationToken, hostname)
}()
res = &model.AuthResponse{ res = &model.AuthResponse{
Message: `Verification email has been sent. Please check your inbox`, Message: `Verification email has been sent. Please check your inbox`,
User: userToReturn, User: userToReturn,
} }
} else { } else {
scope := []string{"openid", "email", "profile"}
authToken, err := token.CreateAuthToken(user, roles) authToken, err := token.CreateAuthToken(gc, user, roles, scope)
if err != nil { if err != nil {
return res, err return res, err
} }
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash) sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
utils.SaveSessionInDB(user.ID, gc) cookie.SetSession(gc, authToken.FingerPrintHash)
go utils.SaveSessionInDB(gc, user.ID)
expiresIn := int64(1800)
res = &model.AuthResponse{ res = &model.AuthResponse{
Message: `Signed up successfully.`, Message: `Signed up successfully.`,
AccessToken: &authToken.AccessToken.Token, AccessToken: &authToken.AccessToken.Token,
ExpiresAt: &authToken.AccessToken.ExpiresAt, ExpiresIn: &expiresIn,
User: userToReturn, User: userToReturn,
} }
} }

View File

@ -13,6 +13,7 @@ import (
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/email" "github.com/authorizerdev/authorizer/server/email"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/sessionstore" "github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/token"
@ -28,7 +29,11 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
return res, err return res, err
} }
claims, err := token.ValidateAccessToken(gc) accessToken, err := token.GetAccessToken(gc)
if err != nil {
return res, err
}
claims, err := token.ValidateAccessToken(gc, accessToken)
if err != nil { if err != nil {
return res, err return res, err
} }
@ -38,8 +43,8 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
return res, fmt.Errorf("please enter at least one param to update") return res, fmt.Errorf("please enter at least one param to update")
} }
userEmail := fmt.Sprintf("%v", claims["email"]) userID := claims["sub"].(string)
user, err := db.Provider.GetUserByEmail(userEmail) user, err := db.Provider.GetUserByID(userID)
if err != nil { if err != nil {
return res, err return res, err
} }
@ -108,22 +113,28 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
newEmail := strings.ToLower(*params.Email) newEmail := strings.ToLower(*params.Email)
// check if user with new email exists // check if user with new email exists
_, err := db.Provider.GetUserByEmail(newEmail) _, err := db.Provider.GetUserByEmail(newEmail)
// err = nil means user exists // err = nil means user exists
if err == nil { if err == nil {
return res, fmt.Errorf("user with this email address already exists") return res, fmt.Errorf("user with this email address already exists")
} }
sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID)) // TODO figure out how to delete all user sessions
cookie.DeleteCookie(gc) go sessionstore.DeleteAllUserSession(user.ID)
hostname := utils.GetHost(gc) cookie.DeleteSession(gc)
user.Email = newEmail user.Email = newEmail
if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
hostname := utils.GetHost(gc)
user.EmailVerifiedAt = nil user.EmailVerifiedAt = nil
hasEmailChanged = true hasEmailChanged = true
// insert verification request // insert verification request
nonce, nonceHash, err := utils.GenerateNonce()
if err != nil {
return res, err
}
verificationType := constants.VerificationTypeUpdateEmail verificationType := constants.VerificationTypeUpdateEmail
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname) verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname, nonceHash)
if err != nil { if err != nil {
log.Println(`error generating token`, err) log.Println(`error generating token`, err)
} }
@ -132,14 +143,14 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
Identifier: verificationType, Identifier: verificationType,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: newEmail, Email: newEmail,
Nonce: nonce,
}) })
// exec it as go routin so that we can reduce the api latency // exec it as go routin so that we can reduce the api latency
go func() { go email.SendVerificationMail(newEmail, verificationToken, hostname)
email.SendVerificationMail(newEmail, verificationToken, hostname)
}()
}
}
}
_, err = db.Provider.UpdateUser(user) _, err = db.Provider.UpdateUser(user)
if err != nil { if err != nil {
log.Println("error updating user:", err) log.Println("error updating user:", err)

View File

@ -8,7 +8,6 @@ import (
"time" "time"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/email" "github.com/authorizerdev/authorizer/server/email"
@ -95,15 +94,19 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
return res, fmt.Errorf("user with this email address already exists") return res, fmt.Errorf("user with this email address already exists")
} }
sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID)) // TODO figure out how to do this
cookie.DeleteCookie(gc) go sessionstore.DeleteAllUserSession(user.ID)
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
user.Email = newEmail user.Email = newEmail
user.EmailVerifiedAt = nil user.EmailVerifiedAt = nil
// insert verification request // insert verification request
nonce, nonceHash, err := utils.GenerateNonce()
if err != nil {
return res, err
}
verificationType := constants.VerificationTypeUpdateEmail verificationType := constants.VerificationTypeUpdateEmail
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname) verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname, nonceHash)
if err != nil { if err != nil {
log.Println(`error generating token`, err) log.Println(`error generating token`, err)
} }
@ -112,12 +115,12 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
Identifier: verificationType, Identifier: verificationType,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: newEmail, Email: newEmail,
Nonce: nonce,
}) })
// exec it as go routin so that we can reduce the api latency // exec it as go routin so that we can reduce the api latency
go func() { go email.SendVerificationMail(newEmail, verificationToken, hostname)
email.SendVerificationMail(newEmail, verificationToken, hostname)
}()
} }
rolesToSave := "" rolesToSave := ""
@ -136,8 +139,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
rolesToSave = strings.Join(inputRoles, ",") rolesToSave = strings.Join(inputRoles, ",")
} }
sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID)) go sessionstore.DeleteAllUserSession(user.ID)
cookie.DeleteCookie(gc)
} }
if rolesToSave != "" { if rolesToSave != "" {

View File

@ -28,12 +28,17 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
} }
// verify if token exists in db // verify if token exists in db
claim, err := token.ParseJWTToken(params.Token) hostname := utils.GetHost(gc)
encryptedNonce, err := utils.EncryptNonce(verificationRequest.Nonce)
if err != nil {
return res, err
}
claim, err := token.ParseJWTToken(params.Token, hostname, encryptedNonce, verificationRequest.Email)
if err != nil { if err != nil {
return res, fmt.Errorf(`invalid token: %s`, err.Error()) return res, fmt.Errorf(`invalid token: %s`, err.Error())
} }
user, err := db.Provider.GetUserByEmail(claim["email"].(string)) user, err := db.Provider.GetUserByEmail(claim["sub"].(string))
if err != nil { if err != nil {
return res, err return res, err
} }
@ -41,25 +46,35 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
// update email_verified_at in users table // update email_verified_at in users table
now := time.Now().Unix() now := time.Now().Unix()
user.EmailVerifiedAt = &now user.EmailVerifiedAt = &now
db.Provider.UpdateUser(user) user, err = db.Provider.UpdateUser(user)
// delete from verification table if err != nil {
db.Provider.DeleteVerificationRequest(verificationRequest) return res, err
}
roles := strings.Split(user.Roles, ",") // delete from verification table
authToken, err := token.CreateAuthToken(user, roles) err = db.Provider.DeleteVerificationRequest(verificationRequest)
if err != nil { if err != nil {
return res, err return res, err
} }
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
utils.SaveSessionInDB(user.ID, gc)
roles := strings.Split(user.Roles, ",")
scope := []string{"openid", "email", "profile"}
authToken, err := token.CreateAuthToken(gc, user, roles, scope)
if err != nil {
return res, err
}
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash)
go utils.SaveSessionInDB(gc, user.ID)
expiresIn := int64(1800)
res = &model.AuthResponse{ res = &model.AuthResponse{
Message: `Email verified successfully.`, Message: `Email verified successfully.`,
AccessToken: &authToken.AccessToken.Token, AccessToken: &authToken.AccessToken.Token,
ExpiresAt: &authToken.AccessToken.ExpiresAt, IDToken: &authToken.IDToken.Token,
ExpiresIn: &expiresIn,
User: user.AsAPIUser(), User: user.AsAPIUser(),
} }
return res, nil return res, nil
} }

View File

@ -1,6 +1,7 @@
package sessionstore package sessionstore
import ( import (
"strings"
"sync" "sync"
) )
@ -11,42 +12,6 @@ type InMemoryStore struct {
stateStore map[string]string stateStore map[string]string
} }
// AddUserSession adds a user session to the in-memory store.
func (c *InMemoryStore) AddUserSession(userId, accessToken, refreshToken string) {
c.mutex.Lock()
defer c.mutex.Unlock()
// delete sessions > 500 // not recommended for production
if len(c.sessionStore) >= 500 {
c.sessionStore = map[string]map[string]string{}
}
// check if entry exists in map
_, exists := c.sessionStore[userId]
if exists {
tempMap := c.sessionStore[userId]
tempMap[accessToken] = refreshToken
c.sessionStore[userId] = tempMap
} else {
tempMap := map[string]string{
accessToken: refreshToken,
}
c.sessionStore[userId] = tempMap
}
}
// DeleteAllUserSession deletes all the user sessions from in-memory store.
func (c *InMemoryStore) DeleteAllUserSession(userId string) {
c.mutex.Lock()
defer c.mutex.Unlock()
delete(c.sessionStore, userId)
}
// DeleteUserSession deletes the particular user session from in-memory store.
func (c *InMemoryStore) DeleteUserSession(userId, accessToken string) {
c.mutex.Lock()
defer c.mutex.Unlock()
delete(c.sessionStore[userId], accessToken)
}
// ClearStore clears the in-memory store. // ClearStore clears the in-memory store.
func (c *InMemoryStore) ClearStore() { func (c *InMemoryStore) ClearStore() {
c.mutex.Lock() c.mutex.Lock()
@ -54,32 +19,29 @@ func (c *InMemoryStore) ClearStore() {
c.sessionStore = map[string]map[string]string{} c.sessionStore = map[string]map[string]string{}
} }
// GetUserSession returns the user session token from the in-memory store.
func (c *InMemoryStore) GetUserSession(userId, accessToken string) string {
// c.mutex.Lock()
// defer c.mutex.Unlock()
token := ""
if sessionMap, ok := c.sessionStore[userId]; ok {
if val, ok := sessionMap[accessToken]; ok {
token = val
}
}
return token
}
// GetUserSessions returns all the user session token from the in-memory store. // GetUserSessions returns all the user session token from the in-memory store.
func (c *InMemoryStore) GetUserSessions(userId string) map[string]string { func (c *InMemoryStore) GetUserSessions(userId string) map[string]string {
// c.mutex.Lock() // c.mutex.Lock()
// defer c.mutex.Unlock() // defer c.mutex.Unlock()
res := map[string]string{}
sessionMap, ok := c.sessionStore[userId] for k, v := range c.stateStore {
if !ok { split := strings.Split(v, "@")
return nil if split[1] == userId {
res[k] = split[0]
}
} }
return sessionMap return res
}
// DeleteAllUserSession deletes all the user sessions from in-memory store.
func (c *InMemoryStore) DeleteAllUserSession(userId string) {
// c.mutex.Lock()
// defer c.mutex.Unlock()
sessions := GetUserSessions(userId)
for k := range sessions {
RemoveState(k)
}
} }
// SetState sets the state in the in-memory store. // SetState sets the state in the in-memory store.

View File

@ -2,8 +2,8 @@ package sessionstore
import ( import (
"context" "context"
"fmt"
"log" "log"
"strings"
) )
type RedisStore struct { type RedisStore struct {
@ -11,32 +11,6 @@ type RedisStore struct {
store RedisSessionClient store RedisSessionClient
} }
// AddUserSession adds the user session to redis
func (c *RedisStore) AddUserSession(userId, accessToken, refreshToken string) {
err := c.store.HMSet(c.ctx, "authorizer_"+userId, map[string]string{
accessToken: refreshToken,
}).Err()
if err != nil {
log.Fatalln("Error saving redis token:", err)
}
}
// DeleteAllUserSession deletes all the user session from redis
func (c *RedisStore) DeleteAllUserSession(userId string) {
err := c.store.Del(c.ctx, "authorizer_"+userId).Err()
if err != nil {
log.Fatalln("Error deleting redis token:", err)
}
}
// DeleteUserSession deletes the particular user session from redis
func (c *RedisStore) DeleteUserSession(userId, accessToken string) {
err := c.store.HDel(c.ctx, "authorizer_"+userId, accessToken).Err()
if err != nil {
log.Fatalln("Error deleting redis token:", err)
}
}
// ClearStore clears the redis store for authorizer related tokens // ClearStore clears the redis store for authorizer related tokens
func (c *RedisStore) ClearStore() { func (c *RedisStore) ClearStore() {
err := c.store.Del(c.ctx, "authorizer_*").Err() err := c.store.Del(c.ctx, "authorizer_*").Err()
@ -45,32 +19,40 @@ func (c *RedisStore) ClearStore() {
} }
} }
// GetUserSession returns the user session token from the redis store.
func (c *RedisStore) GetUserSession(userId, accessToken string) string {
token := ""
res, err := c.store.HMGet(c.ctx, "authorizer_"+userId, accessToken).Result()
if err != nil {
log.Println("error getting token from redis store:", err)
}
if len(res) > 0 && res[0] != nil {
token = fmt.Sprintf("%v", res[0])
}
return token
}
// GetUserSessions returns all the user session token from the redis store. // GetUserSessions returns all the user session token from the redis store.
func (c *RedisStore) GetUserSessions(userID string) map[string]string { func (c *RedisStore) GetUserSessions(userID string) map[string]string {
res, err := c.store.HGetAll(c.ctx, "authorizer_"+userID).Result() data, err := c.store.HGetAll(c.ctx, "*").Result()
if err != nil { if err != nil {
log.Println("error getting token from redis store:", err) log.Println("error getting token from redis store:", err)
} }
res := map[string]string{}
for k, v := range data {
split := strings.Split(v, "@")
if split[1] == userID {
res[k] = split[0]
}
}
return res return res
} }
// DeleteAllUserSession deletes all the user session from redis
func (c *RedisStore) DeleteAllUserSession(userId string) {
sessions := GetUserSessions(userId)
for k, v := range sessions {
if k == "token" {
err := c.store.Del(c.ctx, v)
if err != nil {
log.Println("Error deleting redis token:", err)
}
}
}
}
// SetState sets the state in redis store. // SetState sets the state in redis store.
func (c *RedisStore) SetState(key, state string) { func (c *RedisStore) SetState(key, value string) {
err := c.store.Set(c.ctx, key, state, 0).Err() err := c.store.Set(c.ctx, key, value, 0).Err()
if err != nil { if err != nil {
log.Fatalln("Error saving redis token:", err) log.Fatalln("Error saving redis token:", err)
} }

View File

@ -22,26 +22,6 @@ type SessionStore struct {
// reference to various session store instances // reference to various session store instances
var SessionStoreObj SessionStore var SessionStoreObj SessionStore
// SetUserSession sets the user session in the session store
func SetUserSession(userId, fingerprint, refreshToken string) {
if SessionStoreObj.RedisMemoryStoreObj != nil {
SessionStoreObj.RedisMemoryStoreObj.AddUserSession(userId, fingerprint, refreshToken)
}
if SessionStoreObj.InMemoryStoreObj != nil {
SessionStoreObj.InMemoryStoreObj.AddUserSession(userId, fingerprint, refreshToken)
}
}
// DeleteUserSession deletes the particular user session from the session store
func DeleteUserSession(userId, fingerprint string) {
if SessionStoreObj.RedisMemoryStoreObj != nil {
SessionStoreObj.RedisMemoryStoreObj.DeleteUserSession(userId, fingerprint)
}
if SessionStoreObj.InMemoryStoreObj != nil {
SessionStoreObj.InMemoryStoreObj.DeleteUserSession(userId, fingerprint)
}
}
// DeleteAllSessions deletes all the sessions from the session store // DeleteAllSessions deletes all the sessions from the session store
func DeleteAllUserSession(userId string) { func DeleteAllUserSession(userId string) {
if SessionStoreObj.RedisMemoryStoreObj != nil { if SessionStoreObj.RedisMemoryStoreObj != nil {
@ -52,18 +32,6 @@ func DeleteAllUserSession(userId string) {
} }
} }
// GetUserSession returns the user session from the session store
func GetUserSession(userId, fingerprint string) string {
if SessionStoreObj.RedisMemoryStoreObj != nil {
return SessionStoreObj.RedisMemoryStoreObj.GetUserSession(userId, fingerprint)
}
if SessionStoreObj.InMemoryStoreObj != nil {
return SessionStoreObj.InMemoryStoreObj.GetUserSession(userId, fingerprint)
}
return ""
}
// GetUserSessions returns all the user sessions from the session store // GetUserSessions returns all the user sessions from the session store
func GetUserSessions(userId string) map[string]string { func GetUserSessions(userId string) map[string]string {
if SessionStoreObj.RedisMemoryStoreObj != nil { if SessionStoreObj.RedisMemoryStoreObj != nil {

View File

@ -1,38 +0,0 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/token"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)
func isValidJWTTests(t *testing.T, s TestSetup) {
t.Helper()
_, ctx := createContext(s)
expiredToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhbGxvd2VkX3JvbGVzIjpbIiJdLCJiaXJ0aGRhdGUiOm51bGwsImNyZWF0ZWRfYXQiOjAsImVtYWlsIjoiam9obi5kb2VAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJleHAiOjE2NDI5NjEwMTEsImV4dHJhIjp7IngtZXh0cmEtaWQiOiJkMmNhMjQwNy05MzZmLTQwYzQtOTQ2NS05Y2M5MWYxZTJhNDQifSwiZmFtaWx5X25hbWUiOm51bGwsImdlbmRlciI6bnVsbCwiZ2l2ZW5fbmFtZSI6bnVsbCwiaWF0IjoxNjQyOTYwOTgxLCJpZCI6ImQyY2EyNDA3LTkzNmYtNDBjNC05NDY1LTljYzkxZjFlMmE0NCIsIm1pZGRsZV9uYW1lIjpudWxsLCJuaWNrbmFtZSI6bnVsbCwicGhvbmVfbnVtYmVyIjpudWxsLCJwaG9uZV9udW1iZXJfdmVyaWZpZWQiOmZhbHNlLCJwaWN0dXJlIjpudWxsLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJqb2huLmRvZUBnbWFpbC5jb20iLCJyb2xlIjpbXSwic2lnbnVwX21ldGhvZHMiOiIiLCJ0b2tlbl90eXBlIjoiYWNjZXNzX3Rva2VuIiwidXBkYXRlZF9hdCI6MH0.FrdyeOC5e8uU1SowGj0omFJuwRnh4BrEk89S_fbEkzs"
t.Run(`should fail for invalid jwt`, func(t *testing.T) {
_, err := resolvers.IsValidJwtResolver(ctx, &model.IsValidJWTQueryInput{
Jwt: &expiredToken,
})
assert.NotNil(t, err)
})
t.Run(`should pass with valid jwt`, func(t *testing.T) {
authToken, err := token.CreateAuthToken(models.User{
ID: uuid.New().String(),
Email: "john.doe@gmail.com",
}, []string{})
assert.Nil(t, err)
res, err := resolvers.IsValidJwtResolver(ctx, &model.IsValidJWTQueryInput{
Jwt: &authToken.AccessToken.Token,
})
assert.Nil(t, err)
assert.True(t, res.Valid)
})
}

View File

@ -9,6 +9,7 @@ import (
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/token"
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
"github.com/google/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -18,12 +19,17 @@ func TestJwt(t *testing.T) {
publicKey := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey) publicKey := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)
privateKey := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPrivateKey) privateKey := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPrivateKey)
clientID := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) clientID := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID)
nonce := uuid.New().String()
hostname := "localhost"
subject := "test"
claims := jwt.MapClaims{ claims := jwt.MapClaims{
"exp": time.Now().Add(time.Minute * 30).Unix(), "exp": time.Now().Add(time.Minute * 30).Unix(),
"iat": time.Now().Unix(), "iat": time.Now().Unix(),
"email": "test@yopmail.com", "email": "test@yopmail.com",
"sub": "test", "sub": subject,
"aud": clientID, "aud": clientID,
"nonce": nonce,
"iss": hostname,
} }
t.Run("invalid jwt type", func(t *testing.T) { t.Run("invalid jwt type", func(t *testing.T) {
@ -42,7 +48,7 @@ func TestJwt(t *testing.T) {
} }
jwtToken, err := token.SignJWTToken(expiredClaims) jwtToken, err := token.SignJWTToken(expiredClaims)
assert.NoError(t, err) assert.NoError(t, err)
_, err = token.ParseJWTToken(jwtToken) _, err = token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.Error(t, err, err.Error(), "Token is expired") assert.Error(t, err, err.Error(), "Token is expired")
}) })
t.Run("HMAC algorithms", func(t *testing.T) { t.Run("HMAC algorithms", func(t *testing.T) {
@ -52,7 +58,7 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken) c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
}) })
@ -61,7 +67,7 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken) c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
}) })
@ -70,7 +76,7 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken) c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
}) })
@ -86,7 +92,7 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken) c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
}) })
@ -99,7 +105,7 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken) c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
}) })
@ -112,7 +118,7 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken) c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
}) })
@ -128,7 +134,7 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken) c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
}) })
@ -141,7 +147,7 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken) c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
}) })
@ -154,7 +160,7 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken) c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
}) })

View File

@ -5,14 +5,17 @@ import (
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func loginTests(t *testing.T, s TestSetup) { func loginTests(t *testing.T, s TestSetup) {
t.Helper() t.Helper()
t.Run(`should login`, func(t *testing.T) { t.Run(`should login`, func(t *testing.T) {
t.Logf("=> is enabled: %v", envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification))
_, ctx := createContext(s) _, ctx := createContext(s)
email := "login." + s.TestInfo.Email email := "login." + s.TestInfo.Email
_, err := resolvers.SignupResolver(ctx, model.SignUpInput{ _, err := resolvers.SignupResolver(ctx, model.SignUpInput{
@ -21,15 +24,19 @@ func loginTests(t *testing.T, s TestSetup) {
ConfirmPassword: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password,
}) })
_, err = resolvers.LoginResolver(ctx, model.LoginInput{ res, err := resolvers.LoginResolver(ctx, model.LoginInput{
Email: email, Email: email,
Password: s.TestInfo.Password, Password: s.TestInfo.Password,
}) })
assert.NotNil(t, err, "should fail because email is not verified") assert.NotNil(t, err, "should fail because email is not verified")
assert.Nil(t, res)
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(email, constants.VerificationTypeBasicAuthSignup) verificationRequest, err := db.Provider.GetVerificationRequestByEmail(email, constants.VerificationTypeBasicAuthSignup)
res, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ n, err := utils.EncryptNonce(verificationRequest.Nonce)
assert.NoError(t, err)
assert.NotEmpty(t, n)
assert.NotNil(t, verificationRequest)
res, err = resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -2,11 +2,9 @@ package test
import ( import (
"fmt" "fmt"
"net/url"
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
@ -30,18 +28,15 @@ func logoutTests(t *testing.T, s TestSetup) {
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })
sessions := sessionstore.GetUserSessions(verifyRes.User.ID)
fingerPrint := ""
refreshToken := ""
for key, val := range sessions {
fingerPrint = key
refreshToken = val
}
fingerPrintHash, _ := crypto.EncryptAES([]byte(fingerPrint))
token := *verifyRes.AccessToken token := *verifyRes.AccessToken
cookie := fmt.Sprintf("%s=%s;%s=%s;%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", url.QueryEscape(string(fingerPrintHash)), envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token) sessions := sessionstore.GetUserSessions(verifyRes.User.ID)
cookie := ""
// set all they keys in cookie one of them should be session cookie
for key := range sessions {
if key != token {
cookie += fmt.Sprintf("%s=%s;", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", key)
}
}
req.Header.Set("Cookie", cookie) req.Header.Set("Cookie", cookie)
_, err = resolvers.LogoutResolver(ctx) _, err = resolvers.LogoutResolver(ctx)

View File

@ -1,12 +1,11 @@
package test package test
import ( import (
"fmt" "context"
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -27,12 +26,13 @@ func magicLinkLoginTests(t *testing.T, s TestSetup) {
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })
assert.NoError(t, err)
token := *verifyRes.AccessToken assert.NotNil(t, verifyRes.AccessToken)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token)) s.GinContext.Request.Header.Set("Authorization", "Bearer "+*verifyRes.AccessToken)
ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext)
_, err = resolvers.ProfileResolver(ctx) _, err = resolvers.ProfileResolver(ctx)
assert.Nil(t, err) assert.Nil(t, err)
s.GinContext.Request.Header.Set("Authorization", "")
cleanData(email) cleanData(email)
}) })
} }

View File

@ -1,12 +1,11 @@
package test package test
import ( import (
"fmt" "context"
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -14,7 +13,7 @@ import (
func profileTests(t *testing.T, s TestSetup) { func profileTests(t *testing.T, s TestSetup) {
t.Helper() t.Helper()
t.Run(`should get profile only with token`, func(t *testing.T) { t.Run(`should get profile only access_token token`, func(t *testing.T) {
req, ctx := createContext(s) req, ctx := createContext(s)
email := "profile." + s.TestInfo.Email email := "profile." + s.TestInfo.Email
@ -31,11 +30,14 @@ func profileTests(t *testing.T, s TestSetup) {
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })
assert.NoError(t, err)
assert.NotNil(t, verifyRes.AccessToken)
token := *verifyRes.AccessToken s.GinContext.Request.Header.Set("Authorization", "Bearer "+*verifyRes.AccessToken)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token)) ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext)
profileRes, err := resolvers.ProfileResolver(ctx) profileRes, err := resolvers.ProfileResolver(ctx)
assert.Nil(t, err) assert.Nil(t, err)
s.GinContext.Request.Header.Set("Authorization", "")
newEmail := *&profileRes.Email newEmail := *&profileRes.Email
assert.Equal(t, email, newEmail, "emails should be equal") assert.Equal(t, email, newEmail, "emails should be equal")

View File

@ -15,15 +15,16 @@ func TestResolvers(t *testing.T) {
// constants.DbTypeArangodb: "http://localhost:8529", // constants.DbTypeArangodb: "http://localhost:8529",
// constants.DbTypeMongodb: "mongodb://localhost:27017", // constants.DbTypeMongodb: "mongodb://localhost:27017",
} }
envstore.EnvStoreObj.ResetStore()
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, "test") envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, "test")
for dbType, dbURL := range databases { for dbType, dbURL := range databases {
s := testSetup()
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseURL, dbURL) envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseURL, dbURL)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseType, dbType) envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseType, dbType)
s := testSetup()
defer s.Server.Close() defer s.Server.Close()
db.InitDB() err := db.InitDB()
if err != nil {
t.Errorf("Error initializing database: %s", err)
}
// clean the persisted config for test to use fresh config // clean the persisted config for test to use fresh config
envData, err := db.Provider.GetEnv() envData, err := db.Provider.GetEnv()
@ -31,12 +32,10 @@ func TestResolvers(t *testing.T) {
envData.EnvData = "" envData.EnvData = ""
db.Provider.UpdateEnv(envData) db.Provider.UpdateEnv(envData)
} }
err = env.InitAllEnv()
if err != nil {
t.Error(err)
}
env.PersistEnv() env.PersistEnv()
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnv, "test")
envstore.EnvStoreObj.UpdateEnvVariable(constants.BoolStoreIdentifier, constants.EnvKeyIsProd, false)
t.Run("should pass tests for "+dbType, func(t *testing.T) { t.Run("should pass tests for "+dbType, func(t *testing.T) {
// admin tests // admin tests
adminSignupTests(t, s) adminSignupTests(t, s)
@ -63,7 +62,6 @@ func TestResolvers(t *testing.T) {
magicLinkLoginTests(t, s) magicLinkLoginTests(t, s)
logoutTests(t, s) logoutTests(t, s)
metaTests(t, s) metaTests(t, s)
isValidJWTTests(t, s)
}) })
} }
} }

View File

@ -2,11 +2,10 @@ package test
import ( import (
"fmt" "fmt"
"net/url" "strings"
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
@ -36,17 +35,15 @@ func sessionTests(t *testing.T, s TestSetup) {
}) })
sessions := sessionstore.GetUserSessions(verifyRes.User.ID) sessions := sessionstore.GetUserSessions(verifyRes.User.ID)
fingerPrint := "" cookie := ""
refreshToken := ""
for key, val := range sessions {
fingerPrint = key
refreshToken = val
}
fingerPrintHash, _ := crypto.EncryptAES([]byte(fingerPrint))
token := *verifyRes.AccessToken token := *verifyRes.AccessToken
cookie := fmt.Sprintf("%s=%s;%s=%s;%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", url.QueryEscape(string(fingerPrintHash)), envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token) // set all they keys in cookie one of them should be session cookie
for key := range sessions {
if key != token {
cookie += fmt.Sprintf("%s=%s;", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", key)
}
}
cookie = strings.TrimSuffix(cookie, ";")
req.Header.Set("Cookie", cookie) req.Header.Set("Cookie", cookie)

View File

@ -72,13 +72,13 @@ func testSetup() TestSetup {
} }
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample") envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample")
env.InitRequiredEnv()
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpHost, "smtp.yopmail.com") envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpHost, "smtp.yopmail.com")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPort, "2525") envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPort, "2525")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpUsername, "lakhan@yopmail.com") envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpUsername, "lakhan@yopmail.com")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPassword, "test") envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPassword, "test")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySenderEmail, "info@yopmail.com") envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySenderEmail, "info@yopmail.com")
envstore.EnvStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyProtectedRoles, []string{"admin"}) envstore.EnvStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyProtectedRoles, []string{"admin"})
env.InitRequiredEnv()
db.InitDB() db.InitDB()
env.InitAllEnv() env.InitAllEnv()
sessionstore.InitSession() sessionstore.InitSession()

View File

@ -1,12 +1,11 @@
package test package test
import ( import (
"fmt" "context"
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -34,18 +33,16 @@ func updateProfileTests(t *testing.T, s TestSetup) {
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })
assert.NoError(t, err)
token := *verifyRes.AccessToken s.GinContext.Request.Header.Set("Authorization", "Bearer "+*verifyRes.AccessToken)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token)) ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext)
_, err = resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{
FamilyName: &fName,
})
assert.Nil(t, err)
newEmail := "new_" + email newEmail := "new_" + email
_, err = resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{ _, err = resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{
Email: &newEmail, Email: &newEmail,
}) })
s.GinContext.Request.Header.Set("Authorization", "")
assert.Nil(t, err) assert.Nil(t, err)
_, err = resolvers.ProfileResolver(ctx) _, err = resolvers.ProfileResolver(ctx)
assert.NotNil(t, err, "unauthorized") assert.NotNil(t, err, "unauthorized")

View File

@ -19,12 +19,15 @@ func verificationRequestsTest(t *testing.T, s TestSetup) {
req, ctx := createContext(s) req, ctx := createContext(s)
email := "verification_requests." + s.TestInfo.Email email := "verification_requests." + s.TestInfo.Email
resolvers.SignupResolver(ctx, model.SignUpInput{ res, err := resolvers.SignupResolver(ctx, model.SignUpInput{
Email: email, Email: email,
Password: s.TestInfo.Password, Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password,
}) })
assert.NoError(t, err)
assert.NotNil(t, res)
limit := int64(10) limit := int64(10)
page := int64(1) page := int64(1)
pagination := &model.PaginatedInput{ pagination := &model.PaginatedInput{

View File

@ -2,23 +2,23 @@ package token
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"log" "log"
"os" "os"
"strings" "strings"
"time" "time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"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/gin-gonic/gin"
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/robertkrimen/otto" "github.com/robertkrimen/otto"
"github.com/authorizerdev/authorizer/server/constants"
"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/authorizerdev/authorizer/server/utils"
) )
// JWTToken is a struct to hold JWT token and its expiration time // JWTToken is a struct to hold JWT token and its expiration time
@ -33,60 +33,219 @@ type Token struct {
FingerPrintHash string `json:"fingerprint_hash"` FingerPrintHash string `json:"fingerprint_hash"`
RefreshToken *JWTToken `json:"refresh_token"` RefreshToken *JWTToken `json:"refresh_token"`
AccessToken *JWTToken `json:"access_token"` AccessToken *JWTToken `json:"access_token"`
IDToken *JWTToken `json:"id_token"`
}
// SessionData
type SessionData struct {
Subject string `json:"sub"`
Roles []string `json:"roles"`
Scope []string `json:"scope"`
Nonce string `json:"nonce"`
IssuedAt int64 `json:"iat"`
ExpiresAt int64 `json:"exp"`
}
// CreateSessionToken creates a new session token
func CreateSessionToken(user models.User, nonce string, roles, scope []string) (*SessionData, string, error) {
fingerPrintMap := &SessionData{
Nonce: nonce,
Roles: roles,
Subject: user.ID,
Scope: scope,
IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().AddDate(1, 0, 0).Unix(),
}
fingerPrintBytes, _ := json.Marshal(fingerPrintMap)
fingerPrintHash, err := crypto.EncryptAES(string(fingerPrintBytes))
if err != nil {
return nil, "", err
}
return fingerPrintMap, fingerPrintHash, nil
} }
// CreateAuthToken creates a new auth token when userlogs in // CreateAuthToken creates a new auth token when userlogs in
func CreateAuthToken(user models.User, roles []string) (*Token, error) { func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string) (*Token, error) {
fingerprint := uuid.NewString() hostname := utils.GetHost(gc)
fingerPrintHashBytes, err := crypto.EncryptAES([]byte(fingerprint)) nonce := uuid.New().String()
_, fingerPrintHash, err := CreateSessionToken(user, nonce, roles, scope)
if err != nil { if err != nil {
return nil, err return nil, err
} }
refreshToken, refreshTokenExpiresAt, err := CreateRefreshToken(user, roles) accessToken, accessTokenExpiresAt, err := CreateAccessToken(user, roles, scope, hostname, nonce)
if err != nil { if err != nil {
return nil, err return nil, err
} }
accessToken, accessTokenExpiresAt, err := CreateAccessToken(user, roles) idToken, idTokenExpiresAt, err := CreateIDToken(user, roles, hostname, nonce)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Token{ res := &Token{
FingerPrint: fingerprint, FingerPrint: nonce,
FingerPrintHash: string(fingerPrintHashBytes), FingerPrintHash: fingerPrintHash,
RefreshToken: &JWTToken{Token: refreshToken, ExpiresAt: refreshTokenExpiresAt},
AccessToken: &JWTToken{Token: accessToken, ExpiresAt: accessTokenExpiresAt}, AccessToken: &JWTToken{Token: accessToken, ExpiresAt: accessTokenExpiresAt},
}, nil IDToken: &JWTToken{Token: idToken, ExpiresAt: idTokenExpiresAt},
}
if utils.StringSliceContains(scope, "offline_access") {
refreshToken, refreshTokenExpiresAt, err := CreateRefreshToken(user, roles, hostname, nonce)
if err != nil {
return nil, err
}
res.RefreshToken = &JWTToken{Token: refreshToken, ExpiresAt: refreshTokenExpiresAt}
}
return res, nil
} }
// CreateRefreshToken util to create JWT token // CreateRefreshToken util to create JWT token
func CreateRefreshToken(user models.User, roles []string) (string, int64, error) { func CreateRefreshToken(user models.User, roles []string, hostname, nonce string) (string, int64, error) {
// expires in 1 year // expires in 1 year
expiryBound := time.Hour * 8760 expiryBound := time.Hour * 8760
expiresAt := time.Now().Add(expiryBound).Unix() expiresAt := time.Now().Add(expiryBound).Unix()
customClaims := jwt.MapClaims{ customClaims := jwt.MapClaims{
"iss": "", "iss": hostname,
"aud": "", "aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID),
"sub": user.ID, "sub": user.ID,
"exp": expiresAt, "exp": expiresAt,
"iat": time.Now().Unix(), "iat": time.Now().Unix(),
"token_type": constants.TokenTypeRefreshToken, "token_type": constants.TokenTypeRefreshToken,
"roles": roles, "roles": roles,
"id": user.ID, "nonce": nonce,
} }
token, err := SignJWTToken(customClaims) token, err := SignJWTToken(customClaims)
if err != nil { if err != nil {
return "", 0, err return "", 0, err
} }
return token, expiresAt, nil return token, expiresAt, nil
} }
// CreateAccessToken util to create JWT token, based on // CreateAccessToken util to create JWT token, based on
// user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT // user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT
func CreateAccessToken(user models.User, roles []string) (string, int64, error) { func CreateAccessToken(user models.User, roles, scopes []string, hostName, nonce string) (string, int64, error) {
expiryBound := time.Minute * 30
expiresAt := time.Now().Add(expiryBound).Unix()
customClaims := jwt.MapClaims{
"iss": hostName,
"aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID),
"nonce": nonce,
"sub": user.ID,
"exp": expiresAt,
"iat": time.Now().Unix(),
"token_type": constants.TokenTypeAccessToken,
"scope": scopes,
"roles": roles,
}
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`)
}
if !strings.HasPrefix(auth, "Bearer ") {
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) {
var res map[string]interface{}
if accessToken == "" {
return res, fmt.Errorf(`unauthorized`)
}
savedSession := sessionstore.GetState(accessToken)
if savedSession == "" {
return res, fmt.Errorf(`unauthorized`)
}
savedSessionSplit := strings.Split(savedSession, "@")
nonce := savedSessionSplit[0]
userID := savedSessionSplit[1]
hostname := utils.GetHost(gc)
res, err := ParseJWTToken(accessToken, hostname, nonce, userID)
if err != nil {
return res, err
}
if res["token_type"] != constants.TokenTypeAccessToken {
return res, fmt.Errorf(`unauthorized: invalid token type`)
}
return res, nil
}
func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionData, error) {
if encryptedSession == "" {
return nil, fmt.Errorf(`unauthorized`)
}
savedSession := sessionstore.GetState(encryptedSession)
if savedSession == "" {
return nil, fmt.Errorf(`unauthorized`)
}
savedSessionSplit := strings.Split(savedSession, "@")
nonce := savedSessionSplit[0]
userID := savedSessionSplit[1]
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
}
if res.Nonce != nonce {
return nil, fmt.Errorf(`unauthorized: invalid nonce`)
}
if res.Subject != userID {
return nil, fmt.Errorf(`unauthorized: invalid user id`)
}
if res.ExpiresAt < time.Now().Unix() {
return nil, fmt.Errorf(`unauthorized: token expired`)
}
// TODO validate scope
// if !reflect.DeepEqual(res.Roles, roles) {
// return res, "", fmt.Errorf(`unauthorized`)
// }
return &res, nil
}
// CreateIDToken util to create JWT token, based on
// user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT
func CreateIDToken(user models.User, roles []string, hostname, nonce string) (string, int64, error) {
expiryBound := time.Minute * 30 expiryBound := time.Minute * 30
expiresAt := time.Now().Add(expiryBound).Unix() expiresAt := time.Now().Add(expiryBound).Unix()
@ -97,13 +256,13 @@ func CreateAccessToken(user models.User, roles []string) (string, int64, error)
claimKey := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim) claimKey := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim)
customClaims := jwt.MapClaims{ customClaims := jwt.MapClaims{
"iss": "", "iss": hostname,
"aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID), "aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID),
"nonce": "", "nonce": nonce,
"sub": user.ID, "sub": user.ID,
"exp": expiresAt, "exp": expiresAt,
"iat": time.Now().Unix(), "iat": time.Now().Unix(),
"token_type": constants.TokenTypeAccessToken, "token_type": constants.TokenTypeIdentityToken,
"allowed_roles": strings.Split(user.Roles, ","), "allowed_roles": strings.Split(user.Roles, ","),
claimKey: roles, claimKey: roles,
} }
@ -152,58 +311,18 @@ func CreateAccessToken(user models.User, roles []string) (string, int64, error)
return token, expiresAt, nil return token, expiresAt, nil
} }
// GetAccessToken returns the access token from the request (either from header or cookie) // GetIDToken returns the id token from the request header
func GetAccessToken(gc *gin.Context) (string, error) { func GetIDToken(gc *gin.Context) (string, error) {
token, err := cookie.GetAccessTokenCookie(gc)
if err != nil || token == "" {
// try to check in auth header for cookie // try to check in auth header for cookie
auth := gc.Request.Header.Get("Authorization") auth := gc.Request.Header.Get("Authorization")
if auth == "" { if auth == "" {
return "", fmt.Errorf(`unauthorized`) return "", fmt.Errorf(`unauthorized`)
} }
token = strings.TrimPrefix(auth, "Bearer ") if !strings.HasPrefix(auth, "Bearer ") {
return "", fmt.Errorf(`not a bearer token`)
} }
token := strings.TrimPrefix(auth, "Bearer ")
return token, nil 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
}
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
}

View File

@ -44,7 +44,7 @@ func SignJWTToken(claims jwt.MapClaims) (string, error) {
} }
// ParseJWTToken common util to parse jwt token // ParseJWTToken common util to parse jwt token
func ParseJWTToken(token string) (jwt.MapClaims, error) { func ParseJWTToken(token, hostname, nonce, subject string) (jwt.MapClaims, error) {
jwtType := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType) jwtType := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
signingMethod := jwt.GetSigningMethod(jwtType) signingMethod := jwt.GetSigningMethod(jwtType)
@ -87,5 +87,21 @@ func ParseJWTToken(token string) (jwt.MapClaims, error) {
claims["exp"] = intExp claims["exp"] = intExp
claims["iat"] = intIat claims["iat"] = intIat
if claims["aud"] != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) {
return claims, errors.New("invalid audience")
}
if claims["nonce"] != nonce {
return claims, errors.New("invalid nonce")
}
if claims["iss"] != hostname {
return claims, errors.New("invalid issuer")
}
if claims["sub"] != subject {
return claims, errors.New("invalid subject")
}
return claims, nil return claims, nil
} }

View File

@ -9,13 +9,15 @@ import (
) )
// CreateVerificationToken creates a verification JWT token // CreateVerificationToken creates a verification JWT token
func CreateVerificationToken(email, tokenType, hostname string) (string, error) { func CreateVerificationToken(email, tokenType, hostname, nonceHash string) (string, error) {
claims := jwt.MapClaims{ claims := jwt.MapClaims{
"iss": hostname,
"aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID),
"sub": email,
"exp": time.Now().Add(time.Minute * 30).Unix(), "exp": time.Now().Add(time.Minute * 30).Unix(),
"iat": time.Now().Unix(), "iat": time.Now().Unix(),
"token_type": tokenType, "token_type": tokenType,
"email": email, "nonce": nonceHash,
"host": hostname,
"redirect_url": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL), "redirect_url": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL),
} }

View File

@ -20,7 +20,7 @@ func StringSliceContains(s []string, e string) bool {
// SaveSessionInDB saves sessions generated for a given user with meta information // SaveSessionInDB saves sessions generated for a given user with meta information
// Do not store token here as that could be security breach // Do not store token here as that could be security breach
func SaveSessionInDB(userId string, c *gin.Context) { func SaveSessionInDB(c *gin.Context, userId string) {
sessionData := models.Session{ sessionData := models.Session{
UserID: userId, UserID: userId,
UserAgent: GetUserAgent(c.Request), UserAgent: GetUserAgent(c.Request),

36
server/utils/nonce.go Normal file
View File

@ -0,0 +1,36 @@
package utils
import (
"github.com/google/uuid"
"github.com/authorizerdev/authorizer/server/crypto"
)
// GenerateNonce generats random nonce string and returns
// the nonce string, nonce hash, error
func GenerateNonce() (string, string, error) {
nonce := uuid.New().String()
nonceHash, err := crypto.EncryptAES(nonce)
if err != nil {
return "", "", err
}
return nonce, nonceHash, err
}
// EncryptNonce nonce string
func EncryptNonce(nonce string) (string, error) {
nonceHash, err := crypto.EncryptAES(nonce)
if err != nil {
return "", err
}
return nonceHash, err
}
// DecryptNonce nonce string
func DecryptNonce(nonceHash string) (string, error) {
nonce, err := crypto.DecryptAES(nonceHash)
if err != nil {
return "", err
}
return nonce, err
}