feat: add forgot password for mobile login

This commit is contained in:
Lakhan Samani 2023-12-03 22:49:40 +05:30
parent 5b75521490
commit c95db8b07b
5 changed files with 102 additions and 60 deletions

View File

@ -2791,7 +2791,8 @@ input UpdateUserInput {
} }
input ForgotPasswordInput { input ForgotPasswordInput {
email: String! email: String
phone_number: String
state: String state: String
redirect_uri: String redirect_uri: String
} }
@ -16821,7 +16822,7 @@ func (ec *executionContext) unmarshalInputForgotPasswordInput(ctx context.Contex
asMap[k] = v asMap[k] = v
} }
fieldsInOrder := [...]string{"email", "state", "redirect_uri"} fieldsInOrder := [...]string{"email", "phone_number", "state", "redirect_uri"}
for _, k := range fieldsInOrder { for _, k := range fieldsInOrder {
v, ok := asMap[k] v, ok := asMap[k]
if !ok { if !ok {
@ -16832,11 +16833,20 @@ func (ec *executionContext) unmarshalInputForgotPasswordInput(ctx context.Contex
var err error var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email")) ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email"))
data, err := ec.unmarshalNString2string(ctx, v) data, err := ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil { if err != nil {
return it, err return it, err
} }
it.Email = data 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": case "state":
var err error var err error

View File

@ -138,7 +138,8 @@ type Error struct {
} }
type ForgotPasswordInput struct { type ForgotPasswordInput struct {
Email string `json:"email"` Email *string `json:"email,omitempty"`
PhoneNumber *string `json:"phone_number,omitempty"`
State *string `json:"state,omitempty"` State *string `json:"state,omitempty"`
RedirectURI *string `json:"redirect_uri,omitempty"` RedirectURI *string `json:"redirect_uri,omitempty"`
} }

View File

@ -449,7 +449,8 @@ input UpdateUserInput {
} }
input ForgotPasswordInput { input ForgotPasswordInput {
email: String! email: String
phone_number: String
state: String state: String
redirect_uri: String redirect_uri: String
} }

View File

@ -11,14 +11,13 @@ import (
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models" "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/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/parsers" "github.com/authorizerdev/authorizer/server/parsers"
"github.com/authorizerdev/authorizer/server/refs" "github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"github.com/authorizerdev/authorizer/server/validators"
) )
// ForgotPasswordResolver is a resolver for forgot password mutation // 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) log.Debug("Error getting basic auth disabled: ", err)
isBasicAuthDisabled = true isBasicAuthDisabled = true
} }
if isBasicAuthDisabled { isEmailVerificationDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification)
log.Debug("Basic authentication is disabled") 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`) return res, fmt.Errorf(`basic authentication is disabled for this instance`)
} }
params.Email = strings.ToLower(params.Email) if isMobileBasicAuthDisabled && isMobileLogin && !isMobileVerificationDisabled {
log.Debug("Mobile basic authentication is disabled.")
if !validators.IsValidEmail(params.Email) { return res, fmt.Errorf(`mobile basic authentication is disabled for this instance`)
log.Debug("Invalid email address: ", params.Email) }
return res, fmt.Errorf("invalid email") 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 { if err != nil {
log.Debug("User not found: ", err) log.Debug("Failed to get user: ", err)
return res, fmt.Errorf(`user with this email not found`) return res, fmt.Errorf(`bad user credentials`)
} }
hostname := parsers.GetHost(gc) hostname := parsers.GetHost(gc)
_, nonceHash, err := utils.GenerateNonce() _, nonceHash, err := utils.GenerateNonce()
if err != nil { if err != nil {
log.Debug("Failed to generate nonce: ", err) log.Debug("Failed to generate nonce: ", err)
return res, err return res, err
} }
if user.RevokedTimestamp != nil {
redirectURI := "" log.Debug("User access is revoked")
// give higher preference to params redirect uri return res, fmt.Errorf(`user access has been revoked`)
if strings.TrimSpace(refs.StringValue(params.RedirectURI)) != "" { }
redirectURI = refs.StringValue(params.RedirectURI) if isEmailLogin {
} else { redirectURI := ""
redirectURI, err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL) // give higher preference to params redirect uri
if err != nil { if strings.TrimSpace(refs.StringValue(params.RedirectURI)) != "" {
log.Debug("ResetPasswordURL not found using default app url: ", err) redirectURI = refs.StringValue(params.RedirectURI)
redirectURI = hostname + "/app/reset-password" } else {
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyResetPasswordURL, redirectURI) 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),
})
} }
if isMobileLogin {
verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname, nonceHash, redirectURI) // TODO: send sms
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
}
// 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{ res = &model.Response{
Message: `Please check your inbox! We have sent a password reset link.`, Message: `Please check your inbox! We have sent a password reset link.`,
} }

View File

@ -16,6 +16,7 @@ import (
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models" "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/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/refs" "github.com/authorizerdev/authorizer/server/refs"
@ -23,8 +24,6 @@ import (
"github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"github.com/authorizerdev/authorizer/server/validators" "github.com/authorizerdev/authorizer/server/validators"
mailService "github.com/authorizerdev/authorizer/server/email"
) )
// LoginResolver is a resolver for login mutation // LoginResolver is a resolver for login mutation