From 4e3d73e76761943e63dfd80b8bff50eef1cba3bb Mon Sep 17 00:00:00 2001 From: anik-ghosh-au7 Date: Fri, 29 Jul 2022 13:49:46 +0530 Subject: [PATCH] feat: otp resolvers updated --- server/graph/generated/generated.go | 109 ++++++++++++++++++++++++++++ server/graph/model/models_gen.go | 4 + server/graph/schema.graphqls | 5 ++ server/graph/schema.resolvers.go | 4 + server/resolvers/login.go | 6 ++ server/resolvers/resend_otp.go | 59 +++++++++++++++ server/resolvers/verify_otp.go | 11 ++- 7 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 server/resolvers/resend_otp.go diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index 535821f..44c181f 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -159,6 +159,7 @@ type ComplexityRoot struct { Login func(childComplexity int, params model.LoginInput) int Logout func(childComplexity int) int MagicLinkLogin func(childComplexity int, params model.MagicLinkLoginInput) int + ResendOtp func(childComplexity int, params model.ResendOTPRequest) int ResendVerifyEmail func(childComplexity int, params model.ResendVerifyEmailInput) int ResetPassword func(childComplexity int, params model.ResetPasswordInput) int Revoke func(childComplexity int, params model.OAuthRevokeInput) int @@ -296,6 +297,7 @@ type MutationResolver interface { ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) Revoke(ctx context.Context, params model.OAuthRevokeInput) (*model.Response, error) VerifyOtp(ctx context.Context, params model.VerifyOTPRequest) (*model.AuthResponse, error) + ResendOtp(ctx context.Context, params model.ResendOTPRequest) (*model.Response, error) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, error) AdminSignup(ctx context.Context, params model.AdminSignupInput) (*model.Response, error) @@ -1067,6 +1069,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.MagicLinkLogin(childComplexity, args["params"].(model.MagicLinkLoginInput)), true + case "Mutation.resend_otp": + if e.complexity.Mutation.ResendOtp == nil { + break + } + + args, err := ec.field_Mutation_resend_otp_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.ResendOtp(childComplexity, args["params"].(model.ResendOTPRequest)), true + case "Mutation.resend_verify_email": if e.complexity.Mutation.ResendVerifyEmail == nil { break @@ -2246,6 +2260,10 @@ input VerifyOTPRequest { otp: String! } +input ResendOTPRequest { + email: String! +} + type Mutation { signup(params: SignUpInput!): AuthResponse! login(params: LoginInput!): AuthResponse! @@ -2258,6 +2276,7 @@ type Mutation { reset_password(params: ResetPasswordInput!): Response! revoke(params: OAuthRevokeInput!): Response! verify_otp(params: VerifyOTPRequest!): AuthResponse! + resend_otp(params: ResendOTPRequest!): Response! # admin only apis _delete_user(params: DeleteUserInput!): Response! _update_user(params: UpdateUserInput!): User! @@ -2586,6 +2605,21 @@ func (ec *executionContext) field_Mutation_magic_link_login_args(ctx context.Con return args, nil } +func (ec *executionContext) field_Mutation_resend_otp_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 model.ResendOTPRequest + if tmp, ok := rawArgs["params"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params")) + arg0, err = ec.unmarshalNResendOTPRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResendOTPRequest(ctx, tmp) + if err != nil { + return nil, err + } + } + args["params"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_resend_verify_email_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -5934,6 +5968,48 @@ func (ec *executionContext) _Mutation_verify_otp(ctx context.Context, field grap return ec.marshalNAuthResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAuthResponse(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation_resend_otp(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_resend_otp_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().ResendOtp(rctx, args["params"].(model.ResendOTPRequest)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.Response) + fc.Result = res + return ec.marshalNResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res) +} + func (ec *executionContext) _Mutation__delete_user(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -10705,6 +10781,29 @@ func (ec *executionContext) unmarshalInputPaginationInput(ctx context.Context, o return it, nil } +func (ec *executionContext) unmarshalInputResendOTPRequest(ctx context.Context, obj interface{}) (model.ResendOTPRequest, error) { + var it model.ResendOTPRequest + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + for k, v := range asMap { + switch k { + case "email": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email")) + it.Email, err = ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputResendVerifyEmailInput(ctx context.Context, obj interface{}) (model.ResendVerifyEmailInput, error) { var it model.ResendVerifyEmailInput asMap := map[string]interface{}{} @@ -12255,6 +12354,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { invalids++ } + case "resend_otp": + out.Values[i] = ec._Mutation_resend_otp(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } case "_delete_user": out.Values[i] = ec._Mutation__delete_user(ctx, field) if out.Values[i] == graphql.Null { @@ -13484,6 +13588,11 @@ func (ec *executionContext) marshalNPagination2ᚖgithubᚗcomᚋauthorizerdev return ec._Pagination(ctx, sel, v) } +func (ec *executionContext) unmarshalNResendOTPRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResendOTPRequest(ctx context.Context, v interface{}) (model.ResendOTPRequest, error) { + res, err := ec.unmarshalInputResendOTPRequest(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalNResendVerifyEmailInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResendVerifyEmailInput(ctx context.Context, v interface{}) (model.ResendVerifyEmailInput, error) { res, err := ec.unmarshalInputResendVerifyEmailInput(ctx, v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index bbdb2f7..b193c0d 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -185,6 +185,10 @@ type PaginationInput struct { Page *int64 `json:"page"` } +type ResendOTPRequest struct { + Email string `json:"email"` +} + type ResendVerifyEmailInput struct { Email string `json:"email"` Identifier string `json:"identifier"` diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index 3a843fe..e3d6908 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -427,6 +427,10 @@ input VerifyOTPRequest { otp: String! } +input ResendOTPRequest { + email: String! +} + type Mutation { signup(params: SignUpInput!): AuthResponse! login(params: LoginInput!): AuthResponse! @@ -439,6 +443,7 @@ type Mutation { reset_password(params: ResetPasswordInput!): Response! revoke(params: OAuthRevokeInput!): Response! verify_otp(params: VerifyOTPRequest!): AuthResponse! + resend_otp(params: ResendOTPRequest!): Response! # admin only apis _delete_user(params: DeleteUserInput!): Response! _update_user(params: UpdateUserInput!): User! diff --git a/server/graph/schema.resolvers.go b/server/graph/schema.resolvers.go index e224137..8d5b55b 100644 --- a/server/graph/schema.resolvers.go +++ b/server/graph/schema.resolvers.go @@ -55,6 +55,10 @@ func (r *mutationResolver) VerifyOtp(ctx context.Context, params model.VerifyOTP return resolvers.VerifyOtpResolver(ctx, params) } +func (r *mutationResolver) ResendOtp(ctx context.Context, params model.ResendOTPRequest) (*model.Response, error) { + return resolvers.ResendOTPResolver(ctx, params) +} + func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) { return resolvers.DeleteUserResolver(ctx, params) } diff --git a/server/resolvers/login.go b/server/resolvers/login.go index c163528..c7fafe3 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -99,6 +99,12 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes } if refs.BoolValue(user.IsMultiFactorAuthEnabled) { + //TODO - send email based on email config + db.Provider.UpsertOTP(ctx, &models.OTP{ + Email: user.Email, + Otp: utils.GenerateOTP(), + ExpiresAt: time.Now().Add(1 * time.Minute).Unix(), + }) return &model.AuthResponse{ Message: "Please check the OTP in your inbox", ShouldShowOtpScreen: refs.NewBoolRef(true), diff --git a/server/resolvers/resend_otp.go b/server/resolvers/resend_otp.go new file mode 100644 index 0000000..1eb1333 --- /dev/null +++ b/server/resolvers/resend_otp.go @@ -0,0 +1,59 @@ +package resolvers + +import ( + "context" + "fmt" + "strings" + "time" + + log "github.com/sirupsen/logrus" + + "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/refs" + "github.com/authorizerdev/authorizer/server/utils" +) + +// ResendOTPResolver is a resolver for resend otp mutation +func ResendOTPResolver(ctx context.Context, params model.ResendOTPRequest) (*model.Response, error) { + var res *model.Response + + log := log.WithFields(log.Fields{ + "email": params.Email, + }) + params.Email = strings.ToLower(params.Email) + user, err := db.Provider.GetUserByEmail(ctx, params.Email) + if err != nil { + log.Debug("Failed to get user by email: ", err) + return res, fmt.Errorf(`user with this email not found`) + } + + if user.RevokedTimestamp != nil { + log.Debug("User access is revoked") + return res, fmt.Errorf(`user access has been revoked`) + } + + if user.EmailVerifiedAt == nil { + log.Debug("User email is not verified") + return res, fmt.Errorf(`email not verified`) + } + + if !refs.BoolValue(user.IsMultiFactorAuthEnabled) { + log.Debug("User multi factor authentication is not enabled") + return res, fmt.Errorf(`multi factor authentication not enabled`) + } + + //TODO - send email based on email config + db.Provider.UpsertOTP(ctx, &models.OTP{ + Email: user.Email, + Otp: utils.GenerateOTP(), + ExpiresAt: time.Now().Add(1 * time.Minute).Unix(), + }) + + res = &model.Response{ + Message: `OTP has been sent. Please check your inbox`, + } + + return res, nil +} diff --git a/server/resolvers/verify_otp.go b/server/resolvers/verify_otp.go index f252dd0..95bb78d 100644 --- a/server/resolvers/verify_otp.go +++ b/server/resolvers/verify_otp.go @@ -32,11 +32,16 @@ func VerifyOtpResolver(ctx context.Context, params model.VerifyOTPRequest) (*mod return res, fmt.Errorf(`invalid email: %s`, err.Error()) } + if params.Otp != otp.Otp { + log.Debug("Failed to verify otp request: Incorrect value") + return res, fmt.Errorf(`invalid otp`) + } + expiresIn := otp.ExpiresAt - time.Now().Unix() - if params.Otp != otp.Otp || expiresIn < 0 { - log.Debug("Failed to verify otp request: ", err) - return res, fmt.Errorf(`invalid otp: %s`, err.Error()) + if expiresIn < 0 { + log.Debug("Failed to verify otp request: Timeout") + return res, fmt.Errorf("otp expired") } user, err := db.Provider.GetUserByEmail(ctx, params.Email)