From 2f849b8f0c268bd089010fdcad4694adab57ae8c Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Thu, 13 Jul 2023 11:39:22 +0530 Subject: [PATCH] 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)