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,