From 5018462559435d8133a6522908735dcb7c6cd005 Mon Sep 17 00:00:00 2001 From: catusax Date: Thu, 20 Jul 2023 15:11:39 +0800 Subject: [PATCH 01/11] feat: add mfa session to secure otp login --- server/constants/cookie.go | 2 + server/cookie/mfa_session.go | 89 +++++++++++++++++++ .../providers/inmemory/provider.go | 18 ++-- .../memorystore/providers/inmemory/store.go | 18 ++++ .../memorystore/providers/provider_tests.go | 11 +++ server/memorystore/providers/providers.go | 4 + server/memorystore/providers/redis/store.go | 30 +++++++ server/resolvers/login.go | 11 ++- server/resolvers/verify_otp.go | 11 +++ 9 files changed, 185 insertions(+), 9 deletions(-) create mode 100644 server/cookie/mfa_session.go diff --git a/server/constants/cookie.go b/server/constants/cookie.go index 71320a9..6dda49d 100644 --- a/server/constants/cookie.go +++ b/server/constants/cookie.go @@ -5,4 +5,6 @@ const ( AppCookieName = "cookie" // AdminCookieName is the name of the cookie that is used to store the admin token AdminCookieName = "authorizer-admin" + + MfaCookieName = "mfa" ) diff --git a/server/cookie/mfa_session.go b/server/cookie/mfa_session.go new file mode 100644 index 0000000..bf691ce --- /dev/null +++ b/server/cookie/mfa_session.go @@ -0,0 +1,89 @@ +package cookie + +import ( + "net/http" + "net/url" + + log "github.com/sirupsen/logrus" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/memorystore" + "github.com/authorizerdev/authorizer/server/parsers" + "github.com/gin-gonic/gin" +) + +// SetSession sets the session cookie in the response +func SetMfaSession(gc *gin.Context, sessionID string) { + appCookieSecure, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyAppCookieSecure) + if err != nil { + log.Debug("Error while getting app cookie secure from env variable: %v", err) + appCookieSecure = true + } + + secure := appCookieSecure + httpOnly := appCookieSecure + hostname := parsers.GetHost(gc) + host, _ := parsers.GetHostParts(hostname) + domain := parsers.GetDomainName(hostname) + if domain != "localhost" { + domain = "." + domain + } + + // Since app cookie can come from cross site it becomes important to set this in lax mode when insecure. + // Example person using custom UI on their app domain and making request to authorizer domain. + // For more information check: + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite + // https://github.com/gin-gonic/gin/blob/master/context.go#L86 + // TODO add ability to sameSite = none / strict from dashboard + if !appCookieSecure { + gc.SetSameSite(http.SameSiteLaxMode) + } else { + gc.SetSameSite(http.SameSiteNoneMode) + } + // TODO allow configuring from dashboard + age := 60 + + gc.SetCookie(constants.MfaCookieName+"_session", sessionID, age, "/", host, secure, httpOnly) + gc.SetCookie(constants.MfaCookieName+"_session_domain", sessionID, age, "/", domain, secure, httpOnly) +} + +// DeleteSession sets session cookies to expire +func DeleteMfaSession(gc *gin.Context) { + appCookieSecure, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyAppCookieSecure) + if err != nil { + log.Debug("Error while getting app cookie secure from env variable: %v", err) + appCookieSecure = true + } + + secure := appCookieSecure + httpOnly := appCookieSecure + hostname := parsers.GetHost(gc) + host, _ := parsers.GetHostParts(hostname) + domain := parsers.GetDomainName(hostname) + if domain != "localhost" { + domain = "." + domain + } + + gc.SetSameSite(http.SameSiteNoneMode) + gc.SetCookie(constants.MfaCookieName+"_session", "", -1, "/", host, secure, httpOnly) + gc.SetCookie(constants.MfaCookieName+"_session_domain", "", -1, "/", domain, secure, httpOnly) +} + +// GetSession gets the session cookie from context +func GetMfaSession(gc *gin.Context) (string, error) { + var cookie *http.Cookie + var err error + cookie, err = gc.Request.Cookie(constants.MfaCookieName + "_session") + if err != nil { + cookie, err = gc.Request.Cookie(constants.MfaCookieName + "_session_domain") + if err != nil { + return "", err + } + } + + decodedValue, err := url.PathUnescape(cookie.Value) + if err != nil { + return "", err + } + return decodedValue, nil +} diff --git a/server/memorystore/providers/inmemory/provider.go b/server/memorystore/providers/inmemory/provider.go index 952092d..e726502 100644 --- a/server/memorystore/providers/inmemory/provider.go +++ b/server/memorystore/providers/inmemory/provider.go @@ -7,18 +7,20 @@ import ( ) type provider struct { - mutex sync.Mutex - sessionStore *stores.SessionStore - stateStore *stores.StateStore - envStore *stores.EnvStore + mutex sync.Mutex + sessionStore *stores.SessionStore + mfasessionStore *stores.SessionStore + stateStore *stores.StateStore + envStore *stores.EnvStore } // NewInMemoryStore returns a new in-memory store. func NewInMemoryProvider() (*provider, error) { return &provider{ - mutex: sync.Mutex{}, - envStore: stores.NewEnvStore(), - sessionStore: stores.NewSessionStore(), - stateStore: stores.NewStateStore(), + mutex: sync.Mutex{}, + envStore: stores.NewEnvStore(), + sessionStore: stores.NewSessionStore(), + mfasessionStore: stores.NewSessionStore(), + stateStore: stores.NewStateStore(), }, nil } diff --git a/server/memorystore/providers/inmemory/store.go b/server/memorystore/providers/inmemory/store.go index 4a8e8ce..45a7986 100644 --- a/server/memorystore/providers/inmemory/store.go +++ b/server/memorystore/providers/inmemory/store.go @@ -42,6 +42,24 @@ func (c *provider) DeleteSessionForNamespace(namespace string) error { return nil } +func (c *provider) SetMfaSession(email, key string, expiration int64) error { + c.mfasessionStore.Set(email, key, email, expiration) + return nil +} + +func (c *provider) GetMfaSession(email, key string) (string, error) { + val := c.mfasessionStore.Get(email, key) + if val == "" { + return "", fmt.Errorf("Not found") + } + return val, nil +} + +func (c *provider) DeleteMfaSession(email, key string) error { + c.mfasessionStore.Remove(email, key) + return nil +} + // SetState sets the state in the in-memory store. func (c *provider) SetState(key, state string) error { if os.Getenv("ENV") != constants.TestEnv { diff --git a/server/memorystore/providers/provider_tests.go b/server/memorystore/providers/provider_tests.go index e569fe8..47f4dba 100644 --- a/server/memorystore/providers/provider_tests.go +++ b/server/memorystore/providers/provider_tests.go @@ -112,4 +112,15 @@ func ProviderTests(t *testing.T, p Provider) { key, err = p.GetUserSession("auth_provider1:124", "access_token_key") assert.Empty(t, key) assert.Error(t, err) + + err = p.SetMfaSession("auth_provider:123", "session123", time.Now().Add(60*time.Second).Unix()) + assert.NoError(t, err) + key, err = p.GetMfaSession("auth_provider:123", "session123") + assert.NoError(t, err) + assert.Equal(t, "auth_provider:123", key) + err = p.DeleteMfaSession("auth_provider:123", "session123") + assert.NoError(t, err) + key, err = p.GetMfaSession("auth_provider:123", "session123") + assert.Error(t, err) + assert.Empty(t, key) } diff --git a/server/memorystore/providers/providers.go b/server/memorystore/providers/providers.go index db58aa7..ae1249f 100644 --- a/server/memorystore/providers/providers.go +++ b/server/memorystore/providers/providers.go @@ -13,6 +13,10 @@ type Provider interface { // DeleteSessionForNamespace deletes the session for a given namespace DeleteSessionForNamespace(namespace string) error + SetMfaSession(email, key string, expiration int64) error + GetMfaSession(email, key string) (string, error) + DeleteMfaSession(email, key string) error + // SetState sets the login state (key, value form) in the session store SetState(key, state string) error // GetState returns the state from the session store diff --git a/server/memorystore/providers/redis/store.go b/server/memorystore/providers/redis/store.go index 058e95e..d55e741 100644 --- a/server/memorystore/providers/redis/store.go +++ b/server/memorystore/providers/redis/store.go @@ -16,6 +16,8 @@ var ( envStorePrefix = "authorizer_env" ) +const mfaSessionPrefix = "mfa_sess_" + // SetUserSession sets the user session for given user identifier in form recipe:user_id func (c *provider) SetUserSession(userId, key, token string, expiration int64) error { currentTime := time.Now() @@ -91,6 +93,34 @@ func (c *provider) DeleteSessionForNamespace(namespace string) error { return nil } +func (c *provider) SetMfaSession(email, key string, expiration int64) error { + currentTime := time.Now() + expireTime := time.Unix(expiration, 0) + duration := expireTime.Sub(currentTime) + err := c.store.Set(c.ctx, fmt.Sprintf("%s%s:%s", mfaSessionPrefix, email, key), email, duration).Err() + if err != nil { + log.Debug("Error saving user session to redis: ", err) + return err + } + return nil +} + +func (c *provider) GetMfaSession(email, key string) (string, error) { + data, err := c.store.Get(c.ctx, fmt.Sprintf("%s%s:%s", mfaSessionPrefix, email, key)).Result() + if err != nil { + return "", err + } + return data, nil +} + +func (c *provider) DeleteMfaSession(email, key string) error { + if err := c.store.Del(c.ctx, fmt.Sprintf("%s%s:%s", mfaSessionPrefix, email, key)).Err(); err != nil { + log.Debug("Error deleting user session from redis: ", err) + // continue + } + return nil +} + // SetState sets the state in redis store. func (c *provider) SetState(key, value string) error { err := c.store.Set(c.ctx, stateStorePrefix+key, value, 0).Err() diff --git a/server/resolvers/login.go b/server/resolvers/login.go index 28a2289..e05d012 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -113,16 +113,25 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes // If email service is not enabled continue the process in any way if refs.BoolValue(user.IsMultiFactorAuthEnabled) && isEmailServiceEnabled && !isMFADisabled { otp := utils.GenerateOTP() + expires := time.Now().Add(1 * time.Minute).Unix() otpData, err := db.Provider.UpsertOTP(ctx, &models.OTP{ Email: user.Email, Otp: otp, - ExpiresAt: time.Now().Add(1 * time.Minute).Unix(), + ExpiresAt: expires, }) if err != nil { log.Debug("Failed to add otp: ", err) return nil, err } + mfaSession := uuid.NewString() + err = memorystore.Provider.SetMfaSession(params.Email, mfaSession, expires) + if err != nil { + log.Debug("Failed to add mfasession: ", err) + return nil, err + } + cookie.SetMfaSession(gc, mfaSession) + go func() { // exec it as go routine so that we can reduce the api latency go email.SendEmail([]string{params.Email}, constants.VerificationTypeOTP, map[string]interface{}{ diff --git a/server/resolvers/verify_otp.go b/server/resolvers/verify_otp.go index 80080d9..aac55e2 100644 --- a/server/resolvers/verify_otp.go +++ b/server/resolvers/verify_otp.go @@ -28,6 +28,17 @@ func VerifyOtpResolver(ctx context.Context, params model.VerifyOTPRequest) (*mod return res, err } + mfaSession, err := cookie.GetMfaSession(gc) + if err != nil { + log.Debug("Failed to get otp request by email: ", err) + return res, fmt.Errorf(`invalid session: %s`, err.Error()) + } + + if _, err := memorystore.Provider.GetMfaSession(params.Email, mfaSession); err != nil { + log.Debug("Failed to get mfa session: ", err) + return res, fmt.Errorf(`invalid session: %s`, err.Error()) + } + otp, err := db.Provider.GetOTPByEmail(ctx, params.Email) if err != nil { log.Debug("Failed to get otp request by email: ", err) From e7652db89cf8d524a29df482979a1247db347e4f Mon Sep 17 00:00:00 2001 From: catusax <11882183+catusax@users.noreply.github.com> Date: Sun, 23 Jul 2023 13:02:14 +0800 Subject: [PATCH 02/11] add comments --- server/constants/cookie.go | 2 +- server/cookie/mfa_session.go | 6 +++--- server/memorystore/providers/inmemory/store.go | 3 +++ server/memorystore/providers/providers.go | 4 +++- server/memorystore/providers/redis/store.go | 3 +++ 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/server/constants/cookie.go b/server/constants/cookie.go index 6dda49d..8f6399b 100644 --- a/server/constants/cookie.go +++ b/server/constants/cookie.go @@ -5,6 +5,6 @@ const ( AppCookieName = "cookie" // AdminCookieName is the name of the cookie that is used to store the admin token AdminCookieName = "authorizer-admin" - + // MfaCookieName is the name of the cookie that is used to store the mfa session MfaCookieName = "mfa" ) diff --git a/server/cookie/mfa_session.go b/server/cookie/mfa_session.go index bf691ce..3fdcaac 100644 --- a/server/cookie/mfa_session.go +++ b/server/cookie/mfa_session.go @@ -12,7 +12,7 @@ import ( "github.com/gin-gonic/gin" ) -// SetSession sets the session cookie in the response +// SetMfaSession sets the mfa session cookie in the response func SetMfaSession(gc *gin.Context, sessionID string) { appCookieSecure, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyAppCookieSecure) if err != nil { @@ -47,7 +47,7 @@ func SetMfaSession(gc *gin.Context, sessionID string) { gc.SetCookie(constants.MfaCookieName+"_session_domain", sessionID, age, "/", domain, secure, httpOnly) } -// DeleteSession sets session cookies to expire +// DeleteMfaSession deletes the mfa session cookies to expire func DeleteMfaSession(gc *gin.Context) { appCookieSecure, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyAppCookieSecure) if err != nil { @@ -69,7 +69,7 @@ func DeleteMfaSession(gc *gin.Context) { gc.SetCookie(constants.MfaCookieName+"_session_domain", "", -1, "/", domain, secure, httpOnly) } -// GetSession gets the session cookie from context +// GetMfaSession gets the mfa session cookie from context func GetMfaSession(gc *gin.Context) (string, error) { var cookie *http.Cookie var err error diff --git a/server/memorystore/providers/inmemory/store.go b/server/memorystore/providers/inmemory/store.go index 45a7986..d03a9df 100644 --- a/server/memorystore/providers/inmemory/store.go +++ b/server/memorystore/providers/inmemory/store.go @@ -42,11 +42,13 @@ func (c *provider) DeleteSessionForNamespace(namespace string) error { return nil } +// SetMfaSession sets the mfa session with key and value of email func (c *provider) SetMfaSession(email, key string, expiration int64) error { c.mfasessionStore.Set(email, key, email, expiration) return nil } +// GetMfaSession returns value of given mfa session func (c *provider) GetMfaSession(email, key string) (string, error) { val := c.mfasessionStore.Get(email, key) if val == "" { @@ -55,6 +57,7 @@ func (c *provider) GetMfaSession(email, key string) (string, error) { return val, nil } +// DeleteMfaSession deletes given mfa session from in-memory store. func (c *provider) DeleteMfaSession(email, key string) error { c.mfasessionStore.Remove(email, key) return nil diff --git a/server/memorystore/providers/providers.go b/server/memorystore/providers/providers.go index ae1249f..6b3eba0 100644 --- a/server/memorystore/providers/providers.go +++ b/server/memorystore/providers/providers.go @@ -12,9 +12,11 @@ type Provider interface { DeleteAllUserSessions(userId string) error // DeleteSessionForNamespace deletes the session for a given namespace DeleteSessionForNamespace(namespace string) error - + // SetMfaSession sets the mfa session with key and value of email SetMfaSession(email, key string, expiration int64) error + // GetMfaSession returns value of given mfa session GetMfaSession(email, key string) (string, error) + // DeleteMfaSession deletes given mfa session from in-memory store. DeleteMfaSession(email, key string) error // SetState sets the login state (key, value form) in the session store diff --git a/server/memorystore/providers/redis/store.go b/server/memorystore/providers/redis/store.go index d55e741..63e3e37 100644 --- a/server/memorystore/providers/redis/store.go +++ b/server/memorystore/providers/redis/store.go @@ -93,6 +93,7 @@ func (c *provider) DeleteSessionForNamespace(namespace string) error { return nil } +// SetMfaSession sets the mfa session with key and value of email func (c *provider) SetMfaSession(email, key string, expiration int64) error { currentTime := time.Now() expireTime := time.Unix(expiration, 0) @@ -105,6 +106,7 @@ func (c *provider) SetMfaSession(email, key string, expiration int64) error { return nil } + // GetMfaSession returns value of given mfa session func (c *provider) GetMfaSession(email, key string) (string, error) { data, err := c.store.Get(c.ctx, fmt.Sprintf("%s%s:%s", mfaSessionPrefix, email, key)).Result() if err != nil { @@ -113,6 +115,7 @@ func (c *provider) GetMfaSession(email, key string) (string, error) { return data, nil } +// DeleteMfaSession deletes given mfa session from in-memory store. func (c *provider) DeleteMfaSession(email, key string) error { if err := c.store.Del(c.ctx, fmt.Sprintf("%s%s:%s", mfaSessionPrefix, email, key)).Err(); err != nil { log.Debug("Error deleting user session from redis: ", err) From 85ca0f09bfdc4efaa6ef225980a909a1bced582b Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 12 Jul 2023 11:24:13 +0530 Subject: [PATCH 03/11] [draft] Move sms verificaiton to otp models --- server/db/models/otp.go | 22 +++++++++++------ server/db/models/sms_verification_requests.go | 3 ++- server/db/providers/arangodb/provider.go | 19 ++++++++++++++- server/db/providers/couchbase/shared.go | 5 ---- server/db/providers/mongodb/otp.go | 13 ++++++++++ server/db/providers/mongodb/provider.go | 6 +++++ .../mongodb/sms_verification_requests.go | 7 ++---- server/db/providers/providers.go | 2 ++ server/db/providers/sql/otp.go | 24 +++++++++++++++---- 9 files changed, 78 insertions(+), 23 deletions(-) diff --git a/server/db/models/otp.go b/server/db/models/otp.go index ac9732b..b0e02ee 100644 --- a/server/db/models/otp.go +++ b/server/db/models/otp.go @@ -1,14 +1,22 @@ package models +const ( + // FieldName email is the field name for email + FieldNameEmail = "email" + // FieldNamePhoneNumber is the field name for phone number + FieldNamePhoneNumber = "phone_number" +) + // OTP model for database type OTP struct { - Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb - ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"` - Email string `gorm:"unique" json:"email" bson:"email" cql:"email" dynamo:"email" index:"email,hash"` - Otp string `json:"otp" bson:"otp" cql:"otp" dynamo:"otp"` - ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at" dynamo:"expires_at"` - CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"` - UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"` + Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb + ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"` + Email string `gorm:"unique" json:"email" bson:"email" cql:"email" dynamo:"email" index:"email,hash"` + PhoneNumber string `gorm:"unique" json:"phone_number" bson:"phone_number" cql:"phone_number" dynamo:"phone_number" index:"phone_number,hash"` + Otp string `json:"otp" bson:"otp" cql:"otp" dynamo:"otp"` + ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at" dynamo:"expires_at"` + CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"` + UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"` } type Paging struct { diff --git a/server/db/models/sms_verification_requests.go b/server/db/models/sms_verification_requests.go index a938298..e14da4c 100644 --- a/server/db/models/sms_verification_requests.go +++ b/server/db/models/sms_verification_requests.go @@ -1,7 +1,8 @@ package models -// SMS verification requests model for database +// SMSVerificationRequest is SMS verification requests model for database type SMSVerificationRequest struct { + Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"` PhoneNumber string `gorm:"unique" json:"phone_number" bson:"phone_number" cql:"phone_number" dynamo:"phone_number" index:"phone_number,hash"` Code string `json:"code" bson:"code" cql:"code" dynamo:"code"` diff --git a/server/db/providers/arangodb/provider.go b/server/db/providers/arangodb/provider.go index 5488428..88fc6e4 100644 --- a/server/db/providers/arangodb/provider.go +++ b/server/db/providers/arangodb/provider.go @@ -225,7 +225,6 @@ func NewProvider() (*provider, error) { return nil, err } } - otpCollection, err := arangodb.Collection(ctx, models.Collections.OTP) if err != nil { return nil, err @@ -235,6 +234,24 @@ func NewProvider() (*provider, error) { Sparse: true, }) + smsVerificationCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.SMSVerificationRequest) + if err != nil { + return nil, err + } + if !smsVerificationCollectionExists { + _, err = arangodb.CreateCollection(ctx, models.Collections.SMSVerificationRequest, nil) + if err != nil { + return nil, err + } + } + smsVerificationCollection, err := arangodb.Collection(ctx, models.Collections.SMSVerificationRequest) + if err != nil { + return nil, err + } + smsVerificationCollection.EnsureHashIndex(ctx, []string{"phone_number"}, &arangoDriver.EnsureHashIndexOptions{ + Unique: true, + Sparse: true, + }) return &provider{ db: arangodb, }, err diff --git a/server/db/providers/couchbase/shared.go b/server/db/providers/couchbase/shared.go index 104ad02..d640f68 100644 --- a/server/db/providers/couchbase/shared.go +++ b/server/db/providers/couchbase/shared.go @@ -11,24 +11,19 @@ import ( func GetSetFields(webhookMap map[string]interface{}) (string, map[string]interface{}) { params := make(map[string]interface{}, 1) - updateFields := "" - for key, value := range webhookMap { if key == "_id" { continue } - if key == "_key" { continue } - if value == nil { updateFields += fmt.Sprintf("%s=$%s,", key, key) params[key] = "null" continue } - valueType := reflect.TypeOf(value) if valueType.Name() == "string" { updateFields += fmt.Sprintf("%s = $%s, ", key, key) diff --git a/server/db/providers/mongodb/otp.go b/server/db/providers/mongodb/otp.go index d6ff2df..8726f5a 100644 --- a/server/db/providers/mongodb/otp.go +++ b/server/db/providers/mongodb/otp.go @@ -2,6 +2,7 @@ package mongodb import ( "context" + "errors" "time" "github.com/authorizerdev/authorizer/server/db/models" @@ -12,6 +13,18 @@ import ( // UpsertOTP to add or update otp func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) { + // check if email or phone number is present + if otpParam.Email == "" && otpParam.PhoneNumber == "" { + return nil, errors.New("email or phone_number is required") + } + // check if email or phone number is present + if otpParam.Email == "" && otpParam.PhoneNumber == "" { + return nil, errors.New("email or phone_number is required") + } + uniqueField := models.FieldNameEmail + if otp.Email == "" && otp.PhoneNumber != "" { + uniqueField = models.FieldNamePhoneNumber + } otp, _ := p.GetOTPByEmail(ctx, otpParam.Email) shouldCreate := false if otp == nil { diff --git a/server/db/providers/mongodb/provider.go b/server/db/providers/mongodb/provider.go index fd4a0b2..1922b0e 100644 --- a/server/db/providers/mongodb/provider.go +++ b/server/db/providers/mongodb/provider.go @@ -118,6 +118,12 @@ func NewProvider() (*provider, error) { Options: options.Index().SetUnique(true).SetSparse(true), }, }, options.CreateIndexes()) + otpCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{ + { + Keys: bson.M{"phone_number": 1}, + Options: options.Index().SetUnique(true).SetSparse(true), + }, + }, options.CreateIndexes()) mongodb.CreateCollection(ctx, models.Collections.SMSVerificationRequest, options.CreateCollection()) smsCollection := mongodb.Collection(models.Collections.SMSVerificationRequest, options.Collection()) diff --git a/server/db/providers/mongodb/sms_verification_requests.go b/server/db/providers/mongodb/sms_verification_requests.go index 1183df1..0ee0e6c 100644 --- a/server/db/providers/mongodb/sms_verification_requests.go +++ b/server/db/providers/mongodb/sms_verification_requests.go @@ -12,10 +12,7 @@ import ( // UpsertSMSRequest adds/updates SMS verification request func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) { - smsVerificationRequest, err := p.GetCodeByPhone(ctx, smsRequest.PhoneNumber) - if err != nil { - return nil, err - } + smsVerificationRequest, _ := p.GetCodeByPhone(ctx, smsRequest.PhoneNumber) // Boolean to check if we should create a new record or update the existing one shouldCreate := false if smsVerificationRequest == nil { @@ -29,7 +26,7 @@ func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSV } shouldCreate = true } - + var err error smsVerificationRequest.UpdatedAt = time.Now().Unix() smsRequestCollection := p.db.Collection(models.Collections.SMSVerificationRequest, options.Collection()) if shouldCreate { diff --git a/server/db/providers/providers.go b/server/db/providers/providers.go index 31c021c..c6065c1 100644 --- a/server/db/providers/providers.go +++ b/server/db/providers/providers.go @@ -82,6 +82,8 @@ type Provider interface { UpsertOTP(ctx context.Context, otp *models.OTP) (*models.OTP, error) // GetOTPByEmail to get otp for a given email address GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) + // GetOTPByPhoneNumber to get otp for a given phone number + GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) // DeleteOTP to delete otp DeleteOTP(ctx context.Context, otp *models.OTP) error diff --git a/server/db/providers/sql/otp.go b/server/db/providers/sql/otp.go index 9aabcab..5503a7d 100644 --- a/server/db/providers/sql/otp.go +++ b/server/db/providers/sql/otp.go @@ -2,6 +2,7 @@ package sql import ( "context" + "errors" "time" "github.com/authorizerdev/authorizer/server/db/models" @@ -14,13 +15,19 @@ func (p *provider) UpsertOTP(ctx context.Context, otp *models.OTP) (*models.OTP, if otp.ID == "" { otp.ID = uuid.New().String() } - + // check if email or phone number is present + if otp.Email == "" && otp.PhoneNumber == "" { + return nil, errors.New("email or phone_number is required") + } + uniqueField := models.FieldNameEmail + if otp.Email == "" && otp.PhoneNumber != "" { + uniqueField = models.FieldNamePhoneNumber + } otp.Key = otp.ID otp.CreatedAt = time.Now().Unix() otp.UpdatedAt = time.Now().Unix() - res := p.db.Clauses(clause.OnConflict{ - Columns: []clause.Column{{Name: "email"}}, + Columns: []clause.Column{{Name: uniqueField}}, DoUpdates: clause.AssignmentColumns([]string{"otp", "expires_at", "updated_at"}), }).Create(&otp) if res.Error != nil { @@ -33,7 +40,6 @@ func (p *provider) UpsertOTP(ctx context.Context, otp *models.OTP) (*models.OTP, // GetOTPByEmail to get otp for a given email address func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) { var otp models.OTP - result := p.db.Where("email = ?", emailAddress).First(&otp) if result.Error != nil { return nil, result.Error @@ -41,6 +47,16 @@ func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*mod return &otp, nil } +// GetOTPByPhoneNumber to get otp for a given phone number +func (p *provider) GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) { + var otp models.OTP + result := p.db.Where("phone_number = ?", phoneNumber).First(&otp) + if result.Error != nil { + return nil, result.Error + } + return &otp, nil +} + // DeleteOTP to delete otp func (p *provider) DeleteOTP(ctx context.Context, otp *models.OTP) error { result := p.db.Delete(&models.OTP{ From 2f849b8f0c268bd089010fdcad4694adab57ae8c Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Thu, 13 Jul 2023 11:39:22 +0530 Subject: [PATCH 04/11] Refactor code for otp --- server/constants/auth_methods.go | 2 + server/db/models/sms_verification_requests.go | 12 -- server/db/providers/arangodb/otp.go | 59 +++++- server/db/providers/arangodb/provider.go | 17 +- .../arangodb/sms_verification_requests.go | 22 --- server/db/providers/cassandradb/otp.go | 46 ++++- server/db/providers/cassandradb/provider.go | 6 +- .../cassandradb/sms_verification_requests.go | 22 --- server/db/providers/couchbase/otp.go | 51 ++++-- server/db/providers/couchbase/provider.go | 4 + .../couchbase/sms_verification_requests.go | 22 --- server/db/providers/dynamodb/otp.go | 54 ++++-- .../dynamodb/sms_verification_requests.go | 22 --- server/db/providers/mongodb/otp.go | 38 ++-- server/db/providers/mongodb/provider.go | 9 - .../mongodb/sms_verification_requests.go | 66 ------- server/db/providers/provider_template/otp.go | 5 + .../sms_verification_requests.go | 22 --- server/db/providers/providers.go | 7 - server/db/providers/sql/provider.go | 2 +- .../sql/sms_verification_requests.go | 49 ----- server/graph/generated/generated.go | 173 ++---------------- server/graph/model/models_gen.go | 12 +- server/graph/schema.graphqls | 10 +- server/graph/schema.resolvers.go | 5 - server/resolvers/mobile_signup.go | 22 +-- server/resolvers/verify_mobile.go | 62 ------- server/resolvers/verify_otp.go | 52 +++--- server/test/mobile_login_test.go | 14 +- server/test/resend_otp_test.go | 4 +- server/test/resolvers_test.go | 1 - server/test/verify_mobile_test.go | 79 -------- server/test/verify_otp_test.go | 2 +- 33 files changed, 277 insertions(+), 696 deletions(-) delete mode 100644 server/db/models/sms_verification_requests.go delete mode 100644 server/db/providers/arangodb/sms_verification_requests.go delete mode 100644 server/db/providers/cassandradb/sms_verification_requests.go delete mode 100644 server/db/providers/couchbase/sms_verification_requests.go delete mode 100644 server/db/providers/dynamodb/sms_verification_requests.go delete mode 100644 server/db/providers/mongodb/sms_verification_requests.go delete mode 100644 server/db/providers/provider_template/sms_verification_requests.go delete mode 100644 server/db/providers/sql/sms_verification_requests.go delete mode 100644 server/resolvers/verify_mobile.go delete mode 100644 server/test/verify_mobile_test.go diff --git a/server/constants/auth_methods.go b/server/constants/auth_methods.go index 9e7b9fa..dbe5175 100644 --- a/server/constants/auth_methods.go +++ b/server/constants/auth_methods.go @@ -7,6 +7,8 @@ const ( AuthRecipeMethodMobileBasicAuth = "mobile_basic_auth" // AuthRecipeMethodMagicLinkLogin is the magic_link_login auth method AuthRecipeMethodMagicLinkLogin = "magic_link_login" + // AuthRecipeMethodMobileOTP is the mobile_otp auth method + AuthRecipeMethodMobileOTP = "mobile_otp" // AuthRecipeMethodGoogle is the google auth method AuthRecipeMethodGoogle = "google" // AuthRecipeMethodGithub is the github auth method diff --git a/server/db/models/sms_verification_requests.go b/server/db/models/sms_verification_requests.go deleted file mode 100644 index e14da4c..0000000 --- a/server/db/models/sms_verification_requests.go +++ /dev/null @@ -1,12 +0,0 @@ -package models - -// SMSVerificationRequest is SMS verification requests model for database -type SMSVerificationRequest struct { - Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb - ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"` - PhoneNumber string `gorm:"unique" json:"phone_number" bson:"phone_number" cql:"phone_number" dynamo:"phone_number" index:"phone_number,hash"` - Code string `json:"code" bson:"code" cql:"code" dynamo:"code"` - CodeExpiresAt int64 `json:"code_expires_at" bson:"code_expires_at" cql:"code_expires_at" dynamo:"code_expires_at"` - CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"` - UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"` -} diff --git a/server/db/providers/arangodb/otp.go b/server/db/providers/arangodb/otp.go index 29f265a..8fef639 100644 --- a/server/db/providers/arangodb/otp.go +++ b/server/db/providers/arangodb/otp.go @@ -2,6 +2,7 @@ package arangodb import ( "context" + "errors" "fmt" "time" @@ -12,17 +13,31 @@ import ( // UpsertOTP to add or update otp func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) { - otp, _ := p.GetOTPByEmail(ctx, otpParam.Email) + // check if email or phone number is present + if otpParam.Email == "" && otpParam.PhoneNumber == "" { + return nil, errors.New("email or phone_number is required") + } + uniqueField := models.FieldNameEmail + if otpParam.Email == "" && otpParam.PhoneNumber != "" { + uniqueField = models.FieldNamePhoneNumber + } + var otp *models.OTP + if uniqueField == models.FieldNameEmail { + otp, _ = p.GetOTPByEmail(ctx, otpParam.Email) + } else { + otp, _ = p.GetOTPByPhoneNumber(ctx, otpParam.PhoneNumber) + } shouldCreate := false if otp == nil { id := uuid.NewString() otp = &models.OTP{ - ID: id, - Key: id, - Otp: otpParam.Otp, - Email: otpParam.Email, - ExpiresAt: otpParam.ExpiresAt, - CreatedAt: time.Now().Unix(), + ID: id, + Key: id, + Otp: otpParam.Otp, + Email: otpParam.Email, + PhoneNumber: otpParam.PhoneNumber, + ExpiresAt: otpParam.ExpiresAt, + CreatedAt: time.Now().Unix(), } shouldCreate = true } else { @@ -67,7 +82,35 @@ func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*mod for { if !cursor.HasMore() { if otp.Key == "" { - return nil, fmt.Errorf("email template not found") + return nil, fmt.Errorf("otp with given email not found") + } + break + } + _, err := cursor.ReadDocument(ctx, &otp) + if err != nil { + return nil, err + } + } + + return &otp, nil +} + +// GetOTPByPhoneNumber to get otp for a given phone number +func (p *provider) GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) { + var otp models.OTP + query := fmt.Sprintf("FOR d in %s FILTER d.phone_number == @phone_number RETURN d", models.Collections.OTP) + bindVars := map[string]interface{}{ + "phone_number": phoneNumber, + } + cursor, err := p.db.Query(ctx, query, bindVars) + if err != nil { + return nil, err + } + defer cursor.Close() + for { + if !cursor.HasMore() { + if otp.Key == "" { + return nil, fmt.Errorf("otp with given phone_number not found") } break } diff --git a/server/db/providers/arangodb/provider.go b/server/db/providers/arangodb/provider.go index 88fc6e4..2dd7b28 100644 --- a/server/db/providers/arangodb/provider.go +++ b/server/db/providers/arangodb/provider.go @@ -233,22 +233,7 @@ func NewProvider() (*provider, error) { Unique: true, Sparse: true, }) - - smsVerificationCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.SMSVerificationRequest) - if err != nil { - return nil, err - } - if !smsVerificationCollectionExists { - _, err = arangodb.CreateCollection(ctx, models.Collections.SMSVerificationRequest, nil) - if err != nil { - return nil, err - } - } - smsVerificationCollection, err := arangodb.Collection(ctx, models.Collections.SMSVerificationRequest) - if err != nil { - return nil, err - } - smsVerificationCollection.EnsureHashIndex(ctx, []string{"phone_number"}, &arangoDriver.EnsureHashIndexOptions{ + otpCollection.EnsureHashIndex(ctx, []string{"phone_number"}, &arangoDriver.EnsureHashIndexOptions{ Unique: true, Sparse: true, }) diff --git a/server/db/providers/arangodb/sms_verification_requests.go b/server/db/providers/arangodb/sms_verification_requests.go deleted file mode 100644 index 6eb97b8..0000000 --- a/server/db/providers/arangodb/sms_verification_requests.go +++ /dev/null @@ -1,22 +0,0 @@ -package arangodb - -import ( - "context" - - "github.com/authorizerdev/authorizer/server/db/models" -) - -// UpsertSMSRequest adds/updates SMS verification request -func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) { - return nil, nil -} - -// GetCodeByPhone to get code for a given phone number -func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) { - return nil, nil -} - -// DeleteSMSRequest to delete SMS verification request -func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error { - return nil -} diff --git a/server/db/providers/cassandradb/otp.go b/server/db/providers/cassandradb/otp.go index bfe481d..e453242 100644 --- a/server/db/providers/cassandradb/otp.go +++ b/server/db/providers/cassandradb/otp.go @@ -2,6 +2,7 @@ package cassandradb import ( "context" + "errors" "fmt" "time" @@ -12,17 +13,31 @@ import ( // UpsertOTP to add or update otp func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) { - otp, _ := p.GetOTPByEmail(ctx, otpParam.Email) + // check if email or phone number is present + if otpParam.Email == "" && otpParam.PhoneNumber == "" { + return nil, errors.New("email or phone_number is required") + } + uniqueField := models.FieldNameEmail + if otpParam.Email == "" && otpParam.PhoneNumber != "" { + uniqueField = models.FieldNamePhoneNumber + } + var otp *models.OTP + if uniqueField == models.FieldNameEmail { + otp, _ = p.GetOTPByEmail(ctx, otpParam.Email) + } else { + otp, _ = p.GetOTPByPhoneNumber(ctx, otpParam.PhoneNumber) + } shouldCreate := false if otp == nil { shouldCreate = true otp = &models.OTP{ - ID: uuid.NewString(), - Otp: otpParam.Otp, - Email: otpParam.Email, - ExpiresAt: otpParam.ExpiresAt, - CreatedAt: time.Now().Unix(), - UpdatedAt: time.Now().Unix(), + ID: uuid.NewString(), + Otp: otpParam.Otp, + Email: otpParam.Email, + PhoneNumber: otpParam.PhoneNumber, + ExpiresAt: otpParam.ExpiresAt, + CreatedAt: time.Now().Unix(), + UpdatedAt: time.Now().Unix(), } } else { otp.Otp = otpParam.Otp @@ -32,7 +47,7 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models otp.UpdatedAt = time.Now().Unix() query := "" if shouldCreate { - query = fmt.Sprintf(`INSERT INTO %s (id, email, otp, expires_at, created_at, updated_at) VALUES ('%s', '%s', '%s', %d, %d, %d)`, KeySpace+"."+models.Collections.OTP, otp.ID, otp.Email, otp.Otp, otp.ExpiresAt, otp.CreatedAt, otp.UpdatedAt) + query = fmt.Sprintf(`INSERT INTO %s (id, email, phone_number, otp, expires_at, created_at, updated_at) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d)`, KeySpace+"."+models.Collections.OTP, otp.ID, otp.Email, otp.PhoneNumber, otp.Otp, otp.ExpiresAt, otp.CreatedAt, otp.UpdatedAt) } else { query = fmt.Sprintf(`UPDATE %s SET otp = '%s', expires_at = %d, updated_at = %d WHERE id = '%s'`, KeySpace+"."+models.Collections.OTP, otp.Otp, otp.ExpiresAt, otp.UpdatedAt, otp.ID) } @@ -48,8 +63,19 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models // GetOTPByEmail to get otp for a given email address func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) { var otp models.OTP - query := fmt.Sprintf(`SELECT id, email, otp, expires_at, created_at, updated_at FROM %s WHERE email = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.OTP, emailAddress) - err := p.db.Query(query).Consistency(gocql.One).Scan(&otp.ID, &otp.Email, &otp.Otp, &otp.ExpiresAt, &otp.CreatedAt, &otp.UpdatedAt) + query := fmt.Sprintf(`SELECT id, email, phone_number, otp, expires_at, created_at, updated_at FROM %s WHERE email = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.OTP, emailAddress) + err := p.db.Query(query).Consistency(gocql.One).Scan(&otp.ID, &otp.Email, &otp.PhoneNumber, &otp.Otp, &otp.ExpiresAt, &otp.CreatedAt, &otp.UpdatedAt) + if err != nil { + return nil, err + } + return &otp, nil +} + +// GetOTPByPhoneNumber to get otp for a given phone number +func (p *provider) GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) { + var otp models.OTP + query := fmt.Sprintf(`SELECT id, email, phone_number, otp, expires_at, created_at, updated_at FROM %s WHERE phone_number = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.OTP, phoneNumber) + err := p.db.Query(query).Consistency(gocql.One).Scan(&otp.ID, &otp.Email, &otp.PhoneNumber, &otp.Otp, &otp.ExpiresAt, &otp.CreatedAt, &otp.UpdatedAt) if err != nil { return nil, err } diff --git a/server/db/providers/cassandradb/provider.go b/server/db/providers/cassandradb/provider.go index 1d8fa49..1c989be 100644 --- a/server/db/providers/cassandradb/provider.go +++ b/server/db/providers/cassandradb/provider.go @@ -254,7 +254,11 @@ func NewProvider() (*provider, error) { if err != nil { return nil, err } - + otpIndexQueryPhoneNumber := fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_otp_phone_number ON %s.%s (phone_number)", KeySpace, models.Collections.OTP) + err = session.Query(otpIndexQueryPhoneNumber).Exec() + if err != nil { + return nil, err + } return &provider{ db: session, }, err diff --git a/server/db/providers/cassandradb/sms_verification_requests.go b/server/db/providers/cassandradb/sms_verification_requests.go deleted file mode 100644 index ddead30..0000000 --- a/server/db/providers/cassandradb/sms_verification_requests.go +++ /dev/null @@ -1,22 +0,0 @@ -package cassandradb - -import ( - "context" - - "github.com/authorizerdev/authorizer/server/db/models" -) - -// UpsertSMSRequest adds/updates SMS verification request -func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) { - return nil, nil -} - -// GetCodeByPhone to get code for a given phone number -func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) { - return nil, nil -} - -// DeleteSMSRequest to delete SMS verification request -func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error { - return nil -} diff --git a/server/db/providers/couchbase/otp.go b/server/db/providers/couchbase/otp.go index cdcfde9..1fe6532 100644 --- a/server/db/providers/couchbase/otp.go +++ b/server/db/providers/couchbase/otp.go @@ -2,6 +2,7 @@ package couchbase import ( "context" + "errors" "fmt" "time" @@ -12,24 +13,36 @@ import ( // UpsertOTP to add or update otp func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) { - otp, _ := p.GetOTPByEmail(ctx, otpParam.Email) - + // check if email or phone number is present + if otpParam.Email == "" && otpParam.PhoneNumber == "" { + return nil, errors.New("email or phone_number is required") + } + uniqueField := models.FieldNameEmail + if otpParam.Email == "" && otpParam.PhoneNumber != "" { + uniqueField = models.FieldNamePhoneNumber + } + var otp *models.OTP + if uniqueField == models.FieldNameEmail { + otp, _ = p.GetOTPByEmail(ctx, otpParam.Email) + } else { + otp, _ = p.GetOTPByPhoneNumber(ctx, otpParam.PhoneNumber) + } shouldCreate := false if otp == nil { shouldCreate = true otp = &models.OTP{ - ID: uuid.NewString(), - Otp: otpParam.Otp, - Email: otpParam.Email, - ExpiresAt: otpParam.ExpiresAt, - CreatedAt: time.Now().Unix(), - UpdatedAt: time.Now().Unix(), + ID: uuid.NewString(), + Otp: otpParam.Otp, + Email: otpParam.Email, + PhoneNumber: otpParam.PhoneNumber, + ExpiresAt: otpParam.ExpiresAt, + CreatedAt: time.Now().Unix(), + UpdatedAt: time.Now().Unix(), } } else { otp.Otp = otpParam.Otp otp.ExpiresAt = otpParam.ExpiresAt } - otp.UpdatedAt = time.Now().Unix() if shouldCreate { insertOpt := gocb.InsertOptions{ @@ -54,7 +67,7 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models // GetOTPByEmail to get otp for a given email address func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) { otp := models.OTP{} - query := fmt.Sprintf(`SELECT _id, email, otp, expires_at, created_at, updated_at FROM %s.%s WHERE email = $1 LIMIT 1`, p.scopeName, models.Collections.OTP) + query := fmt.Sprintf(`SELECT _id, email, phone_number, otp, expires_at, created_at, updated_at FROM %s.%s WHERE email = $1 LIMIT 1`, p.scopeName, models.Collections.OTP) q, err := p.db.Query(query, &gocb.QueryOptions{ ScanConsistency: gocb.QueryScanConsistencyRequestPlus, PositionalParameters: []interface{}{emailAddress}, @@ -63,11 +76,27 @@ func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*mod return nil, err } err = q.One(&otp) - if err != nil { return nil, err } + return &otp, nil +} +// GetOTPByPhoneNumber to get otp for a given phone number +func (p *provider) GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) { + otp := models.OTP{} + query := fmt.Sprintf(`SELECT _id, email, phone_number, otp, expires_at, created_at, updated_at FROM %s.%s WHERE phone_number = $1 LIMIT 1`, p.scopeName, models.Collections.OTP) + q, err := p.db.Query(query, &gocb.QueryOptions{ + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + PositionalParameters: []interface{}{phoneNumber}, + }) + if err != nil { + return nil, err + } + err = q.One(&otp) + if err != nil { + return nil, err + } return &otp, nil } diff --git a/server/db/providers/couchbase/provider.go b/server/db/providers/couchbase/provider.go index c5d5404..723e47a 100644 --- a/server/db/providers/couchbase/provider.go +++ b/server/db/providers/couchbase/provider.go @@ -166,5 +166,9 @@ func GetIndex(scopeName string) map[string][]string { otpIndex1 := fmt.Sprintf("CREATE INDEX OTPEmailIndex ON %s.%s(email)", scopeName, models.Collections.OTP) indices[models.Collections.OTP] = []string{otpIndex1} + // OTP index + otpIndex2 := fmt.Sprintf("CREATE INDEX OTPPhoneNumberIndex ON %s.%s(phone_number)", scopeName, models.Collections.OTP) + indices[models.Collections.OTP] = []string{otpIndex2} + return indices } diff --git a/server/db/providers/couchbase/sms_verification_requests.go b/server/db/providers/couchbase/sms_verification_requests.go deleted file mode 100644 index 0639179..0000000 --- a/server/db/providers/couchbase/sms_verification_requests.go +++ /dev/null @@ -1,22 +0,0 @@ -package couchbase - -import ( - "context" - - "github.com/authorizerdev/authorizer/server/db/models" -) - -// UpsertSMSRequest adds/updates SMS verification request -func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) { - return nil, nil -} - -// GetCodeByPhone to get code for a given phone number -func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) { - return nil, nil -} - -// DeleteSMSRequest to delete SMS verification request -func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error { - return nil -} diff --git a/server/db/providers/dynamodb/otp.go b/server/db/providers/dynamodb/otp.go index 063f634..bb55523 100644 --- a/server/db/providers/dynamodb/otp.go +++ b/server/db/providers/dynamodb/otp.go @@ -11,27 +11,39 @@ import ( // UpsertOTP to add or update otp func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) { - otp, _ := p.GetOTPByEmail(ctx, otpParam.Email) + // check if email or phone number is present + if otpParam.Email == "" && otpParam.PhoneNumber == "" { + return nil, errors.New("email or phone_number is required") + } + uniqueField := models.FieldNameEmail + if otpParam.Email == "" && otpParam.PhoneNumber != "" { + uniqueField = models.FieldNamePhoneNumber + } + var otp *models.OTP + if uniqueField == models.FieldNameEmail { + otp, _ = p.GetOTPByEmail(ctx, otpParam.Email) + } else { + otp, _ = p.GetOTPByPhoneNumber(ctx, otpParam.PhoneNumber) + } shouldCreate := false if otp == nil { id := uuid.NewString() otp = &models.OTP{ - ID: id, - Key: id, - Otp: otpParam.Otp, - Email: otpParam.Email, - ExpiresAt: otpParam.ExpiresAt, - CreatedAt: time.Now().Unix(), + ID: id, + Key: id, + Otp: otpParam.Otp, + Email: otpParam.Email, + PhoneNumber: otpParam.PhoneNumber, + ExpiresAt: otpParam.ExpiresAt, + CreatedAt: time.Now().Unix(), } shouldCreate = true } else { otp.Otp = otpParam.Otp otp.ExpiresAt = otpParam.ExpiresAt } - collection := p.db.Table(models.Collections.OTP) otp.UpdatedAt = time.Now().Unix() - var err error if shouldCreate { err = collection.Put(otp).RunWithContext(ctx) @@ -41,7 +53,6 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models if err != nil { return nil, err } - return otp, nil } @@ -49,20 +60,32 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) { var otps []models.OTP var otp models.OTP - collection := p.db.Table(models.Collections.OTP) - err := collection.Scan().Index("email").Filter("'email' = ?", emailAddress).Limit(1).AllWithContext(ctx, &otps) - if err != nil { return nil, err } if len(otps) > 0 { otp = otps[0] return &otp, nil - } else { - return nil, errors.New("no docuemnt found") } + return nil, errors.New("no docuemnt found") +} + +// GetOTPByPhoneNumber to get otp for a given phone number +func (p *provider) GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) { + var otps []models.OTP + var otp models.OTP + collection := p.db.Table(models.Collections.OTP) + err := collection.Scan().Index("phone_number").Filter("'phone_number' = ?", phoneNumber).Limit(1).AllWithContext(ctx, &otps) + if err != nil { + return nil, err + } + if len(otps) > 0 { + otp = otps[0] + return &otp, nil + } + return nil, errors.New("no docuemnt found") } // DeleteOTP to delete otp @@ -75,6 +98,5 @@ func (p *provider) DeleteOTP(ctx context.Context, otp *models.OTP) error { return err } } - return nil } diff --git a/server/db/providers/dynamodb/sms_verification_requests.go b/server/db/providers/dynamodb/sms_verification_requests.go deleted file mode 100644 index 6569a54..0000000 --- a/server/db/providers/dynamodb/sms_verification_requests.go +++ /dev/null @@ -1,22 +0,0 @@ -package dynamodb - -import ( - "context" - - "github.com/authorizerdev/authorizer/server/db/models" -) - -// UpsertSMSRequest adds/updates SMS verification request -func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) { - return nil, nil -} - -// GetCodeByPhone to get code for a given phone number -func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) { - return nil, nil -} - -// DeleteSMSRequest to delete SMS verification request -func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error { - return nil -} diff --git a/server/db/providers/mongodb/otp.go b/server/db/providers/mongodb/otp.go index 8726f5a..d70818d 100644 --- a/server/db/providers/mongodb/otp.go +++ b/server/db/providers/mongodb/otp.go @@ -13,29 +13,31 @@ import ( // UpsertOTP to add or update otp func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) { - // check if email or phone number is present - if otpParam.Email == "" && otpParam.PhoneNumber == "" { - return nil, errors.New("email or phone_number is required") - } // check if email or phone number is present if otpParam.Email == "" && otpParam.PhoneNumber == "" { return nil, errors.New("email or phone_number is required") } uniqueField := models.FieldNameEmail - if otp.Email == "" && otp.PhoneNumber != "" { + if otpParam.Email == "" && otpParam.PhoneNumber != "" { uniqueField = models.FieldNamePhoneNumber } - otp, _ := p.GetOTPByEmail(ctx, otpParam.Email) + var otp *models.OTP + if uniqueField == models.FieldNameEmail { + otp, _ = p.GetOTPByEmail(ctx, otpParam.Email) + } else { + otp, _ = p.GetOTPByPhoneNumber(ctx, otpParam.PhoneNumber) + } shouldCreate := false if otp == nil { id := uuid.NewString() otp = &models.OTP{ - ID: id, - Key: id, - Otp: otpParam.Otp, - Email: otpParam.Email, - ExpiresAt: otpParam.ExpiresAt, - CreatedAt: time.Now().Unix(), + ID: id, + Key: id, + Otp: otpParam.Otp, + Email: otpParam.Email, + PhoneNumber: otpParam.PhoneNumber, + ExpiresAt: otpParam.ExpiresAt, + CreatedAt: time.Now().Unix(), } shouldCreate = true } else { @@ -54,20 +56,28 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models if err != nil { return nil, err } - return otp, nil } // GetOTPByEmail to get otp for a given email address func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) { var otp models.OTP - otpCollection := p.db.Collection(models.Collections.OTP, options.Collection()) err := otpCollection.FindOne(ctx, bson.M{"email": emailAddress}).Decode(&otp) if err != nil { return nil, err } + return &otp, nil +} +// GetOTPByPhoneNumber to get otp for a given phone number +func (p *provider) GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) { + var otp models.OTP + otpCollection := p.db.Collection(models.Collections.OTP, options.Collection()) + err := otpCollection.FindOne(ctx, bson.M{"phone_number": phoneNumber}).Decode(&otp) + if err != nil { + return nil, err + } return &otp, nil } diff --git a/server/db/providers/mongodb/provider.go b/server/db/providers/mongodb/provider.go index 1922b0e..30af342 100644 --- a/server/db/providers/mongodb/provider.go +++ b/server/db/providers/mongodb/provider.go @@ -125,15 +125,6 @@ func NewProvider() (*provider, error) { }, }, options.CreateIndexes()) - mongodb.CreateCollection(ctx, models.Collections.SMSVerificationRequest, options.CreateCollection()) - smsCollection := mongodb.Collection(models.Collections.SMSVerificationRequest, options.Collection()) - smsCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{ - { - Keys: bson.M{"phone_number": 1}, - Options: options.Index().SetUnique(true).SetSparse(true), - }, - }, options.CreateIndexes()) - return &provider{ db: mongodb, }, nil diff --git a/server/db/providers/mongodb/sms_verification_requests.go b/server/db/providers/mongodb/sms_verification_requests.go deleted file mode 100644 index 0ee0e6c..0000000 --- a/server/db/providers/mongodb/sms_verification_requests.go +++ /dev/null @@ -1,66 +0,0 @@ -package mongodb - -import ( - "context" - "time" - - "github.com/authorizerdev/authorizer/server/db/models" - "github.com/google/uuid" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo/options" -) - -// UpsertSMSRequest adds/updates SMS verification request -func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) { - smsVerificationRequest, _ := p.GetCodeByPhone(ctx, smsRequest.PhoneNumber) - // Boolean to check if we should create a new record or update the existing one - shouldCreate := false - if smsVerificationRequest == nil { - id := uuid.NewString() - smsVerificationRequest = &models.SMSVerificationRequest{ - ID: id, - CreatedAt: time.Now().Unix(), - Code: smsRequest.Code, - PhoneNumber: smsRequest.PhoneNumber, - CodeExpiresAt: smsRequest.CodeExpiresAt, - } - shouldCreate = true - } - var err error - smsVerificationRequest.UpdatedAt = time.Now().Unix() - smsRequestCollection := p.db.Collection(models.Collections.SMSVerificationRequest, options.Collection()) - if shouldCreate { - _, err = smsRequestCollection.InsertOne(ctx, smsVerificationRequest) - } else { - _, err = smsRequestCollection.UpdateOne(ctx, bson.M{"phone_number": bson.M{"$eq": smsRequest.PhoneNumber}}, bson.M{"$set": smsVerificationRequest}, options.MergeUpdateOptions()) - } - if err != nil { - return nil, err - } - return smsVerificationRequest, nil -} - -// GetCodeByPhone to get code for a given phone number -func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) { - var smsVerificationRequest models.SMSVerificationRequest - - smsRequestCollection := p.db.Collection(models.Collections.SMSVerificationRequest, options.Collection()) - err := smsRequestCollection.FindOne(ctx, bson.M{"phone_number": phoneNumber}).Decode(&smsVerificationRequest) - - if err != nil { - return nil, err - } - - return &smsVerificationRequest, nil -} - -// DeleteSMSRequest to delete SMS verification request -func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error { - smsVerificationRequests := p.db.Collection(models.Collections.SMSVerificationRequest, options.Collection()) - _, err := smsVerificationRequests.DeleteOne(nil, bson.M{"_id": smsRequest.ID}, options.Delete()) - if err != nil { - return err - } - - return nil -} diff --git a/server/db/providers/provider_template/otp.go b/server/db/providers/provider_template/otp.go index d8685e7..0716711 100644 --- a/server/db/providers/provider_template/otp.go +++ b/server/db/providers/provider_template/otp.go @@ -16,6 +16,11 @@ func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*mod return nil, nil } +// GetOTPByPhoneNumber to get otp for a given phone number +func (p *provider) GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) { + return nil, nil +} + // DeleteOTP to delete otp func (p *provider) DeleteOTP(ctx context.Context, otp *models.OTP) error { return nil diff --git a/server/db/providers/provider_template/sms_verification_requests.go b/server/db/providers/provider_template/sms_verification_requests.go deleted file mode 100644 index d238a80..0000000 --- a/server/db/providers/provider_template/sms_verification_requests.go +++ /dev/null @@ -1,22 +0,0 @@ -package provider_template - -import ( - "context" - - "github.com/authorizerdev/authorizer/server/db/models" -) - -// UpsertSMSRequest adds/updates SMS verification request -func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) { - return nil, nil -} - -// GetCodeByPhone to get code for a given phone number -func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) { - return nil, nil -} - -// DeleteSMSRequest to delete SMS verification request -func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error { - return nil -} diff --git a/server/db/providers/providers.go b/server/db/providers/providers.go index c6065c1..be25b53 100644 --- a/server/db/providers/providers.go +++ b/server/db/providers/providers.go @@ -86,11 +86,4 @@ type Provider interface { GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) // DeleteOTP to delete otp DeleteOTP(ctx context.Context, otp *models.OTP) error - - // UpsertSMSRequest adds/updates SMS verification request - UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) - // GetCodeByPhone to get code for a given phone number - GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) - // DeleteSMSRequest to delete SMS verification request - DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error } diff --git a/server/db/providers/sql/provider.go b/server/db/providers/sql/provider.go index 89ea31b..2101953 100644 --- a/server/db/providers/sql/provider.go +++ b/server/db/providers/sql/provider.go @@ -77,7 +77,7 @@ func NewProvider() (*provider, error) { logrus.Debug("Failed to drop phone number constraint:", err) } - err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{}, &models.Webhook{}, models.WebhookLog{}, models.EmailTemplate{}, &models.OTP{}, &models.SMSVerificationRequest{}) + err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{}, &models.Webhook{}, models.WebhookLog{}, models.EmailTemplate{}, &models.OTP{}) if err != nil { return nil, err } diff --git a/server/db/providers/sql/sms_verification_requests.go b/server/db/providers/sql/sms_verification_requests.go deleted file mode 100644 index 387b5eb..0000000 --- a/server/db/providers/sql/sms_verification_requests.go +++ /dev/null @@ -1,49 +0,0 @@ -package sql - -import ( - "context" - "time" - - "github.com/authorizerdev/authorizer/server/db/models" - "github.com/google/uuid" - "gorm.io/gorm/clause" -) - -// UpsertSMSRequest adds/updates SMS verification request -func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) { - if smsRequest.ID == "" { - smsRequest.ID = uuid.New().String() - } - smsRequest.CreatedAt = time.Now().Unix() - smsRequest.UpdatedAt = time.Now().Unix() - res := p.db.Clauses(clause.OnConflict{ - Columns: []clause.Column{{Name: "phone_number"}}, - DoUpdates: clause.AssignmentColumns([]string{"code", "code_expires_at", "updated_at"}), - }).Create(smsRequest) - if res.Error != nil { - return nil, res.Error - } - return smsRequest, nil -} - -// GetCodeByPhone to get code for a given phone number -func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) { - var sms_verification_request models.SMSVerificationRequest - - result := p.db.Where("phone_number = ?", phoneNumber).First(&sms_verification_request) - if result.Error != nil { - return nil, result.Error - } - return &sms_verification_request, nil -} - -// DeleteSMSRequest to delete SMS verification request -func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error { - result := p.db.Delete(&models.SMSVerificationRequest{ - ID: smsRequest.ID, - }) - if result.Error != nil { - return result.Error - } - return nil -} diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index 885b04d..b2493ef 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -198,7 +198,6 @@ type ComplexityRoot struct { UpdateUser func(childComplexity int, params model.UpdateUserInput) int UpdateWebhook func(childComplexity int, params model.UpdateWebhookRequest) int VerifyEmail func(childComplexity int, params model.VerifyEmailInput) int - VerifyMobile func(childComplexity int, params model.VerifyMobileRequest) int VerifyOtp func(childComplexity int, params model.VerifyOTPRequest) int } @@ -344,7 +343,6 @@ type MutationResolver interface { Revoke(ctx context.Context, params model.OAuthRevokeInput) (*model.Response, error) VerifyOtp(ctx context.Context, params model.VerifyOTPRequest) (*model.AuthResponse, error) ResendOtp(ctx context.Context, params model.ResendOTPRequest) (*model.Response, error) - VerifyMobile(ctx context.Context, params model.VerifyMobileRequest) (*model.AuthResponse, error) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, error) AdminSignup(ctx context.Context, params model.AdminSignupInput) (*model.Response, error) @@ -1438,18 +1436,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.VerifyEmail(childComplexity, args["params"].(model.VerifyEmailInput)), true - case "Mutation.verify_mobile": - if e.complexity.Mutation.VerifyMobile == nil { - break - } - - args, err := ec.field_Mutation_verify_mobile_args(context.TODO(), rawArgs) - if err != nil { - return 0, false - } - - return e.complexity.Mutation.VerifyMobile(childComplexity, args["params"].(model.VerifyMobileRequest)), true - case "Mutation.verify_otp": if e.complexity.Mutation.VerifyOtp == nil { break @@ -2120,7 +2106,6 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputValidateJWTTokenInput, ec.unmarshalInputValidateSessionInput, ec.unmarshalInputVerifyEmailInput, - ec.unmarshalInputVerifyMobileRequest, ec.unmarshalInputVerifyOTPRequest, ec.unmarshalInputWebhookRequest, ) @@ -2269,11 +2254,6 @@ type SMSVerificationRequests { updated_at: Int64 } -input VerifyMobileRequest { - phone_number: String! - code: String! -} - type Error { message: String! reason: String! @@ -2728,7 +2708,9 @@ input DeleteEmailTemplateRequest { } input VerifyOTPRequest { - email: String! + # either email or phone_number is required + email: String + phone_number: String otp: String! # state is used for authorization code grant flow # it is used to get code for an on-going auth process during login @@ -2764,7 +2746,6 @@ type Mutation { revoke(params: OAuthRevokeInput!): Response! verify_otp(params: VerifyOTPRequest!): AuthResponse! resend_otp(params: ResendOTPRequest!): Response! - verify_mobile(params: VerifyMobileRequest!): AuthResponse! # admin only apis _delete_user(params: DeleteUserInput!): Response! _update_user(params: UpdateUserInput!): User! @@ -3230,21 +3211,6 @@ func (ec *executionContext) field_Mutation_verify_email_args(ctx context.Context return args, nil } -func (ec *executionContext) field_Mutation_verify_mobile_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { - var err error - args := map[string]interface{}{} - var arg0 model.VerifyMobileRequest - if tmp, ok := rawArgs["params"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params")) - arg0, err = ec.unmarshalNVerifyMobileRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐVerifyMobileRequest(ctx, tmp) - if err != nil { - return nil, err - } - } - args["params"] = arg0 - return args, nil -} - func (ec *executionContext) field_Mutation_verify_otp_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -8635,77 +8601,6 @@ func (ec *executionContext) fieldContext_Mutation_resend_otp(ctx context.Context return fc, nil } -func (ec *executionContext) _Mutation_verify_mobile(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Mutation_verify_mobile(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 ec.resolvers.Mutation().VerifyMobile(rctx, fc.Args["params"].(model.VerifyMobileRequest)) - }) - 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.AuthResponse) - fc.Result = res - return ec.marshalNAuthResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAuthResponse(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_Mutation_verify_mobile(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "Mutation", - Field: field, - IsMethod: true, - IsResolver: true, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - switch field.Name { - case "message": - return ec.fieldContext_AuthResponse_message(ctx, field) - case "should_show_otp_screen": - return ec.fieldContext_AuthResponse_should_show_otp_screen(ctx, field) - case "access_token": - return ec.fieldContext_AuthResponse_access_token(ctx, field) - case "id_token": - return ec.fieldContext_AuthResponse_id_token(ctx, field) - case "refresh_token": - return ec.fieldContext_AuthResponse_refresh_token(ctx, field) - case "expires_in": - return ec.fieldContext_AuthResponse_expires_in(ctx, field) - case "user": - return ec.fieldContext_AuthResponse_user(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type AuthResponse", field.Name) - }, - } - defer func() { - if r := recover(); r != nil { - err = ec.Recover(ctx, r) - ec.Error(ctx, err) - } - }() - ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_Mutation_verify_mobile_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { - ec.Error(ctx, err) - return - } - return fc, nil -} - func (ec *executionContext) _Mutation__delete_user(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Mutation__delete_user(ctx, field) if err != nil { @@ -17781,42 +17676,6 @@ func (ec *executionContext) unmarshalInputVerifyEmailInput(ctx context.Context, return it, nil } -func (ec *executionContext) unmarshalInputVerifyMobileRequest(ctx context.Context, obj interface{}) (model.VerifyMobileRequest, error) { - var it model.VerifyMobileRequest - asMap := map[string]interface{}{} - for k, v := range obj.(map[string]interface{}) { - asMap[k] = v - } - - fieldsInOrder := [...]string{"phone_number", "code"} - for _, k := range fieldsInOrder { - v, ok := asMap[k] - if !ok { - continue - } - switch k { - case "phone_number": - var err error - - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("phone_number")) - it.PhoneNumber, err = ec.unmarshalNString2string(ctx, v) - if err != nil { - return it, err - } - case "code": - var err error - - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("code")) - it.Code, err = ec.unmarshalNString2string(ctx, v) - if err != nil { - return it, err - } - } - } - - return it, nil -} - func (ec *executionContext) unmarshalInputVerifyOTPRequest(ctx context.Context, obj interface{}) (model.VerifyOTPRequest, error) { var it model.VerifyOTPRequest asMap := map[string]interface{}{} @@ -17824,7 +17683,7 @@ func (ec *executionContext) unmarshalInputVerifyOTPRequest(ctx context.Context, asMap[k] = v } - fieldsInOrder := [...]string{"email", "otp", "state"} + fieldsInOrder := [...]string{"email", "phone_number", "otp", "state"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -17835,7 +17694,15 @@ func (ec *executionContext) unmarshalInputVerifyOTPRequest(ctx context.Context, var err error ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email")) - it.Email, err = ec.unmarshalNString2string(ctx, v) + it.Email, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "phone_number": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("phone_number")) + it.PhoneNumber, err = ec.unmarshalOString2ᚖstring(ctx, v) if err != nil { return it, err } @@ -18723,15 +18590,6 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) return ec._Mutation_resend_otp(ctx, field) }) - if out.Values[i] == graphql.Null { - invalids++ - } - case "verify_mobile": - - out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { - return ec._Mutation_verify_mobile(ctx, field) - }) - if out.Values[i] == graphql.Null { invalids++ } @@ -20798,11 +20656,6 @@ func (ec *executionContext) unmarshalNVerifyEmailInput2githubᚗcomᚋauthorizer return res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) unmarshalNVerifyMobileRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐVerifyMobileRequest(ctx context.Context, v interface{}) (model.VerifyMobileRequest, error) { - res, err := ec.unmarshalInputVerifyMobileRequest(ctx, v) - return res, graphql.ErrorOnPath(ctx, err) -} - func (ec *executionContext) unmarshalNVerifyOTPRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐVerifyOTPRequest(ctx context.Context, v interface{}) (model.VerifyOTPRequest, error) { res, err := ec.unmarshalInputVerifyOTPRequest(ctx, v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index d327f9a..7621995 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -486,15 +486,11 @@ type VerifyEmailInput struct { State *string `json:"state"` } -type VerifyMobileRequest struct { - PhoneNumber string `json:"phone_number"` - Code string `json:"code"` -} - type VerifyOTPRequest struct { - Email string `json:"email"` - Otp string `json:"otp"` - State *string `json:"state"` + Email *string `json:"email"` + PhoneNumber *string `json:"phone_number"` + Otp string `json:"otp"` + State *string `json:"state"` } type Webhook struct { diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index c830236..bb83b9b 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -84,11 +84,6 @@ type SMSVerificationRequests { updated_at: Int64 } -input VerifyMobileRequest { - phone_number: String! - code: String! -} - type Error { message: String! reason: String! @@ -543,7 +538,9 @@ input DeleteEmailTemplateRequest { } input VerifyOTPRequest { - email: String! + # either email or phone_number is required + email: String + phone_number: String otp: String! # state is used for authorization code grant flow # it is used to get code for an on-going auth process during login @@ -579,7 +576,6 @@ type Mutation { revoke(params: OAuthRevokeInput!): Response! verify_otp(params: VerifyOTPRequest!): AuthResponse! resend_otp(params: ResendOTPRequest!): Response! - verify_mobile(params: VerifyMobileRequest!): AuthResponse! # admin only apis _delete_user(params: DeleteUserInput!): Response! _update_user(params: UpdateUserInput!): User! diff --git a/server/graph/schema.resolvers.go b/server/graph/schema.resolvers.go index beb49b2..eecb6b2 100644 --- a/server/graph/schema.resolvers.go +++ b/server/graph/schema.resolvers.go @@ -81,11 +81,6 @@ func (r *mutationResolver) ResendOtp(ctx context.Context, params model.ResendOTP return resolvers.ResendOTPResolver(ctx, params) } -// VerifyMobile is the resolver for the verify_mobile field. -func (r *mutationResolver) VerifyMobile(ctx context.Context, params model.VerifyMobileRequest) (*model.AuthResponse, error) { - return resolvers.VerifyMobileResolver(ctx, params) -} - // DeleteUser is the resolver for the _delete_user field. func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) { return resolvers.DeleteUserResolver(ctx, params) diff --git a/server/resolvers/mobile_signup.go b/server/resolvers/mobile_signup.go index 9aee0a6..908ecfe 100644 --- a/server/resolvers/mobile_signup.go +++ b/server/resolvers/mobile_signup.go @@ -8,7 +8,7 @@ import ( "github.com/google/uuid" log "github.com/sirupsen/logrus" - + "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/crypto" @@ -17,9 +17,9 @@ import ( "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/refs" + "github.com/authorizerdev/authorizer/server/smsproviders" "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" - "github.com/authorizerdev/authorizer/server/smsproviders" "github.com/authorizerdev/authorizer/server/validators" ) @@ -133,8 +133,8 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput) } user := models.User{ - Email: emailInput, - PhoneNumber: &mobile, + Email: emailInput, + PhoneNumber: &mobile, } user.Roles = strings.Join(inputRoles, ",") @@ -179,7 +179,7 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput) log.Debug("MFA service not enabled: ", err) isMFAEnforced = false } - + if isMFAEnforced { user.IsMultiFactorAuthEnabled = refs.NewBoolRef(true) } @@ -197,11 +197,11 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput) log.Debug("Failed to add user: ", err) return res, err } - + if !disablePhoneVerification { duration, _ := time.ParseDuration("10m") smsCode := utils.GenerateOTP() - + smsBody := strings.Builder{} smsBody.WriteString("Your verification code is: ") smsBody.WriteString(smsCode) @@ -213,10 +213,10 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput) } go func() { - db.Provider.UpsertSMSRequest(ctx, &models.SMSVerificationRequest{ - PhoneNumber: mobile, - Code: smsCode, - CodeExpiresAt: time.Now().Add(duration).Unix(), + db.Provider.UpsertOTP(ctx, &models.OTP{ + PhoneNumber: mobile, + Otp: smsCode, + ExpiresAt: time.Now().Add(duration).Unix(), }) smsproviders.SendSMS(mobile, smsBody.String()) }() diff --git a/server/resolvers/verify_mobile.go b/server/resolvers/verify_mobile.go deleted file mode 100644 index 4e077d7..0000000 --- a/server/resolvers/verify_mobile.go +++ /dev/null @@ -1,62 +0,0 @@ -package resolvers - -import ( - "fmt" - "context" - "time" - - "github.com/authorizerdev/authorizer/server/graph/model" - "github.com/authorizerdev/authorizer/server/utils" - "github.com/authorizerdev/authorizer/server/db" - log "github.com/sirupsen/logrus" -) - -func VerifyMobileResolver(ctx context.Context, params model.VerifyMobileRequest) (*model.AuthResponse, error) { - var res *model.AuthResponse - - _, err := utils.GinContextFromContext(ctx) - if err != nil { - log.Debug("Failed to get GinContext: ", err) - return res, err - } - - smsVerificationRequest, err := db.Provider.GetCodeByPhone(ctx, params.PhoneNumber) - if err != nil { - log.Debug("Failed to get sms request by phone: ", err) - return res, err - } - - if smsVerificationRequest.Code != params.Code { - log.Debug("Failed to verify request: bad credentials") - return res, fmt.Errorf(`bad credentials`) - } - - expiresIn := smsVerificationRequest.CodeExpiresAt - time.Now().Unix() - if expiresIn < 0 { - log.Debug("Failed to verify sms request: Timeout") - return res, fmt.Errorf("time expired") - } - - res = &model.AuthResponse{ - Message: "successful", - } - - user, err := db.Provider.GetUserByPhoneNumber(ctx, params.PhoneNumber) - if user.PhoneNumberVerifiedAt == nil { - now := time.Now().Unix() - user.PhoneNumberVerifiedAt = &now - } - - _, err = db.Provider.UpdateUser(ctx, *user) - if err != nil { - log.Debug("Failed to update user: ", err) - return res, err - } - - err = db.Provider.DeleteSMSRequest(ctx, smsVerificationRequest) - if err != nil { - log.Debug("Failed to delete sms request: ", err.Error()) - } - - return res, err -} diff --git a/server/resolvers/verify_otp.go b/server/resolvers/verify_otp.go index aac55e2..124ee3f 100644 --- a/server/resolvers/verify_otp.go +++ b/server/resolvers/verify_otp.go @@ -27,47 +27,53 @@ func VerifyOtpResolver(ctx context.Context, params model.VerifyOTPRequest) (*mod log.Debug("Failed to get GinContext: ", err) return res, err } - - mfaSession, err := cookie.GetMfaSession(gc) - if err != nil { - log.Debug("Failed to get otp request by email: ", err) - return res, fmt.Errorf(`invalid session: %s`, err.Error()) + if refs.StringValue(params.Email) == "" && refs.StringValue(params.PhoneNumber) == "" { + log.Debug("Email or phone number is required") + return res, fmt.Errorf(`email or phone_number is required`) } - if _, err := memorystore.Provider.GetMfaSession(params.Email, mfaSession); err != nil { - log.Debug("Failed to get mfa session: ", err) - return res, fmt.Errorf(`invalid session: %s`, err.Error()) + currentField := models.FieldNameEmail + if refs.StringValue(params.Email) == "" { + currentField = models.FieldNamePhoneNumber } - - otp, err := db.Provider.GetOTPByEmail(ctx, params.Email) - if err != nil { - log.Debug("Failed to get otp request by email: ", err) - return res, fmt.Errorf(`invalid email: %s`, err.Error()) + var otp *models.OTP + if currentField == models.FieldNameEmail { + otp, err = db.Provider.GetOTPByEmail(ctx, refs.StringValue(params.Email)) + } else { + otp, err = db.Provider.GetOTPByPhoneNumber(ctx, refs.StringValue(params.PhoneNumber)) + } + if otp == nil && err != nil { + log.Debugf("Failed to get otp request for %s: %s", currentField, err.Error()) + return res, fmt.Errorf(`invalid %s: %s`, currentField, err.Error()) } - if params.Otp != otp.Otp { log.Debug("Failed to verify otp request: Incorrect value") return res, fmt.Errorf(`invalid otp`) } - expiresIn := otp.ExpiresAt - time.Now().Unix() - if expiresIn < 0 { log.Debug("Failed to verify otp request: Timeout") return res, fmt.Errorf("otp expired") } - - user, err := db.Provider.GetUserByEmail(ctx, params.Email) - if err != nil { + var user models.User + if currentField == models.FieldNameEmail { + user, err = db.Provider.GetUserByEmail(ctx, refs.StringValue(params.Email)) + } else { + // TODO fix after refs of db providers are fixed + var u *models.User + u, err = db.Provider.GetUserByPhoneNumber(ctx, refs.StringValue(params.PhoneNumber)) + user = *u + } + if user.ID == "" && err != nil { log.Debug("Failed to get user by email: ", err) return res, err } - - isSignUp := user.EmailVerifiedAt == nil - + isSignUp := user.EmailVerifiedAt == nil && user.PhoneNumberVerifiedAt == nil // TODO - Add Login method in DB when we introduce OTP for social media login loginMethod := constants.AuthRecipeMethodBasicAuth - + if currentField == models.FieldNamePhoneNumber { + loginMethod = constants.AuthRecipeMethodMobileOTP + } roles := strings.Split(user.Roles, ",") scope := []string{"openid", "email", "profile"} code := "" diff --git a/server/test/mobile_login_test.go b/server/test/mobile_login_test.go index 48b7690..d81d780 100644 --- a/server/test/mobile_login_test.go +++ b/server/test/mobile_login_test.go @@ -5,8 +5,8 @@ import ( "testing" "github.com/authorizerdev/authorizer/server/constants" - "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/refs" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/stretchr/testify/assert" @@ -54,17 +54,17 @@ func mobileLoginTests(t *testing.T, s TestSetup) { assert.NotNil(t, err, "should fail because phone is not verified") assert.Nil(t, res) - smsRequest, err := db.Provider.GetCodeByPhone(ctx, phoneNumber) + smsRequest, err := db.Provider.GetOTPByPhoneNumber(ctx, phoneNumber) assert.NoError(t, err) - assert.NotEmpty(t, smsRequest.Code) + assert.NotEmpty(t, smsRequest.Otp) - verifySMSRequest, err := resolvers.VerifyMobileResolver(ctx, model.VerifyMobileRequest{ - PhoneNumber: phoneNumber, - Code: smsRequest.Code, + verifySMSRequest, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{ + PhoneNumber: &phoneNumber, + Otp: smsRequest.Otp, }) assert.Nil(t, err) assert.NotEqual(t, verifySMSRequest.Message, "", "message should not be empty") - + res, err = resolvers.MobileLoginResolver(ctx, model.MobileLoginInput{ PhoneNumber: phoneNumber, Password: s.TestInfo.Password, diff --git a/server/test/resend_otp_test.go b/server/test/resend_otp_test.go index 73e715d..eb6993f 100644 --- a/server/test/resend_otp_test.go +++ b/server/test/resend_otp_test.go @@ -84,13 +84,13 @@ func resendOTPTest(t *testing.T, s TestSetup) { // Should return error for older otp verifyOtpRes, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{ - Email: email, + Email: &email, Otp: otp.Otp, }) assert.Error(t, err) assert.Nil(t, verifyOtpRes) verifyOtpRes, err = resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{ - Email: email, + Email: &email, Otp: newOtp.Otp, }) assert.NoError(t, err) diff --git a/server/test/resolvers_test.go b/server/test/resolvers_test.go index 446986c..ecda491 100644 --- a/server/test/resolvers_test.go +++ b/server/test/resolvers_test.go @@ -135,7 +135,6 @@ func TestResolvers(t *testing.T) { validateJwtTokenTest(t, s) verifyOTPTest(t, s) resendOTPTest(t, s) - verifyMobileTest(t, s) validateSessionTests(t, s) updateAllUsersTest(t, s) diff --git a/server/test/verify_mobile_test.go b/server/test/verify_mobile_test.go deleted file mode 100644 index b4daa5e..0000000 --- a/server/test/verify_mobile_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package test - -import ( - "strings" - "testing" - - "github.com/authorizerdev/authorizer/server/constants" - "github.com/authorizerdev/authorizer/server/graph/model" - "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/refs" - "github.com/authorizerdev/authorizer/server/resolvers" - "github.com/stretchr/testify/assert" -) - -func verifyMobileTest(t *testing.T, s TestSetup) { - t.Helper() - t.Run(`should verify mobile`, func(t *testing.T) { - _, ctx := createContext(s) - email := "mobile_verification." + s.TestInfo.Email - phoneNumber := "2234567890" - signUpRes, err := resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{ - Email: refs.NewStringRef(email), - PhoneNumber: phoneNumber, - Password: s.TestInfo.Password, - ConfirmPassword: s.TestInfo.Password, - }) - assert.NoError(t, err) - assert.NotNil(t, signUpRes) - assert.Equal(t, email, signUpRes.User.Email) - assert.Equal(t, phoneNumber, refs.StringValue(signUpRes.User.PhoneNumber)) - assert.True(t, strings.Contains(signUpRes.User.SignupMethods, constants.AuthRecipeMethodMobileBasicAuth)) - assert.Len(t, strings.Split(signUpRes.User.SignupMethods, ","), 1) - - res, err := resolvers.MobileLoginResolver(ctx, model.MobileLoginInput{ - PhoneNumber: phoneNumber, - Password: "random_test", - }) - assert.Error(t, err) - assert.Nil(t, res) - - // should fail because phone is not verified - res, err = resolvers.MobileLoginResolver(ctx, model.MobileLoginInput{ - PhoneNumber: phoneNumber, - Password: s.TestInfo.Password, - }) - assert.NotNil(t, err, "should fail because phone is not verified") - assert.Nil(t, res) - - // get code from db - smsRequest, err := db.Provider.GetCodeByPhone(ctx, phoneNumber) - assert.NoError(t, err) - assert.NotEmpty(t, smsRequest.Code) - - // throw an error if the code is not correct - verifySMSRequest, err := resolvers.VerifyMobileResolver(ctx, model.VerifyMobileRequest{ - PhoneNumber: phoneNumber, - Code: "rand_12@1", - }) - assert.NotNil(t, err, "should fail because of bad credentials") - assert.Nil(t, verifySMSRequest) - - verifySMSRequest, err = resolvers.VerifyMobileResolver(ctx, model.VerifyMobileRequest{ - PhoneNumber: phoneNumber, - Code: smsRequest.Code, - }) - assert.Nil(t, err) - assert.NotEqual(t, verifySMSRequest.Message, "", "message should not be empty") - - res, err = resolvers.MobileLoginResolver(ctx, model.MobileLoginInput{ - PhoneNumber: phoneNumber, - Password: s.TestInfo.Password, - }) - assert.NoError(t, err) - assert.NotEmpty(t, res.AccessToken) - assert.NotEmpty(t, res.IDToken) - - cleanData(email) - }) -} diff --git a/server/test/verify_otp_test.go b/server/test/verify_otp_test.go index 9e074cd..750deb5 100644 --- a/server/test/verify_otp_test.go +++ b/server/test/verify_otp_test.go @@ -65,7 +65,7 @@ func verifyOTPTest(t *testing.T, s TestSetup) { assert.NotEmpty(t, otp.Otp) verifyOtpRes, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{ - Email: email, + Email: &email, Otp: otp.Otp, }) assert.Nil(t, err) From cf54fcef031ba03b06adb81a96ecd2adbc1e63c5 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 18 Jul 2023 22:50:23 +0530 Subject: [PATCH 05/11] Fix tests --- server/db/models/otp.go | 2 +- server/resolvers/mobile_login.go | 40 +++++++++++++++++++++++++++++++ server/resolvers/mobile_signup.go | 22 ++++++++++------- server/smsproviders/twilio.go | 20 ++++++---------- server/test/mobile_login_test.go | 21 ++-------------- server/test/mobile_signup_test.go | 22 +++++++++++++---- 6 files changed, 81 insertions(+), 46 deletions(-) diff --git a/server/db/models/otp.go b/server/db/models/otp.go index b0e02ee..8ccb13e 100644 --- a/server/db/models/otp.go +++ b/server/db/models/otp.go @@ -12,7 +12,7 @@ type OTP struct { Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"` Email string `gorm:"unique" json:"email" bson:"email" cql:"email" dynamo:"email" index:"email,hash"` - PhoneNumber string `gorm:"unique" json:"phone_number" bson:"phone_number" cql:"phone_number" dynamo:"phone_number" index:"phone_number,hash"` + PhoneNumber string `gorm:"index:unique_index_phone_number,unique" json:"phone_number" bson:"phone_number" cql:"phone_number" dynamo:"phone_number" index:"phone_number,hash"` Otp string `json:"otp" bson:"otp" cql:"otp" dynamo:"otp"` ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at" dynamo:"expires_at"` CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"` diff --git a/server/resolvers/mobile_login.go b/server/resolvers/mobile_login.go index 9da0a53..8368745 100644 --- a/server/resolvers/mobile_login.go +++ b/server/resolvers/mobile_login.go @@ -17,6 +17,7 @@ import ( "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/refs" + "github.com/authorizerdev/authorizer/server/smsproviders" "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/validators" @@ -94,6 +95,45 @@ func MobileLoginResolver(ctx context.Context, params model.MobileLoginInput) (*m roles = params.Roles } + disablePhoneVerification, _ := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisablePhoneVerification) + if disablePhoneVerification { + now := time.Now().Unix() + user.PhoneNumberVerifiedAt = &now + } + fmt.Println("=> disablePhoneVerification", disablePhoneVerification) + + if !disablePhoneVerification { + duration, _ := time.ParseDuration("10m") + smsCode := utils.GenerateOTP() + + smsBody := strings.Builder{} + smsBody.WriteString("Your verification code is: ") + smsBody.WriteString(smsCode) + + // TODO: For those who enabled the webhook to call their sms vendor separately - sending the otp to their api + if err != nil { + log.Debug("error while upserting user: ", err.Error()) + return nil, err + } + _, err := db.Provider.UpsertOTP(ctx, &models.OTP{ + PhoneNumber: params.PhoneNumber, + Otp: smsCode, + ExpiresAt: time.Now().Add(duration).Unix(), + }) + if err != nil { + log.Debug("error while upserting OTP: ", err.Error()) + return nil, err + } + go func() { + + smsproviders.SendSMS(params.PhoneNumber, smsBody.String()) + }() + return &model.AuthResponse{ + Message: "Please check the OTP", + ShouldShowOtpScreen: refs.NewBoolRef(true), + }, nil + } + scope := []string{"openid", "email", "profile"} if params.Scope != nil && len(scope) > 0 { scope = params.Scope diff --git a/server/resolvers/mobile_signup.go b/server/resolvers/mobile_signup.go index 908ecfe..610b238 100644 --- a/server/resolvers/mobile_signup.go +++ b/server/resolvers/mobile_signup.go @@ -105,7 +105,6 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput) } inputRoles := []string{} - if len(params.Roles) > 0 { // check if roles exists rolesString, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyRoles) @@ -197,7 +196,7 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput) log.Debug("Failed to add user: ", err) return res, err } - + fmt.Println("=> disablePhoneVerification signup", disablePhoneVerification) if !disablePhoneVerification { duration, _ := time.ParseDuration("10m") smsCode := utils.GenerateOTP() @@ -211,15 +210,22 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput) log.Debug("error while upserting user: ", err.Error()) return nil, err } - + _, err = db.Provider.UpsertOTP(ctx, &models.OTP{ + PhoneNumber: mobile, + Otp: smsCode, + ExpiresAt: time.Now().Add(duration).Unix(), + }) + if err != nil { + log.Debug("error while upserting OTP: ", err.Error()) + return nil, err + } go func() { - db.Provider.UpsertOTP(ctx, &models.OTP{ - PhoneNumber: mobile, - Otp: smsCode, - ExpiresAt: time.Now().Add(duration).Unix(), - }) smsproviders.SendSMS(mobile, smsBody.String()) }() + return &model.AuthResponse{ + Message: "Please check the OTP in your inbox", + ShouldShowOtpScreen: refs.NewBoolRef(true), + }, nil } roles := strings.Split(user.Roles, ",") diff --git a/server/smsproviders/twilio.go b/server/smsproviders/twilio.go index 093e924..900be6d 100644 --- a/server/smsproviders/twilio.go +++ b/server/smsproviders/twilio.go @@ -1,43 +1,37 @@ package smsproviders import ( - twilio "github.com/twilio/twilio-go" - api "github.com/twilio/twilio-go/rest/api/v2010" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/memorystore" log "github.com/sirupsen/logrus" + twilio "github.com/twilio/twilio-go" + api "github.com/twilio/twilio-go/rest/api/v2010" ) // TODO: Should be restructured to interface when another provider is added func SendSMS(sendTo, messageBody string) error { - twilioAPISecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwilioAPISecret) - if err != nil || twilioAPISecret == ""{ - log.Errorf("Failed to get api secret: ", err) + if err != nil || twilioAPISecret == "" { + log.Debug("Failed to get api secret: ", err) return err } - twilioAPIKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwilioAPIKey) - if err != nil || twilioAPIKey == ""{ - log.Errorf("Failed to get api key: ", err) + if err != nil || twilioAPIKey == "" { + log.Debug("Failed to get api key: ", err) return err } - twilioSenderFrom, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwilioSenderFrom) if err != nil || twilioSenderFrom == "" { - log.Errorf("Failed to get sender: ", err) + log.Debug("Failed to get sender: ", err) return err } - // accountSID is not a must to send sms on twilio twilioAccountSID, _ := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwilioAccountSID) - client := twilio.NewRestClientWithParams(twilio.ClientParams{ Username: twilioAPIKey, Password: twilioAPISecret, AccountSid: twilioAccountSID, }) - message := &api.CreateMessageParams{} message.SetBody(messageBody) message.SetFrom(twilioSenderFrom) diff --git a/server/test/mobile_login_test.go b/server/test/mobile_login_test.go index d81d780..4cc181a 100644 --- a/server/test/mobile_login_test.go +++ b/server/test/mobile_login_test.go @@ -1,10 +1,8 @@ package test import ( - "strings" "testing" - "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/refs" @@ -26,11 +24,6 @@ func mobileLoginTests(t *testing.T, s TestSetup) { }) assert.NoError(t, err) assert.NotNil(t, signUpRes) - assert.Equal(t, email, signUpRes.User.Email) - assert.Equal(t, phoneNumber, refs.StringValue(signUpRes.User.PhoneNumber)) - assert.True(t, strings.Contains(signUpRes.User.SignupMethods, constants.AuthRecipeMethodMobileBasicAuth)) - assert.Len(t, strings.Split(signUpRes.User.SignupMethods, ","), 1) - res, err := resolvers.MobileLoginResolver(ctx, model.MobileLoginInput{ PhoneNumber: phoneNumber, Password: "random_test", @@ -45,7 +38,6 @@ func mobileLoginTests(t *testing.T, s TestSetup) { }) assert.Error(t, err) assert.Nil(t, res) - // should fail because phone is not verified res, err = resolvers.MobileLoginResolver(ctx, model.MobileLoginInput{ PhoneNumber: phoneNumber, @@ -53,26 +45,17 @@ func mobileLoginTests(t *testing.T, s TestSetup) { }) assert.NotNil(t, err, "should fail because phone is not verified") assert.Nil(t, res) - smsRequest, err := db.Provider.GetOTPByPhoneNumber(ctx, phoneNumber) assert.NoError(t, err) assert.NotEmpty(t, smsRequest.Otp) - verifySMSRequest, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{ PhoneNumber: &phoneNumber, Otp: smsRequest.Otp, }) assert.Nil(t, err) assert.NotEqual(t, verifySMSRequest.Message, "", "message should not be empty") - - res, err = resolvers.MobileLoginResolver(ctx, model.MobileLoginInput{ - PhoneNumber: phoneNumber, - Password: s.TestInfo.Password, - }) - assert.NoError(t, err) - assert.NotEmpty(t, res.AccessToken) - assert.NotEmpty(t, res.IDToken) - + assert.NotEmpty(t, verifySMSRequest.AccessToken) + assert.NotEmpty(t, verifySMSRequest.IDToken) cleanData(email) }) } diff --git a/server/test/mobile_signup_test.go b/server/test/mobile_signup_test.go index 11deccc..56e96a8 100644 --- a/server/test/mobile_signup_test.go +++ b/server/test/mobile_signup_test.go @@ -1,9 +1,11 @@ package test import ( + "fmt" "testing" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/refs" @@ -65,16 +67,26 @@ func mobileSingupTest(t *testing.T, s TestSetup) { }) assert.Error(t, err) assert.Nil(t, res) - + phoneNumber := "1234567890" res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{ - PhoneNumber: "1234567890", + PhoneNumber: phoneNumber, Password: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password, }) assert.NoError(t, err) - assert.NotEmpty(t, res.AccessToken) - assert.Equal(t, "1234567890@authorizer.dev", res.User.Email) - + assert.NotNil(t, res) + assert.True(t, *res.ShouldShowOtpScreen) + // Verify with otp + otp, err := db.Provider.GetOTPByPhoneNumber(ctx, phoneNumber) + fmt.Println("=> otp", otp, err) + assert.Nil(t, err) + assert.NotEmpty(t, otp.Otp) + otpRes, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{ + PhoneNumber: &phoneNumber, + Otp: otp.Otp, + }) + assert.Nil(t, err) + assert.NotEmpty(t, otpRes.Message) res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{ PhoneNumber: "1234567890", Password: s.TestInfo.Password, From 60777026261a56fa61e7e95d4c66ca0cedd74cf4 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sun, 23 Jul 2023 07:29:29 +0530 Subject: [PATCH 06/11] fix: tests for otp refactor --- server/db/models/otp.go | 2 +- server/db/providers/arangodb/provider.go | 6 +----- server/db/providers/cassandradb/provider.go | 8 ++++++++ server/db/providers/dynamodb/otp.go | 2 +- server/db/providers/dynamodb/provider.go | 2 +- server/go.mod | 5 +++-- server/go.sum | 19 +++++++++++++++++++ server/resolvers/mobile_login.go | 2 -- server/resolvers/mobile_signup.go | 2 -- ...{resolvers_test.go => integration_test.go} | 6 +++++- server/test/mobile_signup_test.go | 2 -- server/test/update_webhook_test.go | 6 +++--- server/test/webhook_test.go | 2 +- server/test/webhooks_test.go | 2 +- 14 files changed, 44 insertions(+), 22 deletions(-) rename server/test/{resolvers_test.go => integration_test.go} (93%) diff --git a/server/db/models/otp.go b/server/db/models/otp.go index 8ccb13e..bd0b41c 100644 --- a/server/db/models/otp.go +++ b/server/db/models/otp.go @@ -12,7 +12,7 @@ type OTP struct { Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"` Email string `gorm:"unique" json:"email" bson:"email" cql:"email" dynamo:"email" index:"email,hash"` - PhoneNumber string `gorm:"index:unique_index_phone_number,unique" json:"phone_number" bson:"phone_number" cql:"phone_number" dynamo:"phone_number" index:"phone_number,hash"` + PhoneNumber string `gorm:"index:unique_index_phone_number,unique" json:"phone_number" bson:"phone_number" cql:"phone_number" dynamo:"phone_number"` Otp string `json:"otp" bson:"otp" cql:"otp" dynamo:"otp"` ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at" dynamo:"expires_at"` CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"` diff --git a/server/db/providers/arangodb/provider.go b/server/db/providers/arangodb/provider.go index 2dd7b28..979d56e 100644 --- a/server/db/providers/arangodb/provider.go +++ b/server/db/providers/arangodb/provider.go @@ -229,11 +229,7 @@ func NewProvider() (*provider, error) { if err != nil { return nil, err } - otpCollection.EnsureHashIndex(ctx, []string{"email"}, &arangoDriver.EnsureHashIndexOptions{ - Unique: true, - Sparse: true, - }) - otpCollection.EnsureHashIndex(ctx, []string{"phone_number"}, &arangoDriver.EnsureHashIndexOptions{ + otpCollection.EnsureHashIndex(ctx, []string{models.FieldNameEmail, models.FieldNamePhoneNumber}, &arangoDriver.EnsureHashIndexOptions{ Unique: true, Sparse: true, }) diff --git a/server/db/providers/cassandradb/provider.go b/server/db/providers/cassandradb/provider.go index 1c989be..6f1fe6b 100644 --- a/server/db/providers/cassandradb/provider.go +++ b/server/db/providers/cassandradb/provider.go @@ -254,6 +254,14 @@ func NewProvider() (*provider, error) { if err != nil { return nil, err } + // Add phone_number column to otp table + otpAlterQuery := fmt.Sprintf(`ALTER TABLE %s.%s ADD (phone_number text);`, KeySpace, models.Collections.OTP) + err = session.Query(otpAlterQuery).Exec() + if err != nil { + log.Debug("Failed to alter table as column exists: ", err) + // continue + } + // Add phone number index otpIndexQueryPhoneNumber := fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_otp_phone_number ON %s.%s (phone_number)", KeySpace, models.Collections.OTP) err = session.Query(otpIndexQueryPhoneNumber).Exec() if err != nil { diff --git a/server/db/providers/dynamodb/otp.go b/server/db/providers/dynamodb/otp.go index bb55523..d4abf73 100644 --- a/server/db/providers/dynamodb/otp.go +++ b/server/db/providers/dynamodb/otp.go @@ -77,7 +77,7 @@ func (p *provider) GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) var otps []models.OTP var otp models.OTP collection := p.db.Table(models.Collections.OTP) - err := collection.Scan().Index("phone_number").Filter("'phone_number' = ?", phoneNumber).Limit(1).AllWithContext(ctx, &otps) + err := collection.Scan().Filter("'phone_number' = ?", phoneNumber).Limit(1).AllWithContext(ctx, &otps) if err != nil { return nil, err } diff --git a/server/db/providers/dynamodb/provider.go b/server/db/providers/dynamodb/provider.go index 4bf3345..7935e37 100644 --- a/server/db/providers/dynamodb/provider.go +++ b/server/db/providers/dynamodb/provider.go @@ -31,11 +31,11 @@ func NewProvider() (*provider, error) { if awsRegion != "" { config.Region = aws.String(awsRegion) } - // custom awsAccessKeyID, awsSecretAccessKey took first priority, if not then fetch config from aws credentials if awsAccessKeyID != "" && awsSecretAccessKey != "" { config.Credentials = credentials.NewStaticCredentials(awsAccessKeyID, awsSecretAccessKey, "") } else if dbURL != "" { + log.Debug("Tring to use database url for dynamodb") // static config in case of testing or local-setup config.Credentials = credentials.NewStaticCredentials("key", "key", "") config.Endpoint = aws.String(dbURL) diff --git a/server/go.mod b/server/go.mod index 3408404..62a3227 100644 --- a/server/go.mod +++ b/server/go.mod @@ -5,7 +5,7 @@ go 1.16 require ( github.com/99designs/gqlgen v0.17.20 github.com/arangodb/go-driver v1.2.1 - github.com/aws/aws-sdk-go v1.44.109 + github.com/aws/aws-sdk-go v1.44.298 github.com/coreos/go-oidc/v3 v3.1.0 github.com/couchbase/gocb/v2 v2.6.0 github.com/gin-gonic/gin v1.8.1 @@ -13,11 +13,12 @@ require ( github.com/go-playground/validator/v10 v10.11.1 // indirect github.com/goccy/go-json v0.9.11 // indirect github.com/gocql/gocql v1.2.0 + github.com/gofrs/uuid v4.2.0+incompatible // indirect github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.6 // indirect github.com/google/uuid v1.3.0 - github.com/guregu/dynamo v1.16.0 + github.com/guregu/dynamo v1.20.0 github.com/joho/godotenv v1.3.0 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect diff --git a/server/go.sum b/server/go.sum index 224e06d..e8387ab 100644 --- a/server/go.sum +++ b/server/go.sum @@ -54,6 +54,8 @@ github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdK github.com/aws/aws-sdk-go v1.42.47/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc= github.com/aws/aws-sdk-go v1.44.109 h1:+Na5JPeS0kiEHoBp5Umcuuf+IDqXqD0lXnM920E31YI= github.com/aws/aws-sdk-go v1.44.109/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.298 h1:5qTxdubgV7PptZJmp/2qDwD2JL187ePL7VOxsSh1i3g= +github.com/aws/aws-sdk-go v1.44.298/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= 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/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= @@ -65,6 +67,8 @@ github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -208,6 +212,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/guregu/dynamo v1.16.0 h1:gmI8oi1VHwYQtq7+RPBeOiSssVLgxH/Az2t+NtDtL2c= github.com/guregu/dynamo v1.16.0/go.mod h1:W2Gqcf3MtkrS+Q6fHPGAmRtT0Dyq+TGrqfqrUC9+R/c= +github.com/guregu/dynamo v1.20.0 h1:PDdVVhRSXQFFIHlkhoKF6D8kiwI9IU6uUdz/fF6Iiy4= +github.com/guregu/dynamo v1.20.0/go.mod h1:YQ92BTYVSMIKpFEzhaVqmCJnnSIGxbNF5zvECUaEZRE= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -442,8 +448,11 @@ golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -464,6 +473,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 h1:ZrnxWX62AgTKOSagEqxvb3ffipvEDX2pl7E1TdqLqIc= golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -508,12 +519,17 @@ golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -523,8 +539,11 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/server/resolvers/mobile_login.go b/server/resolvers/mobile_login.go index 8368745..bfef56c 100644 --- a/server/resolvers/mobile_login.go +++ b/server/resolvers/mobile_login.go @@ -100,8 +100,6 @@ func MobileLoginResolver(ctx context.Context, params model.MobileLoginInput) (*m now := time.Now().Unix() user.PhoneNumberVerifiedAt = &now } - fmt.Println("=> disablePhoneVerification", disablePhoneVerification) - if !disablePhoneVerification { duration, _ := time.ParseDuration("10m") smsCode := utils.GenerateOTP() diff --git a/server/resolvers/mobile_signup.go b/server/resolvers/mobile_signup.go index 610b238..5cf1029 100644 --- a/server/resolvers/mobile_signup.go +++ b/server/resolvers/mobile_signup.go @@ -92,7 +92,6 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput) if err != nil { log.Debug("Failed to get user by email: ", err) } - if existingUser != nil { if existingUser.PhoneNumberVerifiedAt != nil { // email is verified @@ -196,7 +195,6 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput) log.Debug("Failed to add user: ", err) return res, err } - fmt.Println("=> disablePhoneVerification signup", disablePhoneVerification) if !disablePhoneVerification { duration, _ := time.ParseDuration("10m") smsCode := utils.GenerateOTP() diff --git a/server/test/resolvers_test.go b/server/test/integration_test.go similarity index 93% rename from server/test/resolvers_test.go rename to server/test/integration_test.go index ecda491..9c9dfa2 100644 --- a/server/test/resolvers_test.go +++ b/server/test/integration_test.go @@ -46,7 +46,6 @@ func TestResolvers(t *testing.T) { for dbType, dbURL := range databases { ctx := context.Background() - memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDatabaseURL, dbURL) memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDatabaseType, dbType) memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDatabaseName, testDb) @@ -57,6 +56,11 @@ func TestResolvers(t *testing.T) { if dbType == constants.DbTypeDynamoDB { memorystore.Provider.UpdateEnvVariable(constants.EnvAwsRegion, "ap-south-1") os.Setenv(constants.EnvAwsRegion, "ap-south-1") + os.Unsetenv(constants.EnvAwsAccessKeyID) + os.Unsetenv(constants.EnvAwsSecretAccessKey) + // Remove aws credentials from env, so that local dynamodb can be used + memorystore.Provider.UpdateEnvVariable(constants.EnvAwsAccessKeyID, "") + memorystore.Provider.UpdateEnvVariable(constants.EnvAwsSecretAccessKey, "") } if dbType == constants.DbTypeCouchbaseDB { memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDatabaseUsername, "Administrator") diff --git a/server/test/mobile_signup_test.go b/server/test/mobile_signup_test.go index 56e96a8..8169d9e 100644 --- a/server/test/mobile_signup_test.go +++ b/server/test/mobile_signup_test.go @@ -1,7 +1,6 @@ package test import ( - "fmt" "testing" "github.com/authorizerdev/authorizer/server/constants" @@ -78,7 +77,6 @@ func mobileSingupTest(t *testing.T, s TestSetup) { assert.True(t, *res.ShouldShowOtpScreen) // Verify with otp otp, err := db.Provider.GetOTPByPhoneNumber(ctx, phoneNumber) - fmt.Println("=> otp", otp, err) assert.Nil(t, err) assert.NotEmpty(t, otp.Otp) otpRes, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{ diff --git a/server/test/update_webhook_test.go b/server/test/update_webhook_test.go index 14ccb94..6e2d023 100644 --- a/server/test/update_webhook_test.go +++ b/server/test/update_webhook_test.go @@ -27,7 +27,7 @@ func updateWebhookTest(t *testing.T, s TestSetup) { webhooks, err := db.Provider.GetWebhookByEventName(ctx, constants.UserDeletedWebhookEvent) assert.NoError(t, err) assert.NotNil(t, webhooks) - assert.Equal(t, 2, len(webhooks)) + assert.GreaterOrEqual(t, len(webhooks), 2) for _, webhook := range webhooks { // it should completely replace headers webhook.Headers = map[string]interface{}{ @@ -58,7 +58,7 @@ func updateWebhookTest(t *testing.T, s TestSetup) { // Check if webhooks with new name is as per expected len accessWebhooks, err := db.Provider.GetWebhookByEventName(ctx, constants.UserAccessEnabledWebhookEvent) assert.NoError(t, err) - assert.Equal(t, 3, len(accessWebhooks)) + assert.GreaterOrEqual(t, len(accessWebhooks), 3) // Revert name change res, err = resolvers.UpdateWebhookResolver(ctx, model.UpdateWebhookRequest{ ID: w.ID, @@ -69,7 +69,7 @@ func updateWebhookTest(t *testing.T, s TestSetup) { updatedWebhooks, err := db.Provider.GetWebhookByEventName(ctx, constants.UserDeletedWebhookEvent) assert.NoError(t, err) assert.NotNil(t, updatedWebhooks) - assert.Equal(t, 2, len(updatedWebhooks)) + assert.GreaterOrEqual(t, len(updatedWebhooks), 2) for _, updatedWebhook := range updatedWebhooks { assert.Contains(t, refs.StringValue(updatedWebhook.EventName), constants.UserDeletedWebhookEvent) assert.Len(t, updatedWebhook.Headers, 1) diff --git a/server/test/webhook_test.go b/server/test/webhook_test.go index 0fb789f..a556f9d 100644 --- a/server/test/webhook_test.go +++ b/server/test/webhook_test.go @@ -28,7 +28,7 @@ func webhookTest(t *testing.T, s TestSetup) { webhooks, err := db.Provider.GetWebhookByEventName(ctx, constants.UserCreatedWebhookEvent) assert.NoError(t, err) assert.NotNil(t, webhooks) - assert.Equal(t, 2, len(webhooks)) + assert.GreaterOrEqual(t, len(webhooks), 2) for _, webhook := range webhooks { res, err := resolvers.WebhookResolver(ctx, model.WebhookRequest{ ID: webhook.ID, diff --git a/server/test/webhooks_test.go b/server/test/webhooks_test.go index 6ed1bb2..74cad74 100644 --- a/server/test/webhooks_test.go +++ b/server/test/webhooks_test.go @@ -30,6 +30,6 @@ func webhooksTest(t *testing.T, s TestSetup) { }) assert.NoError(t, err) assert.NotEmpty(t, webhooks) - assert.Len(t, webhooks.Webhooks, len(s.TestInfo.TestWebhookEventTypes)*2) + assert.GreaterOrEqual(t, len(webhooks.Webhooks), len(s.TestInfo.TestWebhookEventTypes)*2) }) } From 27e3ed82e41c29b781898bb218b9e62fb51b7f21 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sun, 23 Jul 2023 10:03:37 +0530 Subject: [PATCH 07/11] Update resend otp --- .env.test | 4 + server/constants/env.go | 13 ++-- server/env/env.go | 49 ++++++++---- server/env/persist_env.go | 2 +- server/go.mod | 1 - server/go.sum | 15 ---- server/graph/generated/generated.go | 15 +++- server/graph/model/models_gen.go | 5 +- server/graph/schema.graphqls | 3 +- server/memorystore/memory_store.go | 1 + server/memorystore/providers/redis/store.go | 2 +- server/resolvers/login.go | 2 +- server/resolvers/mobile_login.go | 71 +++++------------- server/resolvers/mobile_signup.go | 6 +- server/resolvers/resend_otp.go | 83 ++++++++++++++------- server/resolvers/update_env.go | 7 ++ server/smsproviders/twilio.go | 3 +- server/test/resend_otp_test.go | 4 +- server/test/test.go | 4 + server/utils/webhook.go | 2 + 20 files changed, 162 insertions(+), 130 deletions(-) diff --git a/.env.test b/.env.test index 99c8081..293248a 100644 --- a/.env.test +++ b/.env.test @@ -7,5 +7,9 @@ SMTP_PORT=2525 SMTP_USERNAME=test SMTP_PASSWORD=test SENDER_EMAIL="info@authorizer.dev" +TWILIO_API_KEY=test +TWILIO_API_SECRET=test +TWILIO_ACCOUNT_SID=ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +TWILIO_SENDER=909921212112 SENDER_NAME="Authorizer" AWS_REGION=ap-south-1 \ No newline at end of file diff --git a/server/constants/env.go b/server/constants/env.go index 0f26ede..74b9d36 100644 --- a/server/constants/env.go +++ b/server/constants/env.go @@ -66,6 +66,8 @@ const ( EnvKeySenderName = "SENDER_NAME" // EnvKeyIsEmailServiceEnabled key for env variable IS_EMAIL_SERVICE_ENABLED EnvKeyIsEmailServiceEnabled = "IS_EMAIL_SERVICE_ENABLED" + // EnvKeyIsSMSServiceEnabled key for env variable IS_SMS_SERVICE_ENABLED + EnvKeyIsSMSServiceEnabled = "IS_SMS_SERVICE_ENABLED" // EnvKeyAppCookieSecure key for env variable APP_COOKIE_SECURE EnvKeyAppCookieSecure = "APP_COOKIE_SECURE" // EnvKeyAdminCookieSecure key for env variable ADMIN_COOKIE_SECURE @@ -158,6 +160,9 @@ const ( // EnvKeyDisableMultiFactorAuthentication is key for env variable DISABLE_MULTI_FACTOR_AUTHENTICATION // this variable is used to completely disable multi factor authentication. It will have no effect on profile preference EnvKeyDisableMultiFactorAuthentication = "DISABLE_MULTI_FACTOR_AUTHENTICATION" + // EnvKeyDisablePhoneVerification is key for env variable DISABLE_PHONE_VERIFICATION + // this variable is used to disable phone verification + EnvKeyDisablePhoneVerification = "DISABLE_PHONE_VERIFICATION" // Slice variables // EnvKeyRoles key for env variable ROLES @@ -177,17 +182,13 @@ const ( // This env is used for setting default response mode in authorize handler EnvKeyDefaultAuthorizeResponseMode = "DEFAULT_AUTHORIZE_RESPONSE_MODE" - // Phone verification setting - EnvKeyDisablePhoneVerification = "DISABLE_PHONE_VERIFICATION" - // Twilio env variables - // EnvKeyTwilioAPIKey key for env variable TWILIO_API_KEY EnvKeyTwilioAPIKey = "TWILIO_API_KEY" // EnvKeyTwilioAPISecret key for env variable TWILIO_API_SECRET EnvKeyTwilioAPISecret = "TWILIO_API_SECRET" // EnvKeyTwilioAccountSID key for env variable TWILIO_ACCOUNT_SID EnvKeyTwilioAccountSID = "TWILIO_ACCOUNT_SID" - // EnvKeyTwilioSenderFrom key for env variable TWILIO_SENDER_FROM - EnvKeyTwilioSenderFrom = "TWILIO_SENDER_FROM" + // EnvKeyTwilioSender key for env variable TWILIO_SENDER + EnvKeyTwilioSender = "TWILIO_SENDER" ) diff --git a/server/env/env.go b/server/env/env.go index f61b093..8525927 100644 --- a/server/env/env.go +++ b/server/env/env.go @@ -104,6 +104,13 @@ func InitAllEnv() error { osDisableStrongPassword := os.Getenv(constants.EnvKeyDisableStrongPassword) osEnforceMultiFactorAuthentication := os.Getenv(constants.EnvKeyEnforceMultiFactorAuthentication) osDisableMultiFactorAuthentication := os.Getenv(constants.EnvKeyDisableMultiFactorAuthentication) + // phone verification var + osDisablePhoneVerification := os.Getenv(constants.EnvKeyDisablePhoneVerification) + // twilio vars + osTwilioApiKey := os.Getenv(constants.EnvKeyTwilioAPIKey) + osTwilioApiSecret := os.Getenv(constants.EnvKeyTwilioAPISecret) + osTwilioAccountSid := os.Getenv(constants.EnvKeyTwilioAccountSID) + osTwilioSender := os.Getenv(constants.EnvKeyTwilioSender) // os slice vars osAllowedOrigins := os.Getenv(constants.EnvKeyAllowedOrigins) @@ -111,15 +118,6 @@ func InitAllEnv() error { osDefaultRoles := os.Getenv(constants.EnvKeyDefaultRoles) osProtectedRoles := os.Getenv(constants.EnvKeyProtectedRoles) - // phone verification var - osDisablePhoneVerification := os.Getenv(constants.EnvKeyDisablePhoneVerification) - - // twilio vars - osTwilioApiKey := os.Getenv(constants.EnvKeyTwilioAPIKey) - osTwilioApiSecret := os.Getenv(constants.EnvKeyTwilioAPISecret) - osTwilioAccountSid := os.Getenv(constants.EnvKeyTwilioAccountSID) - osTwilioSenderFrom := os.Getenv(constants.EnvKeyTwilioSenderFrom) - ienv, ok := envData[constants.EnvKeyEnv] if !ok || ienv == "" { envData[constants.EnvKeyEnv] = osEnv @@ -145,7 +143,7 @@ func InitAllEnv() error { if val, ok := envData[constants.EnvAwsRegion]; !ok || val == "" { envData[constants.EnvAwsRegion] = osAwsRegion } - + if osAwsRegion != "" && envData[constants.EnvAwsRegion] != osAwsRegion { envData[constants.EnvAwsRegion] = osAwsRegion } @@ -691,11 +689,11 @@ func InitAllEnv() error { envData[constants.EnvKeyIsEmailServiceEnabled] = false } - if envData[constants.EnvKeySmtpHost] != "" || envData[constants.EnvKeySmtpUsername] != "" || envData[constants.EnvKeySmtpPassword] != "" || envData[constants.EnvKeySenderEmail] != "" && envData[constants.EnvKeySmtpPort] != "" { + if envData[constants.EnvKeySmtpHost] != "" && envData[constants.EnvKeySmtpUsername] != "" && envData[constants.EnvKeySmtpPassword] != "" && envData[constants.EnvKeySenderEmail] != "" && envData[constants.EnvKeySmtpPort] != "" { envData[constants.EnvKeyIsEmailServiceEnabled] = true } - if envData[constants.EnvKeyEnforceMultiFactorAuthentication].(bool) && !envData[constants.EnvKeyIsEmailServiceEnabled].(bool) { + if envData[constants.EnvKeyEnforceMultiFactorAuthentication].(bool) && !envData[constants.EnvKeyIsEmailServiceEnabled].(bool) && !envData[constants.EnvKeyIsSMSServiceEnabled].(bool) { return errors.New("to enable multi factor authentication, please enable email service") } @@ -777,29 +775,39 @@ func InitAllEnv() error { envData[constants.EnvKeyDefaultAuthorizeResponseMode] = osAuthorizeResponseMode } + if val, ok := envData[constants.EnvKeyTwilioAPISecret]; !ok || val == "" { + envData[constants.EnvKeyTwilioAPISecret] = osTwilioApiSecret + } if osTwilioApiSecret != "" && envData[constants.EnvKeyTwilioAPISecret] != osTwilioApiSecret { envData[constants.EnvKeyTwilioAPISecret] = osTwilioApiSecret } + if val, ok := envData[constants.EnvKeyTwilioAPIKey]; !ok || val == "" { + envData[constants.EnvKeyTwilioAPIKey] = osTwilioApiKey + } if osTwilioApiKey != "" && envData[constants.EnvKeyTwilioAPIKey] != osTwilioApiKey { envData[constants.EnvKeyTwilioAPIKey] = osTwilioApiKey } + if val, ok := envData[constants.EnvKeyTwilioAccountSID]; !ok || val == "" { + envData[constants.EnvKeyTwilioAccountSID] = osTwilioAccountSid + } if osTwilioAccountSid != "" && envData[constants.EnvKeyTwilioAccountSID] != osTwilioAccountSid { envData[constants.EnvKeyTwilioAccountSID] = osTwilioAccountSid } - if osTwilioSenderFrom != "" && envData[constants.EnvKeyTwilioSenderFrom] != osTwilioSenderFrom { - envData[constants.EnvKeyTwilioSenderFrom] = osTwilioSenderFrom + if val, ok := envData[constants.EnvKeyTwilioSender]; !ok || val == "" { + envData[constants.EnvKeyTwilioSender] = osTwilioSender + } + if osTwilioSender != "" && envData[constants.EnvKeyTwilioSender] != osTwilioSender { + envData[constants.EnvKeyTwilioSender] = osTwilioSender } if _, ok := envData[constants.EnvKeyDisablePhoneVerification]; !ok { envData[constants.EnvKeyDisablePhoneVerification] = osDisablePhoneVerification == "false" } - if osDisablePhoneVerification != "" { boolValue, err := strconv.ParseBool(osDisablePhoneVerification) - if err != nil { return err } @@ -808,6 +816,15 @@ func InitAllEnv() error { } } + if envData[constants.EnvKeyTwilioAPIKey] == "" || envData[constants.EnvKeyTwilioAPISecret] == "" || envData[constants.EnvKeyTwilioAccountSID] == "" || envData[constants.EnvKeyTwilioSender] == "" { + envData[constants.EnvKeyDisablePhoneVerification] = true + envData[constants.EnvKeyIsSMSServiceEnabled] = false + } + if envData[constants.EnvKeyTwilioAPIKey] != "" && envData[constants.EnvKeyTwilioAPISecret] != "" && envData[constants.EnvKeyTwilioAccountSID] != "" && envData[constants.EnvKeyTwilioSender] != "" { + envData[constants.EnvKeyDisablePhoneVerification] = false + envData[constants.EnvKeyIsSMSServiceEnabled] = true + } + err = memorystore.Provider.UpdateEnvStore(envData) if err != nil { log.Debug("Error while updating env store: ", err) diff --git a/server/env/persist_env.go b/server/env/persist_env.go index 8ba33f2..719c91d 100644 --- a/server/env/persist_env.go +++ b/server/env/persist_env.go @@ -200,7 +200,7 @@ func PersistEnv() error { envValue := strings.TrimSpace(os.Getenv(key)) if envValue != "" { switch key { - case constants.EnvKeyIsProd, constants.EnvKeyDisableBasicAuthentication, constants.EnvKeyDisableMobileBasicAuthentication, constants.EnvKeyDisableEmailVerification, constants.EnvKeyDisableLoginPage, constants.EnvKeyDisableMagicLinkLogin, constants.EnvKeyDisableSignUp, constants.EnvKeyDisableRedisForEnv, constants.EnvKeyDisableStrongPassword, constants.EnvKeyIsEmailServiceEnabled, constants.EnvKeyEnforceMultiFactorAuthentication, constants.EnvKeyDisableMultiFactorAuthentication, constants.EnvKeyAdminCookieSecure, constants.EnvKeyAppCookieSecure, constants.EnvKeyDisablePhoneVerification: + case constants.EnvKeyIsProd, constants.EnvKeyDisableBasicAuthentication, constants.EnvKeyDisableMobileBasicAuthentication, constants.EnvKeyDisableEmailVerification, constants.EnvKeyDisableLoginPage, constants.EnvKeyDisableMagicLinkLogin, constants.EnvKeyDisableSignUp, constants.EnvKeyDisableRedisForEnv, constants.EnvKeyDisableStrongPassword, constants.EnvKeyIsEmailServiceEnabled, constants.EnvKeyIsSMSServiceEnabled, constants.EnvKeyEnforceMultiFactorAuthentication, constants.EnvKeyDisableMultiFactorAuthentication, constants.EnvKeyAdminCookieSecure, constants.EnvKeyAppCookieSecure, constants.EnvKeyDisablePhoneVerification: if envValueBool, err := strconv.ParseBool(envValue); err == nil { if value.(bool) != envValueBool { storeData[key] = envValueBool diff --git a/server/go.mod b/server/go.mod index 62a3227..0d50507 100644 --- a/server/go.mod +++ b/server/go.mod @@ -13,7 +13,6 @@ require ( github.com/go-playground/validator/v10 v10.11.1 // indirect github.com/goccy/go-json v0.9.11 // indirect github.com/gocql/gocql v1.2.0 - github.com/gofrs/uuid v4.2.0+incompatible // indirect github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.6 // indirect diff --git a/server/go.sum b/server/go.sum index e8387ab..4a2c928 100644 --- a/server/go.sum +++ b/server/go.sum @@ -51,9 +51,6 @@ github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e h1:Xg+hGrY2 github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e/go.mod h1:mq7Shfa/CaixoDxiyAAc5jZ6CVBAyPaNQCGS7mkj4Ho= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= -github.com/aws/aws-sdk-go v1.42.47/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc= -github.com/aws/aws-sdk-go v1.44.109 h1:+Na5JPeS0kiEHoBp5Umcuuf+IDqXqD0lXnM920E31YI= -github.com/aws/aws-sdk-go v1.44.109/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.44.298 h1:5qTxdubgV7PptZJmp/2qDwD2JL187ePL7VOxsSh1i3g= github.com/aws/aws-sdk-go v1.44.298/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= @@ -65,8 +62,6 @@ github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao= github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= -github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= -github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -134,8 +129,6 @@ 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/gocql/gocql v1.2.0 h1:TZhsCd7fRuye4VyHr3WCvWwIQaZUmjsqnSIXK9FcVCE= github.com/gocql/gocql v1.2.0/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8= -github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= -github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 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/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= @@ -210,8 +203,6 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/guregu/dynamo v1.16.0 h1:gmI8oi1VHwYQtq7+RPBeOiSssVLgxH/Az2t+NtDtL2c= -github.com/guregu/dynamo v1.16.0/go.mod h1:W2Gqcf3MtkrS+Q6fHPGAmRtT0Dyq+TGrqfqrUC9+R/c= github.com/guregu/dynamo v1.20.0 h1:PDdVVhRSXQFFIHlkhoKF6D8kiwI9IU6uUdz/fF6Iiy4= github.com/guregu/dynamo v1.20.0/go.mod h1:YQ92BTYVSMIKpFEzhaVqmCJnnSIGxbNF5zvECUaEZRE= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= @@ -444,12 +435,9 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= @@ -471,7 +459,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 h1:ZrnxWX62AgTKOSagEqxvb3ffipvEDX2pl7E1TdqLqIc= golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= @@ -520,7 +507,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -540,7 +526,6 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index b2493ef..3b46684 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -2719,7 +2719,8 @@ input VerifyOTPRequest { } input ResendOTPRequest { - email: String! + email: String + phone_number: String # state is used for authorization code grant flow # it is used to get code for an on-going auth process during login # and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token @@ -16375,7 +16376,7 @@ func (ec *executionContext) unmarshalInputResendOTPRequest(ctx context.Context, asMap[k] = v } - fieldsInOrder := [...]string{"email", "state"} + fieldsInOrder := [...]string{"email", "phone_number", "state"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -16386,7 +16387,15 @@ func (ec *executionContext) unmarshalInputResendOTPRequest(ctx context.Context, var err error ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email")) - it.Email, err = ec.unmarshalNString2string(ctx, v) + it.Email, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "phone_number": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("phone_number")) + it.PhoneNumber, err = ec.unmarshalOString2ᚖstring(ctx, v) if err != nil { return it, err } diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index 7621995..8bd0edc 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -245,8 +245,9 @@ type PaginationInput struct { } type ResendOTPRequest struct { - Email string `json:"email"` - State *string `json:"state"` + Email *string `json:"email"` + PhoneNumber *string `json:"phone_number"` + State *string `json:"state"` } type ResendVerifyEmailInput struct { diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index bb83b9b..106d561 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -549,7 +549,8 @@ input VerifyOTPRequest { } input ResendOTPRequest { - email: String! + email: String + phone_number: String # state is used for authorization code grant flow # it is used to get code for an on-going auth process during login # and use that code for setting `c_hash` in id_token diff --git a/server/memorystore/memory_store.go b/server/memorystore/memory_store.go index 4285860..2b3c9b2 100644 --- a/server/memorystore/memory_store.go +++ b/server/memorystore/memory_store.go @@ -33,6 +33,7 @@ func InitMemStore() error { constants.EnvKeyDisableSignUp: false, constants.EnvKeyDisableStrongPassword: false, constants.EnvKeyIsEmailServiceEnabled: false, + constants.EnvKeyIsSMSServiceEnabled: false, constants.EnvKeyEnforceMultiFactorAuthentication: false, constants.EnvKeyDisableMultiFactorAuthentication: false, constants.EnvKeyAppCookieSecure: true, diff --git a/server/memorystore/providers/redis/store.go b/server/memorystore/providers/redis/store.go index 63e3e37..d42e2c0 100644 --- a/server/memorystore/providers/redis/store.go +++ b/server/memorystore/providers/redis/store.go @@ -176,7 +176,7 @@ func (c *provider) GetEnvStore() (map[string]interface{}, error) { return nil, err } for key, value := range data { - if key == constants.EnvKeyDisableBasicAuthentication || key == constants.EnvKeyDisableMobileBasicAuthentication || key == constants.EnvKeyDisableEmailVerification || key == constants.EnvKeyDisableLoginPage || key == constants.EnvKeyDisableMagicLinkLogin || key == constants.EnvKeyDisableRedisForEnv || key == constants.EnvKeyDisableSignUp || key == constants.EnvKeyDisableStrongPassword || key == constants.EnvKeyIsEmailServiceEnabled || key == constants.EnvKeyEnforceMultiFactorAuthentication || key == constants.EnvKeyDisableMultiFactorAuthentication || key == constants.EnvKeyAppCookieSecure || key == constants.EnvKeyAdminCookieSecure { + if key == constants.EnvKeyDisableBasicAuthentication || key == constants.EnvKeyDisableMobileBasicAuthentication || key == constants.EnvKeyDisableEmailVerification || key == constants.EnvKeyDisableLoginPage || key == constants.EnvKeyDisableMagicLinkLogin || key == constants.EnvKeyDisableRedisForEnv || key == constants.EnvKeyDisableSignUp || key == constants.EnvKeyDisableStrongPassword || key == constants.EnvKeyIsEmailServiceEnabled || key == constants.EnvKeyIsSMSServiceEnabled || key == constants.EnvKeyEnforceMultiFactorAuthentication || key == constants.EnvKeyDisableMultiFactorAuthentication || key == constants.EnvKeyAppCookieSecure || key == constants.EnvKeyAdminCookieSecure { boolValue, err := strconv.ParseBool(value) if err != nil { return res, err diff --git a/server/resolvers/login.go b/server/resolvers/login.go index e05d012..667e895 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -106,7 +106,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes } isMFADisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableMultiFactorAuthentication) - if err != nil || !isEmailServiceEnabled { + if err != nil || !isMFADisabled { log.Debug("MFA service not enabled: ", err) } diff --git a/server/resolvers/mobile_login.go b/server/resolvers/mobile_login.go index bfef56c..749b00f 100644 --- a/server/resolvers/mobile_login.go +++ b/server/resolvers/mobile_login.go @@ -95,24 +95,33 @@ func MobileLoginResolver(ctx context.Context, params model.MobileLoginInput) (*m roles = params.Roles } - disablePhoneVerification, _ := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisablePhoneVerification) + disablePhoneVerification, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisablePhoneVerification) + if err != nil { + log.Debug("Error getting disable phone verification: ", err) + } if disablePhoneVerification { now := time.Now().Unix() user.PhoneNumberVerifiedAt = &now } - if !disablePhoneVerification { + isSMSServiceEnabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsSMSServiceEnabled) + if err != nil || !isSMSServiceEnabled { + log.Debug("SMS service not enabled: ", err) + } + if disablePhoneVerification { + now := time.Now().Unix() + user.PhoneNumberVerifiedAt = &now + } + isMFADisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableMultiFactorAuthentication) + if err != nil || !isMFADisabled { + log.Debug("MFA service not enabled: ", err) + } + if !disablePhoneVerification && isSMSServiceEnabled && !isMFADisabled { duration, _ := time.ParseDuration("10m") smsCode := utils.GenerateOTP() smsBody := strings.Builder{} smsBody.WriteString("Your verification code is: ") smsBody.WriteString(smsCode) - - // TODO: For those who enabled the webhook to call their sms vendor separately - sending the otp to their api - if err != nil { - log.Debug("error while upserting user: ", err.Error()) - return nil, err - } _, err := db.Provider.UpsertOTP(ctx, &models.OTP{ PhoneNumber: params.PhoneNumber, Otp: smsCode, @@ -123,7 +132,7 @@ func MobileLoginResolver(ctx context.Context, params model.MobileLoginInput) (*m return nil, err } go func() { - + utils.RegisterEvent(ctx, constants.UserLoginWebhookEvent, constants.AuthRecipeMethodMobileBasicAuth, *user) smsproviders.SendSMS(params.PhoneNumber, smsBody.String()) }() return &model.AuthResponse{ @@ -137,50 +146,6 @@ func MobileLoginResolver(ctx context.Context, params model.MobileLoginInput) (*m scope = params.Scope } - /* - // TODO use sms authentication for MFA - isEmailServiceEnabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsEmailServiceEnabled) - if err != nil || !isEmailServiceEnabled { - log.Debug("Email service not enabled: ", err) - } - - isMFADisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableMultiFactorAuthentication) - if err != nil || !isEmailServiceEnabled { - log.Debug("MFA service not enabled: ", err) - } - - // If email service is not enabled continue the process in any way - if refs.BoolValue(user.IsMultiFactorAuthEnabled) && isEmailServiceEnabled && !isMFADisabled { - otp := utils.GenerateOTP() - otpData, err := db.Provider.UpsertOTP(ctx, &models.OTP{ - Email: user.Email, - Otp: otp, - ExpiresAt: time.Now().Add(1 * time.Minute).Unix(), - }) - if err != nil { - log.Debug("Failed to add otp: ", err) - return nil, err - } - - go func() { - // exec it as go routine so that we can reduce the api latency - go email.SendEmail([]string{params.PhoneNumber}, constants.VerificationTypeOTP, map[string]interface{}{ - "user": user.ToMap(), - "organization": utils.GetOrganization(), - "otp": otpData.Otp, - }) - if err != nil { - log.Debug("Failed to send otp email: ", err) - } - }() - - return &model.AuthResponse{ - Message: "Please check the OTP in your inbox", - ShouldShowOtpScreen: refs.NewBoolRef(true), - }, nil - } - */ - code := "" codeChallenge := "" nonce := "" diff --git a/server/resolvers/mobile_signup.go b/server/resolvers/mobile_signup.go index 5cf1029..a00c440 100644 --- a/server/resolvers/mobile_signup.go +++ b/server/resolvers/mobile_signup.go @@ -187,6 +187,10 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput) now := time.Now().Unix() user.PhoneNumberVerifiedAt = &now } + isSMSServiceEnabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsSMSServiceEnabled) + if err != nil || !isSMSServiceEnabled { + log.Debug("SMS service not enabled: ", err) + } user.SignupMethods = constants.AuthRecipeMethodMobileBasicAuth user, err = db.Provider.AddUser(ctx, user) @@ -195,7 +199,7 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput) log.Debug("Failed to add user: ", err) return res, err } - if !disablePhoneVerification { + if !disablePhoneVerification && isSMSServiceEnabled { duration, _ := time.ParseDuration("10m") smsCode := utils.GenerateOTP() diff --git a/server/resolvers/resend_otp.go b/server/resolvers/resend_otp.go index 65d9cf1..dafe9dd 100644 --- a/server/resolvers/resend_otp.go +++ b/server/resolvers/resend_otp.go @@ -12,23 +12,49 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db/models" - "github.com/authorizerdev/authorizer/server/email" + emailHelper "github.com/authorizerdev/authorizer/server/email" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/refs" + "github.com/authorizerdev/authorizer/server/smsproviders" "github.com/authorizerdev/authorizer/server/utils" ) // ResendOTPResolver is a resolver for resend otp mutation func ResendOTPResolver(ctx context.Context, params model.ResendOTPRequest) (*model.Response, error) { + email := strings.ToLower(strings.Trim(refs.StringValue(params.Email), " ")) + phoneNumber := strings.Trim(refs.StringValue(params.PhoneNumber), " ") log := log.WithFields(log.Fields{ - "email": params.Email, + "email": email, + "phone_number": phoneNumber, }) - params.Email = strings.ToLower(params.Email) - user, err := db.Provider.GetUserByEmail(ctx, params.Email) + if email == "" && phoneNumber == "" { + log.Debug("Email or phone number is required") + return nil, errors.New("email or phone number is required") + } + var user models.User + var err error + if email != "" { + isEmailServiceEnabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsEmailServiceEnabled) + if err != nil || !isEmailServiceEnabled { + log.Debug("Email service not enabled: ", err) + return nil, errors.New("email service not enabled") + } + user, err = db.Provider.GetUserByEmail(ctx, email) + } else { + isSMSServiceEnabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsEmailServiceEnabled) + if err != nil || !isSMSServiceEnabled { + log.Debug("Email service not enabled: ", err) + return nil, errors.New("email service not enabled") + } + // TODO fix after refs fixes + var u *models.User + u, err = db.Provider.GetUserByPhoneNumber(ctx, phoneNumber) + user = *u + } if err != nil { log.Debug("Failed to get user by email: ", err) - return nil, fmt.Errorf(`user with this email not found`) + return nil, fmt.Errorf(`user with this email/phone not found`) } if user.RevokedTimestamp != nil { @@ -36,35 +62,38 @@ func ResendOTPResolver(ctx context.Context, params model.ResendOTPRequest) (*mod return nil, fmt.Errorf(`user access has been revoked`) } - if user.EmailVerifiedAt == nil { + if email != "" && user.EmailVerifiedAt == nil { log.Debug("User email is not verified") return nil, fmt.Errorf(`email not verified`) } + if phoneNumber != "" && user.PhoneNumberVerifiedAt == nil { + log.Debug("User phone number is not verified") + return nil, fmt.Errorf(`phone number not verified`) + } + if !refs.BoolValue(user.IsMultiFactorAuthEnabled) { log.Debug("User multi factor authentication is not enabled") return nil, fmt.Errorf(`multi factor authentication not enabled`) } - isEmailServiceEnabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsEmailServiceEnabled) - if err != nil || !isEmailServiceEnabled { - log.Debug("Email service not enabled: ", err) - return nil, errors.New("email service not enabled") - } - isMFADisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableMultiFactorAuthentication) if err != nil || isMFADisabled { log.Debug("MFA service not enabled: ", err) return nil, errors.New("multi factor authentication is disabled for this instance") } - // get otp by email - otpData, err := db.Provider.GetOTPByEmail(ctx, params.Email) + // get otp by email or phone number + var otpData *models.OTP + if email != "" { + otpData, err = db.Provider.GetOTPByEmail(ctx, refs.StringValue(params.Email)) + } else { + otpData, err = db.Provider.GetOTPByPhoneNumber(ctx, refs.StringValue(params.PhoneNumber)) + } if err != nil { log.Debug("Failed to get otp for given email: ", err) return nil, err } - if otpData == nil { log.Debug("No otp found for given email: ", params.Email) return &model.Response{ @@ -73,28 +102,30 @@ func ResendOTPResolver(ctx context.Context, params model.ResendOTPRequest) (*mod } otp := utils.GenerateOTP() - otpData, err = db.Provider.UpsertOTP(ctx, &models.OTP{ + if _, err := db.Provider.UpsertOTP(ctx, &models.OTP{ Email: user.Email, Otp: otp, ExpiresAt: time.Now().Add(1 * time.Minute).Unix(), - }) - if err != nil { - log.Debug("Error generating new otp: ", err) + }); err != nil { + log.Debug("Error upserting otp: ", err) return nil, err } - go func() { + if email != "" { // exec it as go routine so that we can reduce the api latency - go email.SendEmail([]string{params.Email}, constants.VerificationTypeOTP, map[string]interface{}{ + go emailHelper.SendEmail([]string{email}, constants.VerificationTypeOTP, map[string]interface{}{ "user": user.ToMap(), "organization": utils.GetOrganization(), "otp": otp, }) - if err != nil { - log.Debug("Error sending otp email: ", otp) - } - }() - + } else { + smsBody := strings.Builder{} + smsBody.WriteString("Your verification code is: ") + smsBody.WriteString(otp) + // exec it as go routine so that we can reduce the api latency + go smsproviders.SendSMS(phoneNumber, smsBody.String()) + } + log.Info("OTP has been resent") return &model.Response{ Message: `OTP has been sent. Please check your inbox`, }, nil diff --git a/server/resolvers/update_env.go b/server/resolvers/update_env.go index d9d6881..437565e 100644 --- a/server/resolvers/update_env.go +++ b/server/resolvers/update_env.go @@ -267,6 +267,13 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model updatedData[constants.EnvKeyIsEmailServiceEnabled] = true } + if updatedData[constants.EnvKeyTwilioAPIKey] == "" || updatedData[constants.EnvKeyTwilioAPISecret] == "" || updatedData[constants.EnvKeyTwilioAccountSID] == "" || updatedData[constants.EnvKeyTwilioSender] == "" { + updatedData[constants.EnvKeyIsSMSServiceEnabled] = false + if !updatedData[constants.EnvKeyIsSMSServiceEnabled].(bool) { + updatedData[constants.EnvKeyDisablePhoneVerification] = true + } + } + if !currentData[constants.EnvKeyEnforceMultiFactorAuthentication].(bool) && updatedData[constants.EnvKeyEnforceMultiFactorAuthentication].(bool) && !updatedData[constants.EnvKeyDisableMultiFactorAuthentication].(bool) { go db.Provider.UpdateUsers(ctx, map[string]interface{}{ "is_multi_factor_auth_enabled": true, diff --git a/server/smsproviders/twilio.go b/server/smsproviders/twilio.go index 900be6d..f4f1acb 100644 --- a/server/smsproviders/twilio.go +++ b/server/smsproviders/twilio.go @@ -8,6 +8,7 @@ import ( api "github.com/twilio/twilio-go/rest/api/v2010" ) +// SendSMS util to send sms // TODO: Should be restructured to interface when another provider is added func SendSMS(sendTo, messageBody string) error { twilioAPISecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwilioAPISecret) @@ -20,7 +21,7 @@ func SendSMS(sendTo, messageBody string) error { log.Debug("Failed to get api key: ", err) return err } - twilioSenderFrom, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwilioSenderFrom) + twilioSenderFrom, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwilioSender) if err != nil || twilioSenderFrom == "" { log.Debug("Failed to get sender: ", err) return err diff --git a/server/test/resend_otp_test.go b/server/test/resend_otp_test.go index eb6993f..1550957 100644 --- a/server/test/resend_otp_test.go +++ b/server/test/resend_otp_test.go @@ -51,7 +51,7 @@ func resendOTPTest(t *testing.T, s TestSetup) { assert.NotNil(t, updateRes) // Resend otp should return error as no initial opt is being sent resendOtpRes, err := resolvers.ResendOTPResolver(ctx, model.ResendOTPRequest{ - Email: email, + Email: refs.NewStringRef(email), }) assert.Error(t, err) assert.Nil(t, resendOtpRes) @@ -72,7 +72,7 @@ func resendOTPTest(t *testing.T, s TestSetup) { // resend otp resendOtpRes, err = resolvers.ResendOTPResolver(ctx, model.ResendOTPRequest{ - Email: email, + Email: refs.NewStringRef(email), }) assert.NoError(t, err) assert.NotEmpty(t, resendOtpRes.Message) diff --git a/server/test/test.go b/server/test/test.go index b2727ea..217ad39 100644 --- a/server/test/test.go +++ b/server/test/test.go @@ -126,6 +126,10 @@ func testSetup() TestSetup { memorystore.Provider.UpdateEnvVariable(constants.EnvKeySmtpPassword, "test") memorystore.Provider.UpdateEnvVariable(constants.EnvKeySenderEmail, "info@yopmail.com") memorystore.Provider.UpdateEnvVariable(constants.EnvKeyProtectedRoles, "admin") + memorystore.Provider.UpdateEnvVariable(constants.EnvKeyTwilioAPIKey, "test") + memorystore.Provider.UpdateEnvVariable(constants.EnvKeyTwilioAPISecret, "test") + memorystore.Provider.UpdateEnvVariable(constants.EnvKeyTwilioAccountSID, "test") + memorystore.Provider.UpdateEnvVariable(constants.EnvKeyTwilioSender, "1234567890") err = db.InitDB() if err != nil { diff --git a/server/utils/webhook.go b/server/utils/webhook.go index 705c571..4c6fbd0 100644 --- a/server/utils/webhook.go +++ b/server/utils/webhook.go @@ -16,6 +16,8 @@ import ( log "github.com/sirupsen/logrus" ) +// RegisterEvent util to register event +// TODO change user to user ref func RegisterEvent(ctx context.Context, eventName string, authRecipe string, user models.User) error { webhooks, err := db.Provider.GetWebhookByEventName(ctx, eventName) if err != nil { From 2a2b7abc087b9693c0b61031f1ae97e9a28ec280 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sun, 23 Jul 2023 11:23:24 +0530 Subject: [PATCH 08/11] Add optional show_mobile_otp_screen --- server/graph/generated/generated.go | 130 +++++++++++++++++++++------- server/graph/model/models_gen.go | 15 ++-- server/graph/schema.graphqls | 3 +- server/resolvers/login.go | 4 +- server/resolvers/mobile_login.go | 4 +- server/resolvers/mobile_signup.go | 4 +- server/test/mobile_signup_test.go | 2 +- 7 files changed, 116 insertions(+), 46 deletions(-) diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index 3b46684..887207c 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -45,13 +45,14 @@ type DirectiveRoot struct { type ComplexityRoot struct { AuthResponse struct { - AccessToken func(childComplexity int) int - ExpiresIn func(childComplexity int) int - IDToken func(childComplexity int) int - Message func(childComplexity int) int - RefreshToken func(childComplexity int) int - ShouldShowOtpScreen func(childComplexity int) int - User func(childComplexity int) int + AccessToken func(childComplexity int) int + ExpiresIn func(childComplexity int) int + IDToken func(childComplexity int) int + Message func(childComplexity int) int + RefreshToken func(childComplexity int) int + ShouldShowEmailOtpScreen func(childComplexity int) int + ShouldShowMobileOtpScreen func(childComplexity int) int + User func(childComplexity int) int } EmailTemplate struct { @@ -428,12 +429,19 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.AuthResponse.RefreshToken(childComplexity), true - case "AuthResponse.should_show_otp_screen": - if e.complexity.AuthResponse.ShouldShowOtpScreen == nil { + case "AuthResponse.should_show_email_otp_screen": + if e.complexity.AuthResponse.ShouldShowEmailOtpScreen == nil { break } - return e.complexity.AuthResponse.ShouldShowOtpScreen(childComplexity), true + return e.complexity.AuthResponse.ShouldShowEmailOtpScreen(childComplexity), true + + case "AuthResponse.should_show_mobile_otp_screen": + if e.complexity.AuthResponse.ShouldShowMobileOtpScreen == nil { + break + } + + return e.complexity.AuthResponse.ShouldShowMobileOtpScreen(childComplexity), true case "AuthResponse.user": if e.complexity.AuthResponse.User == nil { @@ -2261,7 +2269,8 @@ type Error { type AuthResponse { message: String! - should_show_otp_screen: Boolean + should_show_email_otp_screen: Boolean + should_show_mobile_otp_screen: Boolean access_token: String id_token: String refresh_token: String @@ -3474,8 +3483,8 @@ func (ec *executionContext) fieldContext_AuthResponse_message(ctx context.Contex return fc, nil } -func (ec *executionContext) _AuthResponse_should_show_otp_screen(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_AuthResponse_should_show_otp_screen(ctx, field) +func (ec *executionContext) _AuthResponse_should_show_email_otp_screen(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_AuthResponse_should_show_email_otp_screen(ctx, field) if err != nil { return graphql.Null } @@ -3488,7 +3497,7 @@ func (ec *executionContext) _AuthResponse_should_show_otp_screen(ctx context.Con }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.ShouldShowOtpScreen, nil + return obj.ShouldShowEmailOtpScreen, nil }) if err != nil { ec.Error(ctx, err) @@ -3502,7 +3511,48 @@ func (ec *executionContext) _AuthResponse_should_show_otp_screen(ctx context.Con return ec.marshalOBoolean2ᚖbool(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_AuthResponse_should_show_otp_screen(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_AuthResponse_should_show_email_otp_screen(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "AuthResponse", + 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) _AuthResponse_should_show_mobile_otp_screen(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_AuthResponse_should_show_mobile_otp_screen(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.ShouldShowMobileOtpScreen, 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_AuthResponse_should_show_mobile_otp_screen(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "AuthResponse", Field: field, @@ -7756,8 +7806,10 @@ func (ec *executionContext) fieldContext_Mutation_signup(ctx context.Context, fi switch field.Name { case "message": return ec.fieldContext_AuthResponse_message(ctx, field) - case "should_show_otp_screen": - return ec.fieldContext_AuthResponse_should_show_otp_screen(ctx, field) + case "should_show_email_otp_screen": + return ec.fieldContext_AuthResponse_should_show_email_otp_screen(ctx, field) + case "should_show_mobile_otp_screen": + return ec.fieldContext_AuthResponse_should_show_mobile_otp_screen(ctx, field) case "access_token": return ec.fieldContext_AuthResponse_access_token(ctx, field) case "id_token": @@ -7827,8 +7879,10 @@ func (ec *executionContext) fieldContext_Mutation_mobile_signup(ctx context.Cont switch field.Name { case "message": return ec.fieldContext_AuthResponse_message(ctx, field) - case "should_show_otp_screen": - return ec.fieldContext_AuthResponse_should_show_otp_screen(ctx, field) + case "should_show_email_otp_screen": + return ec.fieldContext_AuthResponse_should_show_email_otp_screen(ctx, field) + case "should_show_mobile_otp_screen": + return ec.fieldContext_AuthResponse_should_show_mobile_otp_screen(ctx, field) case "access_token": return ec.fieldContext_AuthResponse_access_token(ctx, field) case "id_token": @@ -7898,8 +7952,10 @@ func (ec *executionContext) fieldContext_Mutation_login(ctx context.Context, fie switch field.Name { case "message": return ec.fieldContext_AuthResponse_message(ctx, field) - case "should_show_otp_screen": - return ec.fieldContext_AuthResponse_should_show_otp_screen(ctx, field) + case "should_show_email_otp_screen": + return ec.fieldContext_AuthResponse_should_show_email_otp_screen(ctx, field) + case "should_show_mobile_otp_screen": + return ec.fieldContext_AuthResponse_should_show_mobile_otp_screen(ctx, field) case "access_token": return ec.fieldContext_AuthResponse_access_token(ctx, field) case "id_token": @@ -7969,8 +8025,10 @@ func (ec *executionContext) fieldContext_Mutation_mobile_login(ctx context.Conte switch field.Name { case "message": return ec.fieldContext_AuthResponse_message(ctx, field) - case "should_show_otp_screen": - return ec.fieldContext_AuthResponse_should_show_otp_screen(ctx, field) + case "should_show_email_otp_screen": + return ec.fieldContext_AuthResponse_should_show_email_otp_screen(ctx, field) + case "should_show_mobile_otp_screen": + return ec.fieldContext_AuthResponse_should_show_mobile_otp_screen(ctx, field) case "access_token": return ec.fieldContext_AuthResponse_access_token(ctx, field) case "id_token": @@ -8206,8 +8264,10 @@ func (ec *executionContext) fieldContext_Mutation_verify_email(ctx context.Conte switch field.Name { case "message": return ec.fieldContext_AuthResponse_message(ctx, field) - case "should_show_otp_screen": - return ec.fieldContext_AuthResponse_should_show_otp_screen(ctx, field) + case "should_show_email_otp_screen": + return ec.fieldContext_AuthResponse_should_show_email_otp_screen(ctx, field) + case "should_show_mobile_otp_screen": + return ec.fieldContext_AuthResponse_should_show_mobile_otp_screen(ctx, field) case "access_token": return ec.fieldContext_AuthResponse_access_token(ctx, field) case "id_token": @@ -8513,8 +8573,10 @@ func (ec *executionContext) fieldContext_Mutation_verify_otp(ctx context.Context switch field.Name { case "message": return ec.fieldContext_AuthResponse_message(ctx, field) - case "should_show_otp_screen": - return ec.fieldContext_AuthResponse_should_show_otp_screen(ctx, field) + case "should_show_email_otp_screen": + return ec.fieldContext_AuthResponse_should_show_email_otp_screen(ctx, field) + case "should_show_mobile_otp_screen": + return ec.fieldContext_AuthResponse_should_show_mobile_otp_screen(ctx, field) case "access_token": return ec.fieldContext_AuthResponse_access_token(ctx, field) case "id_token": @@ -9931,8 +9993,10 @@ func (ec *executionContext) fieldContext_Query_session(ctx context.Context, fiel switch field.Name { case "message": return ec.fieldContext_AuthResponse_message(ctx, field) - case "should_show_otp_screen": - return ec.fieldContext_AuthResponse_should_show_otp_screen(ctx, field) + case "should_show_email_otp_screen": + return ec.fieldContext_AuthResponse_should_show_email_otp_screen(ctx, field) + case "should_show_mobile_otp_screen": + return ec.fieldContext_AuthResponse_should_show_mobile_otp_screen(ctx, field) case "access_token": return ec.fieldContext_AuthResponse_access_token(ctx, field) case "id_token": @@ -17790,9 +17854,13 @@ func (ec *executionContext) _AuthResponse(ctx context.Context, sel ast.Selection if out.Values[i] == graphql.Null { invalids++ } - case "should_show_otp_screen": + case "should_show_email_otp_screen": - out.Values[i] = ec._AuthResponse_should_show_otp_screen(ctx, field, obj) + out.Values[i] = ec._AuthResponse_should_show_email_otp_screen(ctx, field, obj) + + case "should_show_mobile_otp_screen": + + out.Values[i] = ec._AuthResponse_should_show_mobile_otp_screen(ctx, field, obj) case "access_token": diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index 8bd0edc..d6139c9 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -26,13 +26,14 @@ type AdminSignupInput struct { } type AuthResponse struct { - Message string `json:"message"` - ShouldShowOtpScreen *bool `json:"should_show_otp_screen"` - AccessToken *string `json:"access_token"` - IDToken *string `json:"id_token"` - RefreshToken *string `json:"refresh_token"` - ExpiresIn *int64 `json:"expires_in"` - User *User `json:"user"` + Message string `json:"message"` + ShouldShowEmailOtpScreen *bool `json:"should_show_email_otp_screen"` + ShouldShowMobileOtpScreen *bool `json:"should_show_mobile_otp_screen"` + AccessToken *string `json:"access_token"` + IDToken *string `json:"id_token"` + RefreshToken *string `json:"refresh_token"` + ExpiresIn *int64 `json:"expires_in"` + User *User `json:"user"` } type DeleteEmailTemplateRequest struct { diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index 106d561..7ceef43 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -91,7 +91,8 @@ type Error { type AuthResponse { message: String! - should_show_otp_screen: Boolean + should_show_email_otp_screen: Boolean + should_show_mobile_otp_screen: Boolean access_token: String id_token: String refresh_token: String diff --git a/server/resolvers/login.go b/server/resolvers/login.go index 667e895..299c6ef 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -145,8 +145,8 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes }() return &model.AuthResponse{ - Message: "Please check the OTP in your inbox", - ShouldShowOtpScreen: refs.NewBoolRef(true), + Message: "Please check the OTP in your inbox", + ShouldShowEmailOtpScreen: refs.NewBoolRef(true), }, nil } diff --git a/server/resolvers/mobile_login.go b/server/resolvers/mobile_login.go index 749b00f..fc131b0 100644 --- a/server/resolvers/mobile_login.go +++ b/server/resolvers/mobile_login.go @@ -136,8 +136,8 @@ func MobileLoginResolver(ctx context.Context, params model.MobileLoginInput) (*m smsproviders.SendSMS(params.PhoneNumber, smsBody.String()) }() return &model.AuthResponse{ - Message: "Please check the OTP", - ShouldShowOtpScreen: refs.NewBoolRef(true), + Message: "Please check the OTP", + ShouldShowMobileOtpScreen: refs.NewBoolRef(true), }, nil } diff --git a/server/resolvers/mobile_signup.go b/server/resolvers/mobile_signup.go index a00c440..70b6d60 100644 --- a/server/resolvers/mobile_signup.go +++ b/server/resolvers/mobile_signup.go @@ -225,8 +225,8 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput) smsproviders.SendSMS(mobile, smsBody.String()) }() return &model.AuthResponse{ - Message: "Please check the OTP in your inbox", - ShouldShowOtpScreen: refs.NewBoolRef(true), + Message: "Please check the OTP in your inbox", + ShouldShowMobileOtpScreen: refs.NewBoolRef(true), }, nil } diff --git a/server/test/mobile_signup_test.go b/server/test/mobile_signup_test.go index 8169d9e..c93472f 100644 --- a/server/test/mobile_signup_test.go +++ b/server/test/mobile_signup_test.go @@ -74,7 +74,7 @@ func mobileSingupTest(t *testing.T, s TestSetup) { }) assert.NoError(t, err) assert.NotNil(t, res) - assert.True(t, *res.ShouldShowOtpScreen) + assert.True(t, *res.ShouldShowMobileOtpScreen) // Verify with otp otp, err := db.Provider.GetOTPByPhoneNumber(ctx, phoneNumber) assert.Nil(t, err) From 80f3698f0668aba6b527ae3c091590304b0d2543 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sun, 23 Jul 2023 11:30:43 +0530 Subject: [PATCH 09/11] [app] bump authorizer-react 1.1.12 --- app/package-lock.json | 58 +++++++++++++++++++++---------------------- app/package.json | 2 +- app/yarn.lock | 34 ++++++++++++------------- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index cf332cb..efba452 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@authorizerdev/authorizer-react": "^1.1.11", + "@authorizerdev/authorizer-react": "^1.1.12", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", @@ -27,9 +27,9 @@ } }, "node_modules/@authorizerdev/authorizer-js": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.2.3.tgz", - "integrity": "sha512-rk/fMRIsqbp+fsy2y09etVjf7CY9/4mG6hf0RKgXgRRfxtAQa1jdkt/De23hBTNeEwAWu6hP/9BQZjcrln6KtA==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.2.5.tgz", + "integrity": "sha512-X855+gqw+xuyVxUkqJh98bi+XgZPupaIgLmURQdZo2jfRVDqnnCP5bAkNr37SyX67r+IMBoPndj6xkN8cwS91Q==", "dependencies": { "cross-fetch": "^3.1.5" }, @@ -41,11 +41,11 @@ } }, "node_modules/@authorizerdev/authorizer-react": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.11.tgz", - "integrity": "sha512-tSI/yjsoeK/RvCOMiHSf1QGOeSpaLYQZEM864LFLndKoJwk7UWCJ86qg1w6ge7B00PmZSNWqST/w5JTcQaVNpw==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.12.tgz", + "integrity": "sha512-CbwkUkcVNPJueY13QyPnXJOeJyJtgtBdycsqSXcxyh4iNrqnxkv9mFRxJn8G/o16jZWmPDH6lNSnZOIfLRKfLA==", "dependencies": { - "@authorizerdev/authorizer-js": "^1.2.3" + "@authorizerdev/authorizer-js": "^1.2.5" }, "engines": { "node": ">=10" @@ -406,11 +406,11 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "node_modules/cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", "dependencies": { - "node-fetch": "2.6.7" + "node-fetch": "^2.6.12" } }, "node_modules/css-color-keywords": { @@ -567,9 +567,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -837,19 +837,19 @@ }, "dependencies": { "@authorizerdev/authorizer-js": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.2.3.tgz", - "integrity": "sha512-rk/fMRIsqbp+fsy2y09etVjf7CY9/4mG6hf0RKgXgRRfxtAQa1jdkt/De23hBTNeEwAWu6hP/9BQZjcrln6KtA==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.2.5.tgz", + "integrity": "sha512-X855+gqw+xuyVxUkqJh98bi+XgZPupaIgLmURQdZo2jfRVDqnnCP5bAkNr37SyX67r+IMBoPndj6xkN8cwS91Q==", "requires": { "cross-fetch": "^3.1.5" } }, "@authorizerdev/authorizer-react": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.11.tgz", - "integrity": "sha512-tSI/yjsoeK/RvCOMiHSf1QGOeSpaLYQZEM864LFLndKoJwk7UWCJ86qg1w6ge7B00PmZSNWqST/w5JTcQaVNpw==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.12.tgz", + "integrity": "sha512-CbwkUkcVNPJueY13QyPnXJOeJyJtgtBdycsqSXcxyh4iNrqnxkv9mFRxJn8G/o16jZWmPDH6lNSnZOIfLRKfLA==", "requires": { - "@authorizerdev/authorizer-js": "^1.2.3" + "@authorizerdev/authorizer-js": "^1.2.5" } }, "@babel/code-frame": { @@ -1144,11 +1144,11 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", "requires": { - "node-fetch": "2.6.7" + "node-fetch": "^2.6.12" } }, "css-color-keywords": { @@ -1270,9 +1270,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", "requires": { "whatwg-url": "^5.0.0" } diff --git a/app/package.json b/app/package.json index 1225346..fa9b74a 100644 --- a/app/package.json +++ b/app/package.json @@ -12,7 +12,7 @@ "author": "Lakhan Samani", "license": "ISC", "dependencies": { - "@authorizerdev/authorizer-react": "^1.1.11", + "@authorizerdev/authorizer-react": "^1.1.12", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", diff --git a/app/yarn.lock b/app/yarn.lock index 380f761..16c1059 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -2,19 +2,19 @@ # yarn lockfile v1 -"@authorizerdev/authorizer-js@^1.2.3": - "integrity" "sha512-rk/fMRIsqbp+fsy2y09etVjf7CY9/4mG6hf0RKgXgRRfxtAQa1jdkt/De23hBTNeEwAWu6hP/9BQZjcrln6KtA==" - "resolved" "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.2.3.tgz" - "version" "1.2.3" +"@authorizerdev/authorizer-js@^1.2.5": + "integrity" "sha512-X855+gqw+xuyVxUkqJh98bi+XgZPupaIgLmURQdZo2jfRVDqnnCP5bAkNr37SyX67r+IMBoPndj6xkN8cwS91Q==" + "resolved" "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.2.5.tgz" + "version" "1.2.5" dependencies: "cross-fetch" "^3.1.5" -"@authorizerdev/authorizer-react@^1.1.11": - "integrity" "sha512-tSI/yjsoeK/RvCOMiHSf1QGOeSpaLYQZEM864LFLndKoJwk7UWCJ86qg1w6ge7B00PmZSNWqST/w5JTcQaVNpw==" - "resolved" "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.11.tgz" - "version" "1.1.11" +"@authorizerdev/authorizer-react@^1.1.12": + "integrity" "sha512-CbwkUkcVNPJueY13QyPnXJOeJyJtgtBdycsqSXcxyh4iNrqnxkv9mFRxJn8G/o16jZWmPDH6lNSnZOIfLRKfLA==" + "resolved" "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.12.tgz" + "version" "1.1.12" dependencies: - "@authorizerdev/authorizer-js" "^1.2.3" + "@authorizerdev/authorizer-js" "^1.2.5" "@babel/code-frame@^7.16.7": "integrity" "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==" @@ -278,11 +278,11 @@ "version" "1.1.3" "cross-fetch@^3.1.5": - "integrity" "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==" - "resolved" "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz" - "version" "3.1.5" + "integrity" "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==" + "resolved" "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz" + "version" "3.1.8" dependencies: - "node-fetch" "2.6.7" + "node-fetch" "^2.6.12" "css-color-keywords@^1.0.0": "integrity" "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=" @@ -389,10 +389,10 @@ "resolved" "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" "version" "2.1.2" -"node-fetch@2.6.7": - "integrity" "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==" - "resolved" "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz" - "version" "2.6.7" +"node-fetch@^2.6.12": + "integrity" "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==" + "resolved" "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz" + "version" "2.6.12" dependencies: "whatwg-url" "^5.0.0" From 9f52c088832328017b72f2ea3404ac8bd9525775 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sun, 23 Jul 2023 16:13:40 +0530 Subject: [PATCH 10/11] [app] bump authorizer-react 1.1.13 --- app/package-lock.json | 30 +++++++++++++++--------------- app/package.json | 2 +- app/yarn.lock | 18 +++++++++--------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index efba452..1b7f5a4 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@authorizerdev/authorizer-react": "^1.1.12", + "@authorizerdev/authorizer-react": "^1.1.13", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", @@ -27,9 +27,9 @@ } }, "node_modules/@authorizerdev/authorizer-js": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.2.5.tgz", - "integrity": "sha512-X855+gqw+xuyVxUkqJh98bi+XgZPupaIgLmURQdZo2jfRVDqnnCP5bAkNr37SyX67r+IMBoPndj6xkN8cwS91Q==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.2.6.tgz", + "integrity": "sha512-9+9phHUMF+AeDM0y+XQvIRDoerOXnQ1vfTfYN6KxWN1apdrkAd9nzS1zUsA2uJSnX3fFZOErn83GjbYYCYF1BA==", "dependencies": { "cross-fetch": "^3.1.5" }, @@ -41,11 +41,11 @@ } }, "node_modules/@authorizerdev/authorizer-react": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.12.tgz", - "integrity": "sha512-CbwkUkcVNPJueY13QyPnXJOeJyJtgtBdycsqSXcxyh4iNrqnxkv9mFRxJn8G/o16jZWmPDH6lNSnZOIfLRKfLA==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.13.tgz", + "integrity": "sha512-LmpzyfR0+nEn+bjUrb/QU9b3kiVoYzMBIvcQ1nV4TNvrvVSqbLPKk+GmoIPkiBEtfy/QSM6XFLkiGNGD9BRP+g==", "dependencies": { - "@authorizerdev/authorizer-js": "^1.2.5" + "@authorizerdev/authorizer-js": "^1.2.6" }, "engines": { "node": ">=10" @@ -837,19 +837,19 @@ }, "dependencies": { "@authorizerdev/authorizer-js": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.2.5.tgz", - "integrity": "sha512-X855+gqw+xuyVxUkqJh98bi+XgZPupaIgLmURQdZo2jfRVDqnnCP5bAkNr37SyX67r+IMBoPndj6xkN8cwS91Q==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.2.6.tgz", + "integrity": "sha512-9+9phHUMF+AeDM0y+XQvIRDoerOXnQ1vfTfYN6KxWN1apdrkAd9nzS1zUsA2uJSnX3fFZOErn83GjbYYCYF1BA==", "requires": { "cross-fetch": "^3.1.5" } }, "@authorizerdev/authorizer-react": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.12.tgz", - "integrity": "sha512-CbwkUkcVNPJueY13QyPnXJOeJyJtgtBdycsqSXcxyh4iNrqnxkv9mFRxJn8G/o16jZWmPDH6lNSnZOIfLRKfLA==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.13.tgz", + "integrity": "sha512-LmpzyfR0+nEn+bjUrb/QU9b3kiVoYzMBIvcQ1nV4TNvrvVSqbLPKk+GmoIPkiBEtfy/QSM6XFLkiGNGD9BRP+g==", "requires": { - "@authorizerdev/authorizer-js": "^1.2.5" + "@authorizerdev/authorizer-js": "^1.2.6" } }, "@babel/code-frame": { diff --git a/app/package.json b/app/package.json index fa9b74a..2406108 100644 --- a/app/package.json +++ b/app/package.json @@ -12,7 +12,7 @@ "author": "Lakhan Samani", "license": "ISC", "dependencies": { - "@authorizerdev/authorizer-react": "^1.1.12", + "@authorizerdev/authorizer-react": "^1.1.13", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", "esbuild": "^0.12.17", diff --git a/app/yarn.lock b/app/yarn.lock index 16c1059..2be982c 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -2,19 +2,19 @@ # yarn lockfile v1 -"@authorizerdev/authorizer-js@^1.2.5": - "integrity" "sha512-X855+gqw+xuyVxUkqJh98bi+XgZPupaIgLmURQdZo2jfRVDqnnCP5bAkNr37SyX67r+IMBoPndj6xkN8cwS91Q==" - "resolved" "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.2.5.tgz" - "version" "1.2.5" +"@authorizerdev/authorizer-js@^1.2.6": + "integrity" "sha512-9+9phHUMF+AeDM0y+XQvIRDoerOXnQ1vfTfYN6KxWN1apdrkAd9nzS1zUsA2uJSnX3fFZOErn83GjbYYCYF1BA==" + "resolved" "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.2.6.tgz" + "version" "1.2.6" dependencies: "cross-fetch" "^3.1.5" -"@authorizerdev/authorizer-react@^1.1.12": - "integrity" "sha512-CbwkUkcVNPJueY13QyPnXJOeJyJtgtBdycsqSXcxyh4iNrqnxkv9mFRxJn8G/o16jZWmPDH6lNSnZOIfLRKfLA==" - "resolved" "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.12.tgz" - "version" "1.1.12" +"@authorizerdev/authorizer-react@^1.1.13": + "integrity" "sha512-LmpzyfR0+nEn+bjUrb/QU9b3kiVoYzMBIvcQ1nV4TNvrvVSqbLPKk+GmoIPkiBEtfy/QSM6XFLkiGNGD9BRP+g==" + "resolved" "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.13.tgz" + "version" "1.1.13" dependencies: - "@authorizerdev/authorizer-js" "^1.2.5" + "@authorizerdev/authorizer-js" "^1.2.6" "@babel/code-frame@^7.16.7": "integrity" "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==" From ba0cf189de056ca3f5fe1dab6af9e2b042638a5d Mon Sep 17 00:00:00 2001 From: catusax Date: Mon, 24 Jul 2023 11:58:36 +0800 Subject: [PATCH 11/11] userid ass mfa session key --- server/memorystore/providers/inmemory/store.go | 14 +++++++------- server/memorystore/providers/providers.go | 8 ++++---- server/memorystore/providers/redis/store.go | 16 ++++++++-------- server/resolvers/login.go | 2 +- server/resolvers/mobile_login.go | 12 +++++++++++- server/resolvers/verify_otp.go | 13 +++++++++++++ 6 files changed, 44 insertions(+), 21 deletions(-) diff --git a/server/memorystore/providers/inmemory/store.go b/server/memorystore/providers/inmemory/store.go index d03a9df..b20fb62 100644 --- a/server/memorystore/providers/inmemory/store.go +++ b/server/memorystore/providers/inmemory/store.go @@ -42,15 +42,15 @@ func (c *provider) DeleteSessionForNamespace(namespace string) error { return nil } -// SetMfaSession sets the mfa session with key and value of email -func (c *provider) SetMfaSession(email, key string, expiration int64) error { - c.mfasessionStore.Set(email, key, email, expiration) +// SetMfaSession sets the mfa session with key and value of userId +func (c *provider) SetMfaSession(userId, key string, expiration int64) error { + c.mfasessionStore.Set(userId, key, userId, expiration) return nil } // GetMfaSession returns value of given mfa session -func (c *provider) GetMfaSession(email, key string) (string, error) { - val := c.mfasessionStore.Get(email, key) +func (c *provider) GetMfaSession(userId, key string) (string, error) { + val := c.mfasessionStore.Get(userId, key) if val == "" { return "", fmt.Errorf("Not found") } @@ -58,8 +58,8 @@ func (c *provider) GetMfaSession(email, key string) (string, error) { } // DeleteMfaSession deletes given mfa session from in-memory store. -func (c *provider) DeleteMfaSession(email, key string) error { - c.mfasessionStore.Remove(email, key) +func (c *provider) DeleteMfaSession(userId, key string) error { + c.mfasessionStore.Remove(userId, key) return nil } diff --git a/server/memorystore/providers/providers.go b/server/memorystore/providers/providers.go index 6b3eba0..331e34a 100644 --- a/server/memorystore/providers/providers.go +++ b/server/memorystore/providers/providers.go @@ -12,12 +12,12 @@ type Provider interface { DeleteAllUserSessions(userId string) error // DeleteSessionForNamespace deletes the session for a given namespace DeleteSessionForNamespace(namespace string) error - // SetMfaSession sets the mfa session with key and value of email - SetMfaSession(email, key string, expiration int64) error + // SetMfaSession sets the mfa session with key and value of userId + SetMfaSession(userId, key string, expiration int64) error // GetMfaSession returns value of given mfa session - GetMfaSession(email, key string) (string, error) + GetMfaSession(userId, key string) (string, error) // DeleteMfaSession deletes given mfa session from in-memory store. - DeleteMfaSession(email, key string) error + DeleteMfaSession(userId, key string) error // SetState sets the login state (key, value form) in the session store SetState(key, state string) error diff --git a/server/memorystore/providers/redis/store.go b/server/memorystore/providers/redis/store.go index d42e2c0..a6ff08f 100644 --- a/server/memorystore/providers/redis/store.go +++ b/server/memorystore/providers/redis/store.go @@ -93,12 +93,12 @@ func (c *provider) DeleteSessionForNamespace(namespace string) error { return nil } -// SetMfaSession sets the mfa session with key and value of email -func (c *provider) SetMfaSession(email, key string, expiration int64) error { +// SetMfaSession sets the mfa session with key and value of userId +func (c *provider) SetMfaSession(userId, key string, expiration int64) error { currentTime := time.Now() expireTime := time.Unix(expiration, 0) duration := expireTime.Sub(currentTime) - err := c.store.Set(c.ctx, fmt.Sprintf("%s%s:%s", mfaSessionPrefix, email, key), email, duration).Err() + err := c.store.Set(c.ctx, fmt.Sprintf("%s%s:%s", mfaSessionPrefix, userId, key), userId, duration).Err() if err != nil { log.Debug("Error saving user session to redis: ", err) return err @@ -106,9 +106,9 @@ func (c *provider) SetMfaSession(email, key string, expiration int64) error { return nil } - // GetMfaSession returns value of given mfa session -func (c *provider) GetMfaSession(email, key string) (string, error) { - data, err := c.store.Get(c.ctx, fmt.Sprintf("%s%s:%s", mfaSessionPrefix, email, key)).Result() +// GetMfaSession returns value of given mfa session +func (c *provider) GetMfaSession(userId, key string) (string, error) { + data, err := c.store.Get(c.ctx, fmt.Sprintf("%s%s:%s", mfaSessionPrefix, userId, key)).Result() if err != nil { return "", err } @@ -116,8 +116,8 @@ func (c *provider) GetMfaSession(email, key string) (string, error) { } // DeleteMfaSession deletes given mfa session from in-memory store. -func (c *provider) DeleteMfaSession(email, key string) error { - if err := c.store.Del(c.ctx, fmt.Sprintf("%s%s:%s", mfaSessionPrefix, email, key)).Err(); err != nil { +func (c *provider) DeleteMfaSession(userId, key string) error { + if err := c.store.Del(c.ctx, fmt.Sprintf("%s%s:%s", mfaSessionPrefix, userId, key)).Err(); err != nil { log.Debug("Error deleting user session from redis: ", err) // continue } diff --git a/server/resolvers/login.go b/server/resolvers/login.go index 299c6ef..c588ca7 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -125,7 +125,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes } mfaSession := uuid.NewString() - err = memorystore.Provider.SetMfaSession(params.Email, mfaSession, expires) + err = memorystore.Provider.SetMfaSession(user.ID, mfaSession, expires) if err != nil { log.Debug("Failed to add mfasession: ", err) return nil, err diff --git a/server/resolvers/mobile_login.go b/server/resolvers/mobile_login.go index fc131b0..89c3825 100644 --- a/server/resolvers/mobile_login.go +++ b/server/resolvers/mobile_login.go @@ -122,15 +122,25 @@ func MobileLoginResolver(ctx context.Context, params model.MobileLoginInput) (*m smsBody := strings.Builder{} smsBody.WriteString("Your verification code is: ") smsBody.WriteString(smsCode) + expires := time.Now().Add(duration).Unix() _, err := db.Provider.UpsertOTP(ctx, &models.OTP{ PhoneNumber: params.PhoneNumber, Otp: smsCode, - ExpiresAt: time.Now().Add(duration).Unix(), + ExpiresAt: expires, }) if err != nil { log.Debug("error while upserting OTP: ", err.Error()) return nil, err } + + mfaSession := uuid.NewString() + err = memorystore.Provider.SetMfaSession(user.ID, mfaSession, expires) + if err != nil { + log.Debug("Failed to add mfasession: ", err) + return nil, err + } + cookie.SetMfaSession(gc, mfaSession) + go func() { utils.RegisterEvent(ctx, constants.UserLoginWebhookEvent, constants.AuthRecipeMethodMobileBasicAuth, *user) smsproviders.SendSMS(params.PhoneNumber, smsBody.String()) diff --git a/server/resolvers/verify_otp.go b/server/resolvers/verify_otp.go index 124ee3f..2982859 100644 --- a/server/resolvers/verify_otp.go +++ b/server/resolvers/verify_otp.go @@ -27,6 +27,13 @@ func VerifyOtpResolver(ctx context.Context, params model.VerifyOTPRequest) (*mod log.Debug("Failed to get GinContext: ", err) return res, err } + + mfaSession, err := cookie.GetMfaSession(gc) + if err != nil { + log.Debug("Failed to get otp request by email: ", err) + return res, fmt.Errorf(`invalid session: %s`, err.Error()) + } + if refs.StringValue(params.Email) == "" && refs.StringValue(params.PhoneNumber) == "" { log.Debug("Email or phone number is required") return res, fmt.Errorf(`email or phone_number is required`) @@ -68,6 +75,12 @@ func VerifyOtpResolver(ctx context.Context, params model.VerifyOTPRequest) (*mod log.Debug("Failed to get user by email: ", err) return res, err } + + if _, err := memorystore.Provider.GetMfaSession(user.ID, mfaSession); err != nil { + log.Debug("Failed to get mfa session: ", err) + return res, fmt.Errorf(`invalid session: %s`, err.Error()) + } + isSignUp := user.EmailVerifiedAt == nil && user.PhoneNumberVerifiedAt == nil // TODO - Add Login method in DB when we introduce OTP for social media login loginMethod := constants.AuthRecipeMethodBasicAuth