From ea2a7db8e02554a710e0ec91c6ab1afec1f38ca4 Mon Sep 17 00:00:00 2001 From: Mussie Teshome Date: Mon, 26 Jun 2023 14:25:13 +0300 Subject: [PATCH] forgot password - with phone --- server/resolvers/forgot_password.go | 158 ++++++++++++++++++---------- 1 file changed, 105 insertions(+), 53 deletions(-) diff --git a/server/resolvers/forgot_password.go b/server/resolvers/forgot_password.go index f497b31..7220f51 100644 --- a/server/resolvers/forgot_password.go +++ b/server/resolvers/forgot_password.go @@ -19,6 +19,7 @@ import ( "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/validators" + "github.com/authorizerdev/authorizer/server/smsproviders" ) // ForgotPasswordResolver is a resolver for forgot password mutation @@ -31,79 +32,130 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu return res, err } + disablePhoneVerification, _ := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisablePhoneVerification) isBasicAuthDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) + if err != nil { log.Debug("Error getting basic auth disabled: ", err) isBasicAuthDisabled = true } + if isBasicAuthDisabled { log.Debug("Basic authentication is disabled") return res, fmt.Errorf(`basic authentication is disabled for this instance`) } - params.Email = strings.ToLower(params.Email) - if !validators.IsValidEmail(params.Email) { - log.Debug("Invalid email address: ", params.Email) - return res, fmt.Errorf("invalid email") + mobile := strings.TrimSpace(params.EmailOrPhone) + + if !validators.IsValidEmail(params.EmailOrPhone) && len(mobile) < 10 { + log.Debug("Invalid email or phone: ", params.EmailOrPhone) + return res, fmt.Errorf("invalid email or phone") } - log := log.WithFields(log.Fields{ - "email": params.Email, - }) - user, err := db.Provider.GetUserByEmail(ctx, params.Email) - if err != nil { - log.Debug("User not found: ", err) - return res, fmt.Errorf(`user with this email not found`) - } + if validators.IsValidEmail(params.EmailOrPhone) { + + params.EmailOrPhone = strings.ToLower(params.EmailOrPhone) - hostname := parsers.GetHost(gc) - _, nonceHash, err := utils.GenerateNonce() - if err != nil { - log.Debug("Failed to generate nonce: ", err) - return res, err - } - - redirectURI := "" - // give higher preference to params redirect uri - if strings.TrimSpace(refs.StringValue(params.RedirectURI)) != "" { - redirectURI = refs.StringValue(params.RedirectURI) - } else { - redirectURI, err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL) + log := log.WithFields(log.Fields{ + "email": params.EmailOrPhone, + }) + user, err := db.Provider.GetUserByEmail(ctx, params.EmailOrPhone) if err != nil { - log.Debug("ResetPasswordURL not found using default app url: ", err) - redirectURI = hostname + "/app/reset-password" - memorystore.Provider.UpdateEnvVariable(constants.EnvKeyResetPasswordURL, redirectURI) + log.Debug("User not found: ", err) + return res, fmt.Errorf(`user with this email not found`) } + + hostname := parsers.GetHost(gc) + _, nonceHash, err := utils.GenerateNonce() + if err != nil { + log.Debug("Failed to generate nonce: ", err) + return res, err + } + + redirectURI := "" + // give higher preference to params redirect uri + if strings.TrimSpace(refs.StringValue(params.RedirectURI)) != "" { + redirectURI = refs.StringValue(params.RedirectURI) + } else { + redirectURI, err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL) + if err != nil { + log.Debug("ResetPasswordURL not found using default app url: ", err) + redirectURI = hostname + "/app/reset-password" + memorystore.Provider.UpdateEnvVariable(constants.EnvKeyResetPasswordURL, redirectURI) + } + } + + verificationToken, err := token.CreateVerificationToken(params.EmailOrPhone, constants.VerificationTypeForgotPassword, hostname, nonceHash, redirectURI) + if err != nil { + log.Debug("Failed to create verification token", err) + return res, err + } + _, err = db.Provider.AddVerificationRequest(ctx, models.VerificationRequest{ + Token: verificationToken, + Identifier: constants.VerificationTypeForgotPassword, + ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), + Email: params.EmailOrPhone, + Nonce: nonceHash, + RedirectURI: redirectURI, + }) + if err != nil { + log.Debug("Failed to add verification request", err) + return res, err + } + + // execute it as go routine so that we can reduce the api latency + go email.SendEmail([]string{params.EmailOrPhone}, constants.VerificationTypeForgotPassword, map[string]interface{}{ + "user": user.ToMap(), + "organization": utils.GetOrganization(), + "verification_url": utils.GetForgotPasswordURL(verificationToken, redirectURI), + }) + + res = &model.Response{ + Message: `Please check your inbox! We have sent a password reset link.`, + } + + return res, nil } - verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname, nonceHash, redirectURI) - if err != nil { - log.Debug("Failed to create verification token", err) - return res, err - } - _, err = db.Provider.AddVerificationRequest(ctx, models.VerificationRequest{ - Token: verificationToken, - Identifier: constants.VerificationTypeForgotPassword, - ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), - Email: params.Email, - Nonce: nonceHash, - RedirectURI: redirectURI, - }) - if err != nil { - log.Debug("Failed to add verification request", err) - return res, err - } + if !disablePhoneVerification && len(mobile) > 9 { + + if _, err := db.Provider.GetUserByPhoneNumber(ctx, refs.StringValue(¶ms.EmailOrPhone)); err != nil { + return res, fmt.Errorf("user with given phone number does not exist") + } + + duration, _ := time.ParseDuration("10m") + smsCode := utils.GenerateOTP() + + smsBody := strings.Builder{} + smsBody.WriteString("Your verification code is: ") + smsBody.WriteString(smsCode) - // execute it as go routine so that we can reduce the api latency - go email.SendEmail([]string{params.Email}, constants.VerificationTypeForgotPassword, map[string]interface{}{ - "user": user.ToMap(), - "organization": utils.GetOrganization(), - "verification_url": utils.GetForgotPasswordURL(verificationToken, redirectURI), - }) + go func() { + _, err = db.Provider.UpsertSMSRequest(ctx, &models.SMSVerificationRequest{ + PhoneNumber: params.EmailOrPhone, + Code: smsCode, + CodeExpiresAt: time.Now().Add(duration).Unix(), + }) - res = &model.Response{ - Message: `Please check your inbox! We have sent a password reset link.`, + if err != nil { + log.Debug("Failed to upsert sms otp: ", err) + return + } + + err = smsproviders.SendSMS(params.EmailOrPhone, smsBody.String()) + if err != nil { + log.Debug("Failed to send sms: ", err) + return + } + }() + + res = &model.Response{ + Message: `verification code has been sent to your phone`, + } + + return res, nil } return res, nil + }