From 7d17032fc236b12af068536d02defd5ba1217315 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sun, 18 Jul 2021 09:25:20 +0530 Subject: [PATCH] Add resolver to update profile Resolves #12 #11 --- server/db/db.go | 1 + server/db/user.go | 23 +- server/enum/verification.go | 15 + server/graph/generated/generated.go | 432 ++++++++++-------- server/graph/model/models_gen.go | 13 +- server/graph/schema.graphqls | 22 +- server/graph/schema.resolvers.go | 14 +- server/resolvers/login.go | 9 +- server/resolvers/profile.go | 7 + server/resolvers/signup.go | 18 +- server/resolvers/token.go | 5 +- server/resolvers/updateProfile.go | 136 ++++++ .../{verifySignUpToken.go => verifyEmail.go} | 7 +- server/utils/authToken.go | 4 +- 14 files changed, 460 insertions(+), 246 deletions(-) create mode 100644 server/enum/verification.go create mode 100644 server/resolvers/updateProfile.go rename server/resolvers/{verifySignUpToken.go => verifyEmail.go} (89%) diff --git a/server/db/db.go b/server/db/db.go index dddc4e7..fc3afd4 100644 --- a/server/db/db.go +++ b/server/db/db.go @@ -14,6 +14,7 @@ import ( type Manager interface { SaveUser(user User) (User, error) + UpdateUser(user User) (User, error) GetUsers() ([]User, error) GetUserByEmail(email string) (User, error) UpdateVerificationTime(verifiedAt int64, id uint) error diff --git a/server/db/user.go b/server/db/user.go index 6929290..60fc483 100644 --- a/server/db/user.go +++ b/server/db/user.go @@ -19,9 +19,28 @@ type User struct { Image string } -// SaveUser function to add user +// SaveUser function to add user even with email conflict func (mgr *manager) SaveUser(user User) (User, error) { - result := mgr.db.Clauses(clause.OnConflict{UpdateAll: true, Columns: []clause.Column{{Name: "email"}}}).Create(&user) + result := mgr.db.Clauses( + clause.OnConflict{ + UpdateAll: true, + Columns: []clause.Column{{Name: "email"}}, + }).Create(&user) + + if result.Error != nil { + log.Println(result.Error) + return user, result.Error + } + return user, nil +} + +// UpdateUser function to update user with ID conflict +func (mgr *manager) UpdateUser(user User) (User, error) { + result := mgr.db.Clauses( + clause.OnConflict{ + UpdateAll: true, + Columns: []clause.Column{{Name: "id"}}, + }).Create(&user) if result.Error != nil { log.Println(result.Error) diff --git a/server/enum/verification.go b/server/enum/verification.go new file mode 100644 index 0000000..af90671 --- /dev/null +++ b/server/enum/verification.go @@ -0,0 +1,15 @@ +package enum + +type VerificationType int + +const ( + BasicAuthSignup VerificationType = iota + UpdateEmail +) + +func (d VerificationType) String() string { + return [...]string{ + "basic_auth_signup", + "update_email", + }[d] +} diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index 5d91dd6..0de4a42 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -56,10 +56,11 @@ 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 - VerifySignupToken func(childComplexity int, params model.VerifySignupTokenInput) int + 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 } Query struct { @@ -72,11 +73,6 @@ type ComplexityRoot struct { Message func(childComplexity int) int } - SignUpResponse struct { - Message func(childComplexity int) int - User func(childComplexity int) int - } - User struct { CreatedAt func(childComplexity int) int Email func(childComplexity int) int @@ -102,10 +98,11 @@ type ComplexityRoot struct { } type MutationResolver interface { - Signup(ctx context.Context, params model.SignUpInput) (*model.SignUpResponse, error) - VerifySignupToken(ctx context.Context, params model.VerifySignupTokenInput) (*model.LoginResponse, error) + Signup(ctx context.Context, params model.SignUpInput) (*model.Response, error) Login(ctx context.Context, params model.LoginInput) (*model.LoginResponse, error) 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) } type QueryResolver interface { Users(ctx context.Context) ([]*model.User, error) @@ -201,17 +198,29 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.Signup(childComplexity, args["params"].(model.SignUpInput)), true - case "Mutation.verifySignupToken": - if e.complexity.Mutation.VerifySignupToken == nil { + case "Mutation.updateProfile": + if e.complexity.Mutation.UpdateProfile == nil { break } - args, err := ec.field_Mutation_verifySignupToken_args(context.TODO(), rawArgs) + args, err := ec.field_Mutation_updateProfile_args(context.TODO(), rawArgs) if err != nil { return 0, false } - return e.complexity.Mutation.VerifySignupToken(childComplexity, args["params"].(model.VerifySignupTokenInput)), true + return e.complexity.Mutation.UpdateProfile(childComplexity, args["params"].(model.UpdateProfileInput)), true + + case "Mutation.verifyEmail": + if e.complexity.Mutation.VerifyEmail == nil { + break + } + + args, err := ec.field_Mutation_verifyEmail_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.VerifyEmail(childComplexity, args["params"].(model.VerifyEmailInput)), true case "Query.profile": if e.complexity.Query.Profile == nil { @@ -241,20 +250,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Response.Message(childComplexity), true - case "SignUpResponse.message": - if e.complexity.SignUpResponse.Message == nil { - break - } - - return e.complexity.SignUpResponse.Message(childComplexity), true - - case "SignUpResponse.user": - if e.complexity.SignUpResponse.User == nil { - break - } - - return e.complexity.SignUpResponse.User(childComplexity), true - case "User.createdAt": if e.complexity.User.CreatedAt == nil { break @@ -478,11 +473,6 @@ type LoginResponse { user: User } -type SignUpResponse { - message: String! - user: User -} - type Response { message: String! } @@ -501,15 +491,26 @@ input LoginInput { password: String! } -input VerifySignupTokenInput { +input VerifyEmailInput { token: String! } +input UpdateProfileInput { + oldPassword: String + newPassword: String + confirmNewPassword: String + firstName: String + lastName: String + image: String + email: String +} + type Mutation { - signup(params: SignUpInput!): SignUpResponse! - verifySignupToken(params: VerifySignupTokenInput!): LoginResponse! + signup(params: SignUpInput!): Response! login(params: LoginInput!): LoginResponse! logout: Response! + updateProfile(params: UpdateProfileInput!): Response! + verifyEmail(params: VerifyEmailInput!): LoginResponse! } type Query { @@ -555,13 +556,28 @@ func (ec *executionContext) field_Mutation_signup_args(ctx context.Context, rawA return args, nil } -func (ec *executionContext) field_Mutation_verifySignupToken_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { +func (ec *executionContext) field_Mutation_updateProfile_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} - var arg0 model.VerifySignupTokenInput + var arg0 model.UpdateProfileInput if tmp, ok := rawArgs["params"]; ok { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params")) - arg0, err = ec.unmarshalNVerifySignupTokenInput2githubᚗcomᚋyauthdevᚋyauthᚋserverᚋgraphᚋmodelᚐVerifySignupTokenInput(ctx, tmp) + arg0, err = ec.unmarshalNUpdateProfileInput2githubᚗcomᚋyauthdevᚋyauthᚋserverᚋgraphᚋmodelᚐUpdateProfileInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["params"] = arg0 + return args, nil +} + +func (ec *executionContext) field_Mutation_verifyEmail_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 model.VerifyEmailInput + if tmp, ok := rawArgs["params"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params")) + arg0, err = ec.unmarshalNVerifyEmailInput2githubᚗcomᚋyauthdevᚋyauthᚋserverᚋgraphᚋmodelᚐVerifyEmailInput(ctx, tmp) if err != nil { return nil, err } @@ -861,51 +877,9 @@ func (ec *executionContext) _Mutation_signup(ctx context.Context, field graphql. } return graphql.Null } - res := resTmp.(*model.SignUpResponse) + res := resTmp.(*model.Response) fc.Result = res - return ec.marshalNSignUpResponse2ᚖgithubᚗcomᚋyauthdevᚋyauthᚋserverᚋgraphᚋmodelᚐSignUpResponse(ctx, field.Selections, res) -} - -func (ec *executionContext) _Mutation_verifySignupToken(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_verifySignupToken_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().VerifySignupToken(rctx, args["params"].(model.VerifySignupTokenInput)) - }) - 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.LoginResponse) - fc.Result = res - return ec.marshalNLoginResponse2ᚖgithubᚗcomᚋyauthdevᚋyauthᚋserverᚋgraphᚋmodelᚐLoginResponse(ctx, field.Selections, res) + return ec.marshalNResponse2ᚖgithubᚗcomᚋyauthdevᚋyauthᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res) } func (ec *executionContext) _Mutation_login(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { @@ -985,6 +959,90 @@ func (ec *executionContext) _Mutation_logout(ctx context.Context, field graphql. return ec.marshalNResponse2ᚖgithubᚗcomᚋyauthdevᚋyauthᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation_updateProfile(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_updateProfile_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().UpdateProfile(rctx, args["params"].(model.UpdateProfileInput)) + }) + 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) _Mutation_verifyEmail(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_verifyEmail_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().VerifyEmail(rctx, args["params"].(model.VerifyEmailInput)) + }) + 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.LoginResponse) + fc.Result = res + return ec.marshalNLoginResponse2ᚖgithubᚗcomᚋyauthdevᚋyauthᚋserverᚋgraphᚋmodelᚐLoginResponse(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 { @@ -1193,73 +1251,6 @@ func (ec *executionContext) _Response_message(ctx context.Context, field graphql return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _SignUpResponse_message(ctx context.Context, field graphql.CollectedField, obj *model.SignUpResponse) (ret graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "SignUpResponse", - Field: field, - Args: nil, - IsMethod: false, - IsResolver: false, - } - - ctx = graphql.WithFieldContext(ctx, fc) - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Message, nil - }) - 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.(string) - fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) -} - -func (ec *executionContext) _SignUpResponse_user(ctx context.Context, field graphql.CollectedField, obj *model.SignUpResponse) (ret graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "SignUpResponse", - Field: field, - Args: nil, - IsMethod: false, - IsResolver: false, - } - - ctx = graphql.WithFieldContext(ctx, fc) - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.User, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.(*model.User) - fc.Result = res - return ec.marshalOUser2ᚖgithubᚗcomᚋyauthdevᚋyauthᚋserverᚋgraphᚋmodelᚐUser(ctx, field.Selections, res) -} - func (ec *executionContext) _User_id(ctx context.Context, field graphql.CollectedField, obj *model.User) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -2991,8 +2982,76 @@ func (ec *executionContext) unmarshalInputSignUpInput(ctx context.Context, obj i return it, nil } -func (ec *executionContext) unmarshalInputVerifySignupTokenInput(ctx context.Context, obj interface{}) (model.VerifySignupTokenInput, error) { - var it model.VerifySignupTokenInput +func (ec *executionContext) unmarshalInputUpdateProfileInput(ctx context.Context, obj interface{}) (model.UpdateProfileInput, error) { + var it model.UpdateProfileInput + var asMap = obj.(map[string]interface{}) + + for k, v := range asMap { + switch k { + case "oldPassword": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("oldPassword")) + it.OldPassword, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "newPassword": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("newPassword")) + it.NewPassword, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "confirmNewPassword": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("confirmNewPassword")) + it.ConfirmNewPassword, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "firstName": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("firstName")) + it.FirstName, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "lastName": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("lastName")) + it.LastName, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "image": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("image")) + it.Image, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "email": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email")) + it.Email, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + +func (ec *executionContext) unmarshalInputVerifyEmailInput(ctx context.Context, obj interface{}) (model.VerifyEmailInput, error) { + var it model.VerifyEmailInput var asMap = obj.(map[string]interface{}) for k, v := range asMap { @@ -3104,11 +3163,6 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { invalids++ } - case "verifySignupToken": - out.Values[i] = ec._Mutation_verifySignupToken(ctx, field) - if out.Values[i] == graphql.Null { - invalids++ - } case "login": out.Values[i] = ec._Mutation_login(ctx, field) if out.Values[i] == graphql.Null { @@ -3119,6 +3173,16 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { invalids++ } + case "updateProfile": + out.Values[i] = ec._Mutation_updateProfile(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } + case "verifyEmail": + out.Values[i] = ec._Mutation_verifyEmail(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -3226,35 +3290,6 @@ func (ec *executionContext) _Response(ctx context.Context, sel ast.SelectionSet, return out } -var signUpResponseImplementors = []string{"SignUpResponse"} - -func (ec *executionContext) _SignUpResponse(ctx context.Context, sel ast.SelectionSet, obj *model.SignUpResponse) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, signUpResponseImplementors) - - out := graphql.NewFieldSet(fields) - var invalids uint32 - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("SignUpResponse") - case "message": - out.Values[i] = ec._SignUpResponse_message(ctx, field, obj) - if out.Values[i] == graphql.Null { - invalids++ - } - case "user": - out.Values[i] = ec._SignUpResponse_user(ctx, field, obj) - default: - panic("unknown field " + strconv.Quote(field.Name)) - } - } - out.Dispatch() - if invalids > 0 { - return graphql.Null - } - return out -} - var userImplementors = []string{"User"} func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj *model.User) graphql.Marshaler { @@ -3658,20 +3693,6 @@ func (ec *executionContext) unmarshalNSignUpInput2githubᚗcomᚋyauthdevᚋyaut return res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) marshalNSignUpResponse2githubᚗcomᚋyauthdevᚋyauthᚋserverᚋgraphᚋmodelᚐSignUpResponse(ctx context.Context, sel ast.SelectionSet, v model.SignUpResponse) graphql.Marshaler { - return ec._SignUpResponse(ctx, sel, &v) -} - -func (ec *executionContext) marshalNSignUpResponse2ᚖgithubᚗcomᚋyauthdevᚋyauthᚋserverᚋgraphᚋmodelᚐSignUpResponse(ctx context.Context, sel ast.SelectionSet, v *model.SignUpResponse) graphql.Marshaler { - if v == nil { - if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - return ec._SignUpResponse(ctx, sel, v) -} - func (ec *executionContext) unmarshalNString2string(ctx context.Context, v interface{}) (string, error) { res, err := graphql.UnmarshalString(v) return res, graphql.ErrorOnPath(ctx, err) @@ -3687,6 +3708,11 @@ func (ec *executionContext) marshalNString2string(ctx context.Context, sel ast.S return res } +func (ec *executionContext) unmarshalNUpdateProfileInput2githubᚗcomᚋyauthdevᚋyauthᚋserverᚋgraphᚋmodelᚐUpdateProfileInput(ctx context.Context, v interface{}) (model.UpdateProfileInput, error) { + res, err := ec.unmarshalInputUpdateProfileInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) marshalNUser2githubᚗcomᚋyauthdevᚋyauthᚋserverᚋgraphᚋmodelᚐUser(ctx context.Context, sel ast.SelectionSet, v model.User) graphql.Marshaler { return ec._User(ctx, sel, &v) } @@ -3738,8 +3764,8 @@ func (ec *executionContext) marshalNUser2ᚖgithubᚗcomᚋyauthdevᚋyauthᚋse return ec._User(ctx, sel, v) } -func (ec *executionContext) unmarshalNVerifySignupTokenInput2githubᚗcomᚋyauthdevᚋyauthᚋserverᚋgraphᚋmodelᚐVerifySignupTokenInput(ctx context.Context, v interface{}) (model.VerifySignupTokenInput, error) { - res, err := ec.unmarshalInputVerifySignupTokenInput(ctx, v) +func (ec *executionContext) unmarshalNVerifyEmailInput2githubᚗcomᚋyauthdevᚋyauthᚋserverᚋgraphᚋmodelᚐVerifyEmailInput(ctx context.Context, v interface{}) (model.VerifyEmailInput, error) { + res, err := ec.unmarshalInputVerifyEmailInput(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 f7ef17d..9425dc1 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -32,9 +32,14 @@ type SignUpInput struct { Image *string `json:"image"` } -type SignUpResponse struct { - Message string `json:"message"` - User *User `json:"user"` +type UpdateProfileInput struct { + OldPassword *string `json:"oldPassword"` + NewPassword *string `json:"newPassword"` + ConfirmNewPassword *string `json:"confirmNewPassword"` + FirstName *string `json:"firstName"` + LastName *string `json:"lastName"` + Image *string `json:"image"` + Email *string `json:"email"` } type User struct { @@ -60,6 +65,6 @@ type VerificationRequest struct { UpdatedAt *int64 `json:"updatedAt"` } -type VerifySignupTokenInput struct { +type VerifyEmailInput struct { Token string `json:"token"` } diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index 8d5f517..c3360da 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -38,11 +38,6 @@ type LoginResponse { user: User } -type SignUpResponse { - message: String! - user: User -} - type Response { message: String! } @@ -61,15 +56,26 @@ input LoginInput { password: String! } -input VerifySignupTokenInput { +input VerifyEmailInput { token: String! } +input UpdateProfileInput { + oldPassword: String + newPassword: String + confirmNewPassword: String + firstName: String + lastName: String + image: String + email: String +} + type Mutation { - signup(params: SignUpInput!): SignUpResponse! - verifySignupToken(params: VerifySignupTokenInput!): LoginResponse! + signup(params: SignUpInput!): Response! login(params: LoginInput!): LoginResponse! logout: Response! + updateProfile(params: UpdateProfileInput!): Response! + verifyEmail(params: VerifyEmailInput!): LoginResponse! } type Query { diff --git a/server/graph/schema.resolvers.go b/server/graph/schema.resolvers.go index 68afbb9..a3bd09c 100644 --- a/server/graph/schema.resolvers.go +++ b/server/graph/schema.resolvers.go @@ -11,14 +11,10 @@ import ( "github.com/yauthdev/yauth/server/resolvers" ) -func (r *mutationResolver) Signup(ctx context.Context, params model.SignUpInput) (*model.SignUpResponse, error) { +func (r *mutationResolver) Signup(ctx context.Context, params model.SignUpInput) (*model.Response, error) { return resolvers.Signup(ctx, params) } -func (r *mutationResolver) VerifySignupToken(ctx context.Context, params model.VerifySignupTokenInput) (*model.LoginResponse, error) { - return resolvers.VerifySignupToken(ctx, params) -} - func (r *mutationResolver) Login(ctx context.Context, params model.LoginInput) (*model.LoginResponse, error) { return resolvers.Login(ctx, params) } @@ -27,6 +23,14 @@ func (r *mutationResolver) Logout(ctx context.Context) (*model.Response, error) return resolvers.Logout(ctx) } +func (r *mutationResolver) UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model.Response, error) { + return resolvers.UpdateProfile(ctx, params) +} + +func (r *mutationResolver) VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.LoginResponse, error) { + return resolvers.VerifyEmail(ctx, params) +} + func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) { return resolvers.Users(ctx) } diff --git a/server/resolvers/login.go b/server/resolvers/login.go index 637c226..6e72a1e 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -2,7 +2,6 @@ package resolvers import ( "context" - "errors" "fmt" "log" "strings" @@ -25,15 +24,15 @@ func Login(ctx context.Context, params model.LoginInput) (*model.LoginResponse, params.Email = strings.ToLower(params.Email) user, err := db.Mgr.GetUserByEmail(params.Email) if err != nil { - return res, errors.New(`User with this email not found`) + return res, fmt.Errorf(`user with this email not found`) } if !strings.Contains(user.SignupMethod, enum.BasicAuth.String()) { - return res, errors.New(`User has not signed up email & password`) + return res, fmt.Errorf(`user has not signed up email & password`) } if user.EmailVerifiedAt <= 0 { - return res, errors.New(`Email not verified`) + return res, fmt.Errorf(`email not verified`) } // match password cost, err := bcrypt.Cost([]byte(user.Password)) @@ -42,7 +41,7 @@ func Login(ctx context.Context, params model.LoginInput) (*model.LoginResponse, if err != nil { log.Println("Compare password error:", err) - return res, errors.New(`Invalid Password`) + return res, fmt.Errorf(`invalid password`) } userIdStr := fmt.Sprintf("%d", user.ID) refreshToken, _, _ := utils.CreateAuthToken(utils.UserAuthInfo{ diff --git a/server/resolvers/profile.go b/server/resolvers/profile.go index a9ecf70..c45f155 100644 --- a/server/resolvers/profile.go +++ b/server/resolvers/profile.go @@ -6,6 +6,7 @@ import ( "github.com/yauthdev/yauth/server/db" "github.com/yauthdev/yauth/server/graph/model" + "github.com/yauthdev/yauth/server/session" "github.com/yauthdev/yauth/server/utils" ) @@ -26,6 +27,12 @@ func Profile(ctx context.Context) (*model.User, error) { return res, err } + sessionToken := session.GetToken(claim.ID) + + if sessionToken == "" { + return res, fmt.Errorf(`unauthorized`) + } + user, err := db.Mgr.GetUserByEmail(claim.Email) if err != nil { return res, err diff --git a/server/resolvers/signup.go b/server/resolvers/signup.go index 850ad6d..47f6ab3 100644 --- a/server/resolvers/signup.go +++ b/server/resolvers/signup.go @@ -2,7 +2,7 @@ package resolvers import ( "context" - "errors" + "fmt" "log" "strings" "time" @@ -13,16 +13,16 @@ import ( "github.com/yauthdev/yauth/server/utils" ) -func Signup(ctx context.Context, params model.SignUpInput) (*model.SignUpResponse, error) { - var res *model.SignUpResponse +func Signup(ctx context.Context, params model.SignUpInput) (*model.Response, error) { + var res *model.Response if params.CofirmPassword != params.Password { - return res, errors.New(`Passowrd and Confirm Password does not match`) + return res, fmt.Errorf(`passowrd and confirm password does not match`) } params.Email = strings.ToLower(params.Email) if !utils.IsValidEmail(params.Email) { - return res, errors.New(`Invalid email address`) + return res, fmt.Errorf(`invalid email address`) } // find user with email @@ -33,7 +33,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.SignUpRespons if existingUser.EmailVerifiedAt > 0 { // email is verified - return res, errors.New(`You have already signed up. Please login`) + return res, fmt.Errorf(`you have already signed up. Please login`) } user := db.User{ Email: params.Email, @@ -57,7 +57,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.SignUpRespons } // insert verification request - verificationType := enum.BasicAuth.String() + verificationType := enum.BasicAuthSignup.String() token, err := utils.CreateVerificationToken(params.Email, verificationType) if err != nil { log.Println(`Error generating token`, err) @@ -74,8 +74,8 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.SignUpRespons utils.SendVerificationMail(params.Email, token) }() - res = &model.SignUpResponse{ - Message: `Verification email sent successfully. Please check your inbox`, + res = &model.Response{ + Message: `Verification email has been sent. Please check your inbox`, } return res, nil diff --git a/server/resolvers/token.go b/server/resolvers/token.go index 014a812..963805d 100644 --- a/server/resolvers/token.go +++ b/server/resolvers/token.go @@ -2,7 +2,6 @@ package resolvers import ( "context" - "errors" "fmt" "github.com/yauthdev/yauth/server/db" @@ -36,19 +35,17 @@ func Token(ctx context.Context) (*model.LoginResponse, error) { sessionToken := session.GetToken(userIdStr) if sessionToken == "" { - return res, errors.New(`Unauthorized`) + return res, fmt.Errorf(`unauthorized`) } // TODO check if session token has expired if accessTokenErr != nil { // if access token has expired and refresh/session token is valid // generate new accessToken - fmt.Println(`here... getting new accesstoken`) token, expiresAt, _ = utils.CreateAuthToken(utils.UserAuthInfo{ ID: userIdStr, Email: user.Email, }, enum.AccessToken) - } utils.SetCookie(gc, token) res = &model.LoginResponse{ diff --git a/server/resolvers/updateProfile.go b/server/resolvers/updateProfile.go new file mode 100644 index 0000000..e6acee4 --- /dev/null +++ b/server/resolvers/updateProfile.go @@ -0,0 +1,136 @@ +package resolvers + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "github.com/yauthdev/yauth/server/db" + "github.com/yauthdev/yauth/server/enum" + "github.com/yauthdev/yauth/server/graph/model" + "github.com/yauthdev/yauth/server/session" + "github.com/yauthdev/yauth/server/utils" + "golang.org/x/crypto/bcrypt" +) + +func UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model.Response, error) { + gc, err := utils.GinContextFromContext(ctx) + var res *model.Response + if err != nil { + return res, err + } + + token, err := utils.GetAuthToken(gc) + if err != nil { + return res, err + } + + claim, err := utils.VerifyAuthToken(token) + if err != nil { + return res, err + } + + sessionToken := session.GetToken(claim.ID) + + if sessionToken == "" { + return res, fmt.Errorf(`unauthorized`) + } + + // validate if all params are not empty + if params.FirstName == nil && params.LastName == nil && params.Image == nil && params.OldPassword == nil && params.Email == nil { + return res, fmt.Errorf("please enter atleast one param to update") + } + + user, err := db.Mgr.GetUserByEmail(claim.Email) + if err != nil { + return res, err + } + + if params.FirstName != nil && user.FirstName != *params.FirstName { + user.FirstName = *params.FirstName + } + + if params.LastName != nil && user.LastName != *params.LastName { + user.LastName = *params.LastName + } + + if params.Image != nil && user.Image != *params.Image { + user.Image = *params.Image + } + + if params.OldPassword != nil { + if err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(*params.OldPassword)); err != nil { + return res, fmt.Errorf("incorrect old password") + } + + if params.NewPassword == nil { + return res, fmt.Errorf("new password is required") + } + + if params.ConfirmNewPassword == nil { + return res, fmt.Errorf("confirm password is required") + } + + if *params.ConfirmNewPassword != *params.NewPassword { + return res, fmt.Errorf(`password and confirm password does not match`) + } + + password, _ := utils.HashPassword(*params.NewPassword) + + user.Password = password + } + + hasEmailChanged := false + + if params.Email != nil && user.Email != *params.Email { + // check if valid email + if !utils.IsValidEmail(*params.Email) { + return res, fmt.Errorf("invalid email address") + } + newEmail := strings.ToLower(*params.Email) + // check if user with new email exists + _, err = db.Mgr.GetUserByEmail(newEmail) + // err = nil means user exists + if err == nil { + return res, fmt.Errorf("user with this email address already exists") + } + + session.DeleteToken(fmt.Sprintf("%d", user.ID)) + utils.DeleteCookie(gc) + + user.Email = newEmail + user.EmailVerifiedAt = 0 + hasEmailChanged = true + // insert verification request + verificationType := enum.UpdateEmail.String() + token, err := utils.CreateVerificationToken(newEmail, verificationType) + if err != nil { + log.Println(`Error generating token`, err) + } + db.Mgr.AddVerification(db.Verification{ + Token: token, + Identifier: verificationType, + ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), + Email: newEmail, + }) + + // exec it as go routin so that we can reduce the api latency + go func() { + utils.SendVerificationMail(newEmail, token) + }() + + } + + _, err = db.Mgr.UpdateUser(user) + message := `Profile details updated successfully.` + if hasEmailChanged { + message += `For the email change we have sent new verification email, please verify and continue` + } + res = &model.Response{ + Message: message, + } + + return res, nil +} diff --git a/server/resolvers/verifySignUpToken.go b/server/resolvers/verifyEmail.go similarity index 89% rename from server/resolvers/verifySignUpToken.go rename to server/resolvers/verifyEmail.go index 97543d4..e381abb 100644 --- a/server/resolvers/verifySignUpToken.go +++ b/server/resolvers/verifyEmail.go @@ -2,7 +2,6 @@ package resolvers import ( "context" - "errors" "fmt" "time" @@ -13,7 +12,7 @@ import ( "github.com/yauthdev/yauth/server/utils" ) -func VerifySignupToken(ctx context.Context, params model.VerifySignupTokenInput) (*model.LoginResponse, error) { +func VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.LoginResponse, error) { gc, err := utils.GinContextFromContext(ctx) var res *model.LoginResponse if err != nil { @@ -22,13 +21,13 @@ func VerifySignupToken(ctx context.Context, params model.VerifySignupTokenInput) _, err = db.Mgr.GetVerificationByToken(params.Token) if err != nil { - return res, errors.New(`Invalid token`) + return res, fmt.Errorf(`invalid token`) } // verify if token exists in db claim, err := utils.VerifyVerificationToken(params.Token) if err != nil { - return res, errors.New(`Invalid token`) + return res, fmt.Errorf(`invalid token`) } user, err := db.Mgr.GetUserByEmail(claim.Email) diff --git a/server/utils/authToken.go b/server/utils/authToken.go index 76634f0..dc90b2d 100644 --- a/server/utils/authToken.go +++ b/server/utils/authToken.go @@ -1,7 +1,7 @@ package utils import ( - "errors" + "fmt" "log" "strings" "time" @@ -56,7 +56,7 @@ func GetAuthToken(gc *gin.Context) (string, error) { log.Println("cookie not found checking headers") auth := gc.Request.Header.Get("Authorization") if auth == "" { - return "", errors.New(`Unauthorized`) + return "", fmt.Errorf(`Unauthorized`) } token = strings.TrimPrefix(auth, "Bearer ")