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 {
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

View File

@ -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"`
}

View File

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

View File

@ -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.`,
}

View File

@ -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