From 2840a085cabe14de558a38648b5aa1ecaa32de37 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sun, 18 Jul 2021 12:56:17 +0530 Subject: [PATCH] Add resolver to resend verify email link Resolves #20 --- server/db/db.go | 1 + server/db/verificationRequests.go | 12 +++ server/graph/generated/generated.go | 116 ++++++++++++++++++++++- server/graph/model/models_gen.go | 4 + server/graph/schema.graphqls | 5 + server/graph/schema.resolvers.go | 4 + server/resolvers/resendVerifyEmail.go | 49 ++++++++++ server/resolvers/verificationRequests.go | 13 +-- 8 files changed, 193 insertions(+), 11 deletions(-) create mode 100644 server/resolvers/resendVerifyEmail.go diff --git a/server/db/db.go b/server/db/db.go index 90ae531..1362ab8 100644 --- a/server/db/db.go +++ b/server/db/db.go @@ -22,6 +22,7 @@ type Manager interface { GetVerificationByToken(token string) (VerificationRequest, error) DeleteToken(email string) error GetVerificationRequests() ([]VerificationRequest, error) + GetVerificationByEmail(email string) (VerificationRequest, error) } type manager struct { diff --git a/server/db/verificationRequests.go b/server/db/verificationRequests.go index e59d6c6..c99fe1d 100644 --- a/server/db/verificationRequests.go +++ b/server/db/verificationRequests.go @@ -41,6 +41,18 @@ func (mgr *manager) GetVerificationByToken(token string) (VerificationRequest, e return verification, nil } +func (mgr *manager) GetVerificationByEmail(email string) (VerificationRequest, error) { + var verification VerificationRequest + result := mgr.db.Where("email = ?", email).First(&verification) + + if result.Error != nil { + log.Println(`Error getting verification token:`, result.Error) + return verification, result.Error + } + + return verification, nil +} + func (mgr *manager) DeleteToken(email string) error { var verification VerificationRequest result := mgr.db.Where("email = ?", email).Delete(&verification) diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index da666f6..7d59191 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -56,11 +56,12 @@ type ComplexityRoot struct { } Mutation struct { - Login func(childComplexity int, params model.LoginInput) int - Logout func(childComplexity int) int - Signup func(childComplexity int, params model.SignUpInput) int - UpdateProfile func(childComplexity int, params model.UpdateProfileInput) int - VerifyEmail func(childComplexity int, params model.VerifyEmailInput) int + Login func(childComplexity int, params model.LoginInput) int + Logout func(childComplexity int) int + ResendVerifyEmail func(childComplexity int, params model.ResendVerifyEmailInput) int + Signup func(childComplexity int, params model.SignUpInput) int + UpdateProfile func(childComplexity int, params model.UpdateProfileInput) int + VerifyEmail func(childComplexity int, params model.VerifyEmailInput) int } Query struct { @@ -104,6 +105,7 @@ type MutationResolver interface { Logout(ctx context.Context) (*model.Response, error) UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model.Response, error) VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.LoginResponse, error) + ResendVerifyEmail(ctx context.Context, params model.ResendVerifyEmailInput) (*model.Response, error) } type QueryResolver interface { Users(ctx context.Context) ([]*model.User, error) @@ -188,6 +190,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.Logout(childComplexity), true + case "Mutation.resendVerifyEmail": + if e.complexity.Mutation.ResendVerifyEmail == nil { + break + } + + args, err := ec.field_Mutation_resendVerifyEmail_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.ResendVerifyEmail(childComplexity, args["params"].(model.ResendVerifyEmailInput)), true + case "Mutation.signup": if e.complexity.Mutation.Signup == nil { break @@ -504,6 +518,10 @@ input VerifyEmailInput { token: String! } +input ResendVerifyEmailInput { + email: String! +} + input UpdateProfileInput { oldPassword: String newPassword: String @@ -520,6 +538,7 @@ type Mutation { logout: Response! updateProfile(params: UpdateProfileInput!): Response! verifyEmail(params: VerifyEmailInput!): LoginResponse! + resendVerifyEmail(params: ResendVerifyEmailInput!): Response! } type Query { @@ -551,6 +570,21 @@ func (ec *executionContext) field_Mutation_login_args(ctx context.Context, rawAr return args, nil } +func (ec *executionContext) field_Mutation_resendVerifyEmail_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 model.ResendVerifyEmailInput + if tmp, ok := rawArgs["params"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params")) + arg0, err = ec.unmarshalNResendVerifyEmailInput2githubᚗcomᚋyauthdevᚋyauthᚋserverᚋgraphᚋmodelᚐResendVerifyEmailInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["params"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_signup_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -1053,6 +1087,48 @@ func (ec *executionContext) _Mutation_verifyEmail(ctx context.Context, field gra return ec.marshalNLoginResponse2ᚖgithubᚗcomᚋyauthdevᚋyauthᚋserverᚋgraphᚋmodelᚐLoginResponse(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation_resendVerifyEmail(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_resendVerifyEmail_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().ResendVerifyEmail(rctx, args["params"].(model.ResendVerifyEmailInput)) + }) + 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ᚋyauthdevᚋyauthᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res) +} + func (ec *executionContext) _Query_users(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -2967,6 +3043,26 @@ func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj in return it, nil } +func (ec *executionContext) unmarshalInputResendVerifyEmailInput(ctx context.Context, obj interface{}) (model.ResendVerifyEmailInput, error) { + var it model.ResendVerifyEmailInput + var asMap = obj.(map[string]interface{}) + + 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) unmarshalInputSignUpInput(ctx context.Context, obj interface{}) (model.SignUpInput, error) { var it model.SignUpInput var asMap = obj.(map[string]interface{}) @@ -3228,6 +3324,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { invalids++ } + case "resendVerifyEmail": + out.Values[i] = ec._Mutation_resendVerifyEmail(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -3733,6 +3834,11 @@ func (ec *executionContext) marshalNLoginResponse2ᚖgithubᚗcomᚋyauthdevᚋy return ec._LoginResponse(ctx, sel, v) } +func (ec *executionContext) unmarshalNResendVerifyEmailInput2githubᚗcomᚋyauthdevᚋyauthᚋserverᚋgraphᚋmodelᚐResendVerifyEmailInput(ctx context.Context, v interface{}) (model.ResendVerifyEmailInput, error) { + res, err := ec.unmarshalInputResendVerifyEmailInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) marshalNResponse2githubᚗcomᚋyauthdevᚋyauthᚋserverᚋgraphᚋmodelᚐResponse(ctx context.Context, sel ast.SelectionSet, v model.Response) graphql.Marshaler { return ec._Response(ctx, sel, &v) } diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index 9425dc1..1f1d2bf 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -19,6 +19,10 @@ type LoginResponse struct { User *User `json:"user"` } +type ResendVerifyEmailInput struct { + Email string `json:"email"` +} + type Response struct { Message string `json:"message"` } diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index 0d3add9..42bf6a0 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -60,6 +60,10 @@ input VerifyEmailInput { token: String! } +input ResendVerifyEmailInput { + email: String! +} + input UpdateProfileInput { oldPassword: String newPassword: String @@ -76,6 +80,7 @@ type Mutation { logout: Response! updateProfile(params: UpdateProfileInput!): Response! verifyEmail(params: VerifyEmailInput!): LoginResponse! + resendVerifyEmail(params: ResendVerifyEmailInput!): Response! } type Query { diff --git a/server/graph/schema.resolvers.go b/server/graph/schema.resolvers.go index 34914aa..5e623e3 100644 --- a/server/graph/schema.resolvers.go +++ b/server/graph/schema.resolvers.go @@ -31,6 +31,10 @@ func (r *mutationResolver) VerifyEmail(ctx context.Context, params model.VerifyE return resolvers.VerifyEmail(ctx, params) } +func (r *mutationResolver) ResendVerifyEmail(ctx context.Context, params model.ResendVerifyEmailInput) (*model.Response, error) { + return resolvers.ResendVerifyEmail(ctx, params) +} + func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) { return resolvers.Users(ctx) } diff --git a/server/resolvers/resendVerifyEmail.go b/server/resolvers/resendVerifyEmail.go new file mode 100644 index 0000000..f9e20b4 --- /dev/null +++ b/server/resolvers/resendVerifyEmail.go @@ -0,0 +1,49 @@ +package resolvers + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "github.com/yauthdev/yauth/server/db" + "github.com/yauthdev/yauth/server/graph/model" + "github.com/yauthdev/yauth/server/utils" +) + +func ResendVerifyEmail(ctx context.Context, params model.ResendVerifyEmailInput) (*model.Response, error) { + var res *model.Response + params.Email = strings.ToLower(params.Email) + + if !utils.IsValidEmail(params.Email) { + return res, fmt.Errorf("invalid email") + } + + verificationRequest, err := db.Mgr.GetVerificationByEmail(params.Email) + if err != nil { + return res, fmt.Errorf(`verification request not found`) + } + + token, err := utils.CreateVerificationToken(params.Email, verificationRequest.Identifier) + if err != nil { + log.Println(`Error generating token`, err) + } + db.Mgr.AddVerification(db.VerificationRequest{ + Token: token, + Identifier: verificationRequest.Identifier, + ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), + Email: params.Email, + }) + + // exec it as go routin so that we can reduce the api latency + go func() { + utils.SendVerificationMail(params.Email, token) + }() + + res = &model.Response{ + Message: `Verification email has been sent. Please check your inbox`, + } + + return res, nil +} diff --git a/server/resolvers/verificationRequests.go b/server/resolvers/verificationRequests.go index 18683bf..3ffb539 100644 --- a/server/resolvers/verificationRequests.go +++ b/server/resolvers/verificationRequests.go @@ -27,12 +27,13 @@ func VerificationRequests(ctx context.Context) ([]*model.VerificationRequest, er for _, verificationRequest := range verificationRequests { res = append(res, &model.VerificationRequest{ - ID: fmt.Sprintf("%d", verificationRequest.ID), - Email: &verificationRequest.Email, - Token: &verificationRequest.Token, - Expires: &verificationRequest.ExpiresAt, - CreatedAt: &verificationRequest.CreatedAt, - UpdatedAt: &verificationRequest.UpdatedAt, + ID: fmt.Sprintf("%d", verificationRequest.ID), + Email: &verificationRequest.Email, + Token: &verificationRequest.Token, + Identifier: &verificationRequest.Identifier, + Expires: &verificationRequest.ExpiresAt, + CreatedAt: &verificationRequest.CreatedAt, + UpdatedAt: &verificationRequest.UpdatedAt, }) }