test-cases:

* completed test cases
* tested for all dbs
This commit is contained in:
lemonScaletech 2023-09-12 19:09:37 +05:30
parent 80e8b3aaac
commit 96fdc38c3c
13 changed files with 230 additions and 58 deletions

View File

@ -267,6 +267,14 @@ func NewProvider() (*provider, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// add totp_secret and totp_verified on users table
totpTableAlterQuery := fmt.Sprintf(`ALTER TABLE %s.%s ADD (totp_verified boolean, totp_secret text, app_data text)`, KeySpace, models.Collections.User)
err = session.Query(totpTableAlterQuery).Exec()
if err != nil {
log.Debug("Failed to alter table as column exists: ", err)
// return nil, err
}
return &provider{ return &provider{
db: session, db: session,
}, err }, err

View File

@ -39,6 +39,7 @@ func (p *provider) AddUser(ctx context.Context, user *models.User) (*models.User
user.CreatedAt = time.Now().Unix() user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix() user.UpdatedAt = time.Now().Unix()
user.TotpVerified = false
bytes, err := json.Marshal(user) bytes, err := json.Marshal(user)
if err != nil { if err != nil {
@ -177,13 +178,19 @@ func (p *provider) ListUsers(ctx context.Context, pagination *model.Pagination)
// there is no offset in cassandra // there is no offset in cassandra
// so we fetch till limit + offset // so we fetch till limit + offset
// and return the results from offset to limit // and return the results from offset to limit
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.User, pagination.Limit+pagination.Offset) query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, "+
"nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled,"+
" created_at, updated_at, totp_verified FROM %s LIMIT %d", KeySpace+"."+models.Collections.User,
pagination.Limit+pagination.Offset)
scanner := p.db.Query(query).Iter().Scanner() scanner := p.db.Query(query).Iter().Scanner()
counter := int64(0) counter := int64(0)
for scanner.Next() { for scanner.Next() {
if counter >= pagination.Offset { if counter >= pagination.Offset {
var user models.User var user models.User
err := scanner.Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.CreatedAt, &user.UpdatedAt) err := scanner.Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods,
&user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber,
&user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled,
&user.CreatedAt, &user.UpdatedAt, &user.TotpVerified)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -200,8 +207,8 @@ func (p *provider) ListUsers(ctx context.Context, pagination *model.Pagination)
// GetUserByEmail to get user information from database using email address // GetUserByEmail to get user information from database using email address
func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.User, error) { func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.User, error) {
var user models.User var user models.User
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s WHERE email = '%s' LIMIT 1 ALLOW FILTERING", KeySpace+"."+models.Collections.User, email) query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at, totp_secret, totp_verified, app_data FROM %s WHERE email = '%s' LIMIT 1 ALLOW FILTERING", KeySpace+"."+models.Collections.User, email)
err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.CreatedAt, &user.UpdatedAt) err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.CreatedAt, &user.UpdatedAt, &user.TotpSecret, &user.TotpVerified, &user.AppData)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -211,8 +218,8 @@ func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.Us
// GetUserByID to get user information from database using user ID // GetUserByID to get user information from database using user ID
func (p *provider) GetUserByID(ctx context.Context, id string) (*models.User, error) { func (p *provider) GetUserByID(ctx context.Context, id string) (*models.User, error) {
var user models.User var user models.User
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s WHERE id = '%s' LIMIT 1", KeySpace+"."+models.Collections.User, id) query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at, totp_secret, totp_verified, app_data FROM %s WHERE id = '%s' LIMIT 1", KeySpace+"."+models.Collections.User, id)
err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.CreatedAt, &user.UpdatedAt) err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.CreatedAt, &user.UpdatedAt, &user.TotpSecret, &user.TotpVerified, &user.AppData)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -103,7 +103,7 @@ func (p *provider) ListUsers(ctx context.Context, pagination *model.Pagination)
// GetUserByEmail to get user information from database using email address // GetUserByEmail to get user information from database using email address
func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.User, error) { func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.User, error) {
var user *models.User var user *models.User
query := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s.%s WHERE email = $1 LIMIT 1", p.scopeName, models.Collections.User) query := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at, totp_secret, totp_verified FROM %s.%s WHERE email = $1 LIMIT 1", p.scopeName, models.Collections.User)
q, err := p.db.Query(query, &gocb.QueryOptions{ q, err := p.db.Query(query, &gocb.QueryOptions{
ScanConsistency: gocb.QueryScanConsistencyRequestPlus, ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
Context: ctx, Context: ctx,
@ -122,7 +122,7 @@ func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.Us
// GetUserByID to get user information from database using user ID // GetUserByID to get user information from database using user ID
func (p *provider) GetUserByID(ctx context.Context, id string) (*models.User, error) { func (p *provider) GetUserByID(ctx context.Context, id string) (*models.User, error) {
var user *models.User var user *models.User
query := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s.%s WHERE _id = $1 LIMIT 1", p.scopeName, models.Collections.User) query := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at, totp_secret, totp_verified FROM %s.%s WHERE _id = $1 LIMIT 1", p.scopeName, models.Collections.User)
q, err := p.db.Query(query, &gocb.QueryOptions{ q, err := p.db.Query(query, &gocb.QueryOptions{
ScanConsistency: gocb.QueryScanConsistencyRequestPlus, ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
Context: ctx, Context: ctx,

View File

@ -13,6 +13,7 @@ require (
github.com/go-playground/validator/v10 v10.11.1 // indirect github.com/go-playground/validator/v10 v10.11.1 // indirect
github.com/goccy/go-json v0.9.11 // indirect github.com/goccy/go-json v0.9.11 // indirect
github.com/gocql/gocql v1.2.0 github.com/gocql/gocql v1.2.0
github.com/gokyle/twofactor v1.0.1
github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.6 // indirect github.com/google/go-cmp v0.5.6 // indirect
@ -26,6 +27,7 @@ require (
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.8.0 github.com/stretchr/testify v1.8.0
github.com/tuotoo/qrcode v0.0.0-20220425170535-52ccc2bebf5d
github.com/twilio/twilio-go v1.7.2 github.com/twilio/twilio-go v1.7.2
github.com/vektah/gqlparser/v2 v2.5.1 github.com/vektah/gqlparser/v2 v2.5.1
go.mongodb.org/mongo-driver v1.8.1 go.mongodb.org/mongo-driver v1.8.1

View File

@ -56,6 +56,8 @@ github.com/aws/aws-sdk-go v1.44.298/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
github.com/bits-and-blooms/bitset v1.2.1 h1:M+/hrU9xlMp7t4TyTDQW97d3tRPVuKFC6zBEK16QnXY=
github.com/bits-and-blooms/bitset v1.2.1/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
@ -131,6 +133,8 @@ github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gocql/gocql v1.2.0 h1:TZhsCd7fRuye4VyHr3WCvWwIQaZUmjsqnSIXK9FcVCE= github.com/gocql/gocql v1.2.0 h1:TZhsCd7fRuye4VyHr3WCvWwIQaZUmjsqnSIXK9FcVCE=
github.com/gocql/gocql v1.2.0/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8= github.com/gocql/gocql v1.2.0/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8=
github.com/gokyle/twofactor v1.0.1 h1:uRhvx0S4Hb82RPIDALnf7QxbmPL49LyyaCkJDpWx+Ek=
github.com/gokyle/twofactor v1.0.1/go.mod h1:4gxzH1eaE/F3Pct/sCDNOylP0ClofUO5j4XZN9tKtLE=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
@ -256,6 +260,8 @@ github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ic
github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275 h1:IZycmTpoUtQK3PD60UYBwjaCUHUP7cML494ao9/O8+Q= github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275 h1:IZycmTpoUtQK3PD60UYBwjaCUHUP7cML494ao9/O8+Q=
github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275/go.mod h1:zt6UU74K6Z6oMOYJbJzYpYucqdcQwSMPBEdSvGiaUMw= github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275/go.mod h1:zt6UU74K6Z6oMOYJbJzYpYucqdcQwSMPBEdSvGiaUMw=
github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc= github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc=
github.com/maruel/rs v1.1.0 h1:dh4OceAF5yD06EASOrb+DS358LI4g0B90YApSdjCP6U=
github.com/maruel/rs v1.1.0/go.mod h1:vzwMjzSJJxLIXmU62qHj6O5QRn5kvCKxFrfaFCxBcUY=
github.com/matryer/moq v0.2.7/go.mod h1:kITsx543GOENm48TUAQyJ9+SAvFSr7iGQXPoth/VUBk= github.com/matryer/moq v0.2.7/go.mod h1:kITsx543GOENm48TUAQyJ9+SAvFSr7iGQXPoth/VUBk=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
@ -323,6 +329,8 @@ github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PK
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tuotoo/qrcode v0.0.0-20220425170535-52ccc2bebf5d h1:4x1FeGJRB00cvxnKXnRJDT89fvG/Lzm2ecm0vlr/qDs=
github.com/tuotoo/qrcode v0.0.0-20220425170535-52ccc2bebf5d/go.mod h1:uSELzeIcTceNCgzbKdJuJa0ouCqqtkyzL+6bnA3rM+M=
github.com/twilio/twilio-go v1.7.2 h1:tX38DXbSuDWWIK+tKAE2AJSMR6d8i7lf9ksY8J29VLE= github.com/twilio/twilio-go v1.7.2 h1:tX38DXbSuDWWIK+tKAE2AJSMR6d8i7lf9ksY8J29VLE=
github.com/twilio/twilio-go v1.7.2/go.mod h1:tdnfQ5TjbewoAu4lf9bMsGvfuJ/QU9gYuv9yx3TSIXU= github.com/twilio/twilio-go v1.7.2/go.mod h1:tdnfQ5TjbewoAu4lf9bMsGvfuJ/QU9gYuv9yx3TSIXU=
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
@ -747,5 +755,7 @@ modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.6.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ= modernc.org/z v1.6.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View File

@ -272,6 +272,7 @@ type ComplexityRoot struct {
RevokedTimestamp func(childComplexity int) int RevokedTimestamp func(childComplexity int) int
Roles func(childComplexity int) int Roles func(childComplexity int) int
SignupMethods func(childComplexity int) int SignupMethods func(childComplexity int) int
TotpVerified func(childComplexity int) int
UpdatedAt func(childComplexity int) int UpdatedAt func(childComplexity int) int
} }
@ -1900,6 +1901,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.User.SignupMethods(childComplexity), true return e.complexity.User.SignupMethods(childComplexity), true
case "User.totp_verified":
if e.complexity.User.TotpVerified == nil {
break
}
return e.complexity.User.TotpVerified(childComplexity), true
case "User.updated_at": case "User.updated_at":
if e.complexity.User.UpdatedAt == nil { if e.complexity.User.UpdatedAt == nil {
break break
@ -2317,6 +2325,7 @@ type User {
revoked_timestamp: Int64 revoked_timestamp: Int64
is_multi_factor_auth_enabled: Boolean is_multi_factor_auth_enabled: Boolean
app_data: Map app_data: Map
totp_verified: Boolean
} }
type Users { type Users {
@ -4013,6 +4022,8 @@ func (ec *executionContext) fieldContext_AuthResponse_user(ctx context.Context,
return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field) return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field)
case "app_data": case "app_data":
return ec.fieldContext_User_app_data(ctx, field) return ec.fieldContext_User_app_data(ctx, field)
case "totp_verified":
return ec.fieldContext_User_totp_verified(ctx, field)
} }
return nil, fmt.Errorf("no field named %q was found under type User", field.Name) return nil, fmt.Errorf("no field named %q was found under type User", field.Name)
}, },
@ -7524,6 +7535,8 @@ func (ec *executionContext) fieldContext_InviteMembersResponse_Users(ctx context
return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field) return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field)
case "app_data": case "app_data":
return ec.fieldContext_User_app_data(ctx, field) return ec.fieldContext_User_app_data(ctx, field)
case "totp_verified":
return ec.fieldContext_User_totp_verified(ctx, field)
} }
return nil, fmt.Errorf("no field named %q was found under type User", field.Name) return nil, fmt.Errorf("no field named %q was found under type User", field.Name)
}, },
@ -9357,6 +9370,8 @@ func (ec *executionContext) fieldContext_Mutation__update_user(ctx context.Conte
return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field) return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field)
case "app_data": case "app_data":
return ec.fieldContext_User_app_data(ctx, field) return ec.fieldContext_User_app_data(ctx, field)
case "totp_verified":
return ec.fieldContext_User_totp_verified(ctx, field)
} }
return nil, fmt.Errorf("no field named %q was found under type User", field.Name) return nil, fmt.Errorf("no field named %q was found under type User", field.Name)
}, },
@ -10669,6 +10684,8 @@ func (ec *executionContext) fieldContext_Query_profile(ctx context.Context, fiel
return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field) return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field)
case "app_data": case "app_data":
return ec.fieldContext_User_app_data(ctx, field) return ec.fieldContext_User_app_data(ctx, field)
case "totp_verified":
return ec.fieldContext_User_totp_verified(ctx, field)
} }
return nil, fmt.Errorf("no field named %q was found under type User", field.Name) return nil, fmt.Errorf("no field named %q was found under type User", field.Name)
}, },
@ -10938,6 +10955,8 @@ func (ec *executionContext) fieldContext_Query__user(ctx context.Context, field
return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field) return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field)
case "app_data": case "app_data":
return ec.fieldContext_User_app_data(ctx, field) return ec.fieldContext_User_app_data(ctx, field)
case "totp_verified":
return ec.fieldContext_User_totp_verified(ctx, field)
} }
return nil, fmt.Errorf("no field named %q was found under type User", field.Name) return nil, fmt.Errorf("no field named %q was found under type User", field.Name)
}, },
@ -12846,6 +12865,47 @@ func (ec *executionContext) fieldContext_User_app_data(ctx context.Context, fiel
return fc, nil return fc, nil
} }
func (ec *executionContext) _User_totp_verified(ctx context.Context, field graphql.CollectedField, obj *model.User) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_User_totp_verified(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.TotpVerified, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*bool)
fc.Result = res
return ec.marshalOBoolean2ᚖbool(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_User_totp_verified(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "User",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Boolean does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Users_pagination(ctx context.Context, field graphql.CollectedField, obj *model.Users) (ret graphql.Marshaler) { func (ec *executionContext) _Users_pagination(ctx context.Context, field graphql.CollectedField, obj *model.Users) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Users_pagination(ctx, field) fc, err := ec.fieldContext_Users_pagination(ctx, field)
if err != nil { if err != nil {
@ -12979,6 +13039,8 @@ func (ec *executionContext) fieldContext_Users_users(ctx context.Context, field
return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field) return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field)
case "app_data": case "app_data":
return ec.fieldContext_User_app_data(ctx, field) return ec.fieldContext_User_app_data(ctx, field)
case "totp_verified":
return ec.fieldContext_User_totp_verified(ctx, field)
} }
return nil, fmt.Errorf("no field named %q was found under type User", field.Name) return nil, fmt.Errorf("no field named %q was found under type User", field.Name)
}, },
@ -13194,6 +13256,8 @@ func (ec *executionContext) fieldContext_ValidateSessionResponse_user(ctx contex
return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field) return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field)
case "app_data": case "app_data":
return ec.fieldContext_User_app_data(ctx, field) return ec.fieldContext_User_app_data(ctx, field)
case "totp_verified":
return ec.fieldContext_User_totp_verified(ctx, field)
} }
return nil, fmt.Errorf("no field named %q was found under type User", field.Name) return nil, fmt.Errorf("no field named %q was found under type User", field.Name)
}, },
@ -20329,6 +20393,10 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj
out.Values[i] = ec._User_app_data(ctx, field, obj) out.Values[i] = ec._User_app_data(ctx, field, obj)
case "totp_verified":
out.Values[i] = ec._User_totp_verified(ctx, field, obj)
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }

View File

@ -455,6 +455,7 @@ type User struct {
RevokedTimestamp *int64 `json:"revoked_timestamp"` RevokedTimestamp *int64 `json:"revoked_timestamp"`
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled"` IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled"`
AppData map[string]interface{} `json:"app_data"` AppData map[string]interface{} `json:"app_data"`
TotpVerified *bool `json:"totp_verified"`
} }
type Users struct { type Users struct {

View File

@ -52,6 +52,7 @@ type User {
revoked_timestamp: Int64 revoked_timestamp: Int64
is_multi_factor_auth_enabled: Boolean is_multi_factor_auth_enabled: Boolean
app_data: Map app_data: Map
totp_verified: Boolean
} }
type Users { type Users {

View File

@ -54,6 +54,9 @@ func resendOTPTest(t *testing.T, s TestSetup) {
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, updateRes) assert.NotNil(t, updateRes)
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableMailOTPLogin, false)
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableTOTPLogin, true)
// Resend otp should return error as no initial opt is being sent // Resend otp should return error as no initial opt is being sent
resendOtpRes, err := resolvers.ResendOTPResolver(ctx, model.ResendOTPRequest{ resendOtpRes, err := resolvers.ResendOTPResolver(ctx, model.ResendOTPRequest{
Email: refs.NewStringRef(email), Email: refs.NewStringRef(email),

View File

@ -28,6 +28,8 @@ func revokeAccessTest(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,
}) })
fmt.Println("\n", verifyRes)
fmt.Println("\n", err)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, verifyRes.AccessToken) assert.NotNil(t, verifyRes.AccessToken)

View File

@ -23,6 +23,8 @@ func verificationRequestsTest(t *testing.T, s TestSetup) {
Password: s.TestInfo.Password, Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password,
}) })
fmt.Println("res", res)
fmt.Println("err", err)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, res) assert.NotNil(t, res)
limit := int64(10) limit := int64(10)

View File

@ -54,6 +54,8 @@ func verifyOTPTest(t *testing.T, s TestSetup) {
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, updateProfileRes.Message) assert.NotEmpty(t, updateProfileRes.Message)
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableMailOTPLogin, false)
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableTOTPLogin, true)
// Login should not return error but access token should be empty as otp should have been sent // Login should not return error but access token should be empty as otp should have been sent
loginRes, err = resolvers.LoginResolver(ctx, model.LoginInput{ loginRes, err = resolvers.LoginResolver(ctx, model.LoginInput{

View File

@ -1,18 +1,30 @@
package test package test
import ( import (
"bytes"
"context"
"encoding/base64"
"fmt"
"github.com/authorizerdev/authorizer/server/constants"
"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/memorystore"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/token"
"github.com/gokyle/twofactor"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tuotoo/qrcode"
"strings"
"testing" "testing"
) )
func verifyTOTPTest(t *testing.T, s TestSetup) { func verifyTOTPTest(t *testing.T, s TestSetup) {
t.Helper() t.Helper()
t.Run(`should verify totp`, func(t *testing.T) { t.Run(`should verify totp`, func(t *testing.T) {
_, ctx := createContext(s) req, ctx := createContext(s)
email := "verify_otp." + s.TestInfo.Email email := "verify_totp." + s.TestInfo.Email
cleanData(email)
res, err := resolvers.SignupResolver(ctx, model.SignUpInput{ res, err := resolvers.SignupResolver(ctx, model.SignUpInput{
Email: email, Email: email,
Password: s.TestInfo.Password, Password: s.TestInfo.Password,
@ -28,54 +40,108 @@ func verifyTOTPTest(t *testing.T, s TestSetup) {
}) })
assert.Error(t, err) assert.Error(t, err)
assert.Nil(t, loginRes) assert.Nil(t, loginRes)
_, err = db.Provider.GenerateTotp(ctx, loginRes.User.ID) verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup)
assert.Nil(t, err) assert.Nil(t, err)
//assert.Equal(t, ??, string) assert.Equal(t, email, verificationRequest.Email)
// verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
// Token: verificationRequest.Token, Token: verificationRequest.Token,
// }) })
// assert.Nil(t, err) assert.Nil(t, err)
// assert.NotEqual(t, verifyRes.AccessToken, "", "access token should not be empty") assert.NotEqual(t, verifyRes.AccessToken, "", "access token should not be empty")
//
// // Using access token update profile // Using access token update profile
// s.GinContext.Request.Header.Set("Authorization", "Bearer "+refs.StringValue(verifyRes.AccessToken)) s.GinContext.Request.Header.Set("Authorization", "Bearer "+refs.StringValue(verifyRes.AccessToken))
// ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext) ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext)
// updateProfileRes, err := resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{ updateProfileRes, err := resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{
// IsMultiFactorAuthEnabled: refs.NewBoolRef(true), IsMultiFactorAuthEnabled: refs.NewBoolRef(true),
// }) })
// assert.NoError(t, err) assert.NoError(t, err)
// assert.NotEmpty(t, updateProfileRes.Message) assert.NotEmpty(t, updateProfileRes.Message)
// memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableTOTPLogin, false)
// // Login should not return error but access token should be empty as otp should have been sent memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableMailOTPLogin, true)
// loginRes, err = resolvers.LoginResolver(ctx, model.LoginInput{
// Email: email, // Login should not return error but access token should be empty
// Password: s.TestInfo.Password, loginRes, err = resolvers.LoginResolver(ctx, model.LoginInput{
// }) Email: email,
// assert.NoError(t, err) Password: s.TestInfo.Password,
// assert.NotNil(t, loginRes) })
// assert.Nil(t, loginRes.AccessToken) assert.NoError(t, err)
// assert.NotNil(t, loginRes)
// // Get otp from db assert.NotNil(t, loginRes.TotpBase64url)
// otp, err := db.Provider.GetOTPByEmail(ctx, email) assert.NotNil(t, loginRes.TokenTotp)
// assert.NoError(t, err) assert.Nil(t, loginRes.AccessToken)
// assert.NotEmpty(t, otp.Otp) assert.Equal(t, loginRes.Message, `Proceed to totp screen`)
// // Get user by email
// user, err := db.Provider.GetUserByEmail(ctx, email) // get totp url for validation
// assert.NoError(t, err) pngBytes, err := base64.StdEncoding.DecodeString(*loginRes.TotpBase64url)
// assert.NotNil(t, user) assert.NoError(t, err)
// // Set mfa cookie session qrmatrix, err := qrcode.Decode(bytes.NewReader(pngBytes))
// mfaSession := uuid.NewString() assert.NoError(t, err)
// memorystore.Provider.SetMfaSession(user.ID, mfaSession, time.Now().Add(1*time.Minute).Unix()) tf, label, err := twofactor.FromURL(qrmatrix.Content)
// cookie := fmt.Sprintf("%s=%s;", constants.MfaCookieName+"_session", mfaSession) data := strings.Split(label, ":")
// cookie = strings.TrimSuffix(cookie, ";") assert.NoError(t, err)
// req.Header.Set("Cookie", cookie) assert.Equal(t, email, data[1])
// verifyOtpRes, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{ assert.NotNil(t, tf)
// Email: &email,
// Otp: otp.Otp, code := tf.OTP()
// })
// assert.Nil(t, err) assert.NotEmpty(t, code)
// assert.NotEqual(t, verifyOtpRes.AccessToken, "", "access token should not be empty")
// cleanData(email) valid, err := resolvers.VerifyTotpResolver(ctx, model.VerifyTOTPRequest{
//}) Otp: code,
Token: *loginRes.TokenTotp,
})
accessToken := *valid.AccessToken
assert.NoError(t, err)
assert.NotNil(t, accessToken)
assert.Equal(t, `Logged in successfully`, valid.Message)
assert.NotEmpty(t, accessToken)
claims, err := token.ParseJWTToken(accessToken)
assert.NoError(t, err)
assert.NotEmpty(t, claims)
loginMethod := claims["login_method"]
sessionKey := verifyRes.User.ID
if loginMethod != nil && loginMethod != "" {
sessionKey = loginMethod.(string) + ":" + verifyRes.User.ID
}
sessionToken, err := memorystore.Provider.GetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+claims["nonce"].(string))
assert.NoError(t, err)
assert.NotEmpty(t, sessionToken)
cookie := fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", sessionToken)
cookie = strings.TrimSuffix(cookie, ";")
req.Header.Set("Cookie", cookie)
//logged out
logout, err := resolvers.LogoutResolver(ctx)
assert.NoError(t, err)
assert.Equal(t, logout.Message, `Logged out successfully`)
loginRes, err = resolvers.LoginResolver(ctx, model.LoginInput{
Email: email,
Password: s.TestInfo.Password,
})
assert.NoError(t, err)
assert.NotNil(t, loginRes)
assert.NotNil(t, loginRes.TokenTotp)
assert.Nil(t, loginRes.TotpBase64url)
assert.Nil(t, loginRes.AccessToken)
assert.Equal(t, loginRes.Message, `Proceed to totp screen`)
code = tf.OTP()
assert.NotEmpty(t, code)
valid, err = resolvers.VerifyTotpResolver(ctx, model.VerifyTOTPRequest{
Otp: code,
Token: *loginRes.TokenTotp,
})
assert.NoError(t, err)
assert.NotNil(t, *valid.AccessToken)
assert.Equal(t, `Logged in successfully`, valid.Message)
cleanData(email)
}) })
} }