diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index b0792bc..967e9c6 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -2791,7 +2791,8 @@ input UpdateUserInput { } input ForgotPasswordInput { - email: String! + email: String + phone_number: String state: String redirect_uri: String } @@ -16821,7 +16822,7 @@ func (ec *executionContext) unmarshalInputForgotPasswordInput(ctx context.Contex asMap[k] = v } - fieldsInOrder := [...]string{"email", "state", "redirect_uri"} + fieldsInOrder := [...]string{"email", "phone_number", "state", "redirect_uri"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -16832,11 +16833,20 @@ func (ec *executionContext) unmarshalInputForgotPasswordInput(ctx context.Contex var err error ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email")) - data, err := ec.unmarshalNString2string(ctx, v) + data, err := ec.unmarshalOString2áš–string(ctx, v) if err != nil { return it, err } it.Email = data + case "phone_number": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("phone_number")) + data, err := ec.unmarshalOString2áš–string(ctx, v) + if err != nil { + return it, err + } + it.PhoneNumber = data case "state": var err error diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index 45f5889..a498ce1 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -138,7 +138,8 @@ type Error struct { } type ForgotPasswordInput struct { - Email string `json:"email"` + Email *string `json:"email,omitempty"` + PhoneNumber *string `json:"phone_number,omitempty"` State *string `json:"state,omitempty"` RedirectURI *string `json:"redirect_uri,omitempty"` } diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index 07d3678..6df16de 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -449,7 +449,8 @@ input UpdateUserInput { } input ForgotPasswordInput { - email: String! + email: String + phone_number: String state: String redirect_uri: String } diff --git a/server/resolvers/forgot_password.go b/server/resolvers/forgot_password.go index 028ff11..dee8563 100644 --- a/server/resolvers/forgot_password.go +++ b/server/resolvers/forgot_password.go @@ -11,14 +11,13 @@ 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" + mailService "github.com/authorizerdev/authorizer/server/email" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/parsers" "github.com/authorizerdev/authorizer/server/refs" "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" - "github.com/authorizerdev/authorizer/server/validators" ) // ForgotPasswordResolver is a resolver for forgot password mutation @@ -36,71 +35,103 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu log.Debug("Error getting basic auth disabled: ", err) isBasicAuthDisabled = true } - if isBasicAuthDisabled { - log.Debug("Basic authentication is disabled") + isEmailVerificationDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) + if err != nil { + log.Debug("Error getting basic auth disabled: ", err) + isEmailVerificationDisabled = true + } + + isMobileBasicAuthDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableMobileBasicAuthentication) + if err != nil { + log.Debug("Error getting mobile basic auth disabled: ", err) + isMobileBasicAuthDisabled = true + } + isMobileVerificationDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisablePhoneVerification) + if err != nil { + log.Debug("Error getting mobile basic auth disabled: ", err) + isMobileVerificationDisabled = true + } + + email := refs.StringValue(params.Email) + phoneNumber := refs.StringValue(params.PhoneNumber) + if email == "" && phoneNumber == "" { + log.Debug("Email or phone number is required") + return res, fmt.Errorf(`email or phone number is required`) + } + log := log.WithFields(log.Fields{ + "email": refs.StringValue(params.Email), + "phone_number": refs.StringValue(params.PhoneNumber), + }) + isEmailLogin := email != "" + isMobileLogin := phoneNumber != "" + if isBasicAuthDisabled && isEmailLogin && !isEmailVerificationDisabled { + 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") + if isMobileBasicAuthDisabled && isMobileLogin && !isMobileVerificationDisabled { + log.Debug("Mobile basic authentication is disabled.") + return res, fmt.Errorf(`mobile basic authentication is disabled for this instance`) + } + var user *models.User + if isEmailLogin { + user, err = db.Provider.GetUserByEmail(ctx, email) + } else { + user, err = db.Provider.GetUserByPhoneNumber(ctx, phoneNumber) } - - 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`) + log.Debug("Failed to get user: ", err) + return res, fmt.Errorf(`bad user credentials`) } - 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) + if user.RevokedTimestamp != nil { + log.Debug("User access is revoked") + return res, fmt.Errorf(`user access has been revoked`) + } + if isEmailLogin { + 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(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: email, + 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 mailService.SendEmail([]string{email}, constants.VerificationTypeForgotPassword, map[string]interface{}{ + "user": user.ToMap(), + "organization": utils.GetOrganization(), + "verification_url": utils.GetForgotPasswordURL(verificationToken, redirectURI), + }) } - - 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 + if isMobileLogin { + // TODO: send sms } - _, 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 - } - - // 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), - }) - res = &model.Response{ Message: `Please check your inbox! We have sent a password reset link.`, } diff --git a/server/resolvers/login.go b/server/resolvers/login.go index 708e3b4..b940370 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -16,6 +16,7 @@ import ( "github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db/models" + mailService "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" @@ -23,8 +24,6 @@ import ( "github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/validators" - - mailService "github.com/authorizerdev/authorizer/server/email" ) // LoginResolver is a resolver for login mutation