From ef5aa743bfbb3050907735c74a9d543be50156b2 Mon Sep 17 00:00:00 2001 From: Untone Date: Tue, 24 Oct 2023 17:01:05 +0300 Subject: [PATCH 1/2] create-user --- .github/CONTRIBUTING.md | 15 ++ dashboard/src/components/EditUserModal.tsx | 7 + dashboard/src/graphql/mutation/index.ts | 8 + dashboard/src/pages/Users.tsx | 18 ++ server/graph/generated/generated.go | 244 +++++++++++++++++++++ server/graph/mod.go | 1 + server/graph/model/models_gen.go | 11 + server/graph/schema.graphqls | 13 ++ server/graph/schema.resolvers.go | 5 + server/resolvers/create_user.go | 135 ++++++++++++ 10 files changed, 457 insertions(+) create mode 100644 server/graph/mod.go create mode 100644 server/resolvers/create_user.go diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 1d5a2f0..bd63e85 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -229,6 +229,21 @@ mutation Logout { } } +mutation CreateUser { + _create_user( + params: { + email: "test@domain.com", + password: "", + nickname: "test", + } + ) { + id + email + roles + } +} + + mutation UpdateUser { _update_user( params: { diff --git a/dashboard/src/components/EditUserModal.tsx b/dashboard/src/components/EditUserModal.tsx index 8184786..5c4aa79 100644 --- a/dashboard/src/components/EditUserModal.tsx +++ b/dashboard/src/components/EditUserModal.tsx @@ -113,6 +113,13 @@ const EditUserModal = ({ status: 'success', position: 'top-right', }); + } else if (res.data?._create_user?.id) { + toast({ + title: 'User created successfully', + isClosable: true, + status: 'success', + position: 'top-right', + }); } onClose(); updateUserList(); diff --git a/dashboard/src/graphql/mutation/index.ts b/dashboard/src/graphql/mutation/index.ts index 47e9acf..2933534 100644 --- a/dashboard/src/graphql/mutation/index.ts +++ b/dashboard/src/graphql/mutation/index.ts @@ -38,6 +38,14 @@ export const UpdateUser = ` } `; +export const CreateUser = ` + mutation createUser($params: CreateUserInput!) { + _create_user(params: $params) { + id + } + } +`; + export const DeleteUser = ` mutation deleteUser($params: DeleteUserInput!) { _delete_user(params: $params) { diff --git a/dashboard/src/pages/Users.tsx b/dashboard/src/pages/Users.tsx index 3a0cc0a..b9e042d 100644 --- a/dashboard/src/pages/Users.tsx +++ b/dashboard/src/pages/Users.tsx @@ -189,6 +189,13 @@ export default function Users() { status: 'success', position: 'top-right', }); + } else if (res.data?._create_user?.id) { + toast({ + title: 'User verification successful', + isClosable: true, + status: 'success', + position: 'top-right', + }); } updateUserList(); }; @@ -272,6 +279,17 @@ export default function Users() { }); updateUserList(); return; + } else if (res.data?._create_user?.id) { + toast({ + title: `Multi factor authentication ${ + user.is_multi_factor_auth_enabled ? 'disabled' : 'enabled' + } for user`, + isClosable: true, + status: 'success', + position: 'top-right', + }); + updateUserList(); + return; } toast({ title: 'Multi factor authentication update failed for user', diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index 9852c8c..f07af78 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -177,6 +177,7 @@ type ComplexityRoot struct { AdminLogin func(childComplexity int, params model.AdminLoginInput) int AdminLogout func(childComplexity int) int AdminSignup func(childComplexity int, params model.AdminSignupInput) int + CreateUser func(childComplexity int, params model.CreateUserInput) int DeactivateAccount func(childComplexity int) int DeleteEmailTemplate func(childComplexity int, params model.DeleteEmailTemplateRequest) int DeleteUser func(childComplexity int, params model.DeleteUserInput) int @@ -351,6 +352,7 @@ type MutationResolver interface { VerifyOtp(ctx context.Context, params model.VerifyOTPRequest) (*model.AuthResponse, error) ResendOtp(ctx context.Context, params model.ResendOTPRequest) (*model.Response, error) DeactivateAccount(ctx context.Context) (*model.Response, error) + CreateUser(ctx context.Context, params model.CreateUserInput) (*model.User, 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) @@ -1167,6 +1169,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.AdminSignup(childComplexity, args["params"].(model.AdminSignupInput)), true + case "Mutation._create_user": + if e.complexity.Mutation.CreateUser == nil { + break + } + + args, err := ec.field_Mutation__create_user_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.CreateUser(childComplexity, args["params"].(model.CreateUserInput)), true + case "Mutation.deactivate_account": if e.complexity.Mutation.DeactivateAccount == nil { break @@ -2124,6 +2138,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputAddWebhookRequest, ec.unmarshalInputAdminLoginInput, ec.unmarshalInputAdminSignupInput, + ec.unmarshalInputCreateUserInput, ec.unmarshalInputDeleteEmailTemplateRequest, ec.unmarshalInputDeleteUserInput, ec.unmarshalInputForgotPasswordInput, @@ -2662,6 +2677,18 @@ input UpdateProfileInput { app_data: Map } +input CreateUserInput { + email: String + password: String + given_name: String + family_name: String + middle_name: String + nickname: String + phone_number: String + picture: String +} + + input UpdateUserInput { id: ID! email: String @@ -2843,6 +2870,7 @@ type Mutation { resend_otp(params: ResendOTPRequest!): Response! deactivate_account: Response! # admin only apis + _create_user(params: CreateUserInput!): User! _delete_user(params: DeleteUserInput!): Response! _update_user(params: UpdateUserInput!): User! _admin_signup(params: AdminSignupInput!): Response! @@ -2947,6 +2975,21 @@ func (ec *executionContext) field_Mutation__admin_signup_args(ctx context.Contex return args, nil } +func (ec *executionContext) field_Mutation__create_user_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 model.CreateUserInput + if tmp, ok := rawArgs["params"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params")) + arg0, err = ec.unmarshalNCreateUserInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐCreateUserInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["params"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation__delete_email_template_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -8846,6 +8889,103 @@ func (ec *executionContext) fieldContext_Mutation_deactivate_account(ctx context return fc, nil } +func (ec *executionContext) _Mutation__create_user(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation__create_user(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().CreateUser(rctx, fc.Args["params"].(model.CreateUserInput)) + }) + 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.User) + fc.Result = res + return ec.marshalNUser2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUser(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation__create_user(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_User_id(ctx, field) + case "email": + return ec.fieldContext_User_email(ctx, field) + case "email_verified": + return ec.fieldContext_User_email_verified(ctx, field) + case "signup_methods": + return ec.fieldContext_User_signup_methods(ctx, field) + case "given_name": + return ec.fieldContext_User_given_name(ctx, field) + case "family_name": + return ec.fieldContext_User_family_name(ctx, field) + case "middle_name": + return ec.fieldContext_User_middle_name(ctx, field) + case "nickname": + return ec.fieldContext_User_nickname(ctx, field) + case "preferred_username": + return ec.fieldContext_User_preferred_username(ctx, field) + case "gender": + return ec.fieldContext_User_gender(ctx, field) + case "birthdate": + return ec.fieldContext_User_birthdate(ctx, field) + case "phone_number": + return ec.fieldContext_User_phone_number(ctx, field) + case "phone_number_verified": + return ec.fieldContext_User_phone_number_verified(ctx, field) + case "picture": + return ec.fieldContext_User_picture(ctx, field) + case "roles": + return ec.fieldContext_User_roles(ctx, field) + case "created_at": + return ec.fieldContext_User_created_at(ctx, field) + case "updated_at": + return ec.fieldContext_User_updated_at(ctx, field) + case "revoked_timestamp": + return ec.fieldContext_User_revoked_timestamp(ctx, field) + case "is_multi_factor_auth_enabled": + return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field) + case "app_data": + return ec.fieldContext_User_app_data(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type User", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation__create_user_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Mutation__delete_user(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Mutation__delete_user(ctx, field) if err != nil { @@ -16109,6 +16249,98 @@ func (ec *executionContext) unmarshalInputAdminSignupInput(ctx context.Context, return it, nil } +func (ec *executionContext) unmarshalInputCreateUserInput(ctx context.Context, obj interface{}) (model.CreateUserInput, error) { + var it model.CreateUserInput + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"email", "password", "given_name", "family_name", "middle_name", "nickname", "phone_number", "picture"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "email": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.Email = data + case "password": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("password")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.Password = data + case "given_name": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("given_name")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.GivenName = data + case "family_name": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("family_name")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.FamilyName = data + case "middle_name": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("middle_name")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.MiddleName = data + case "nickname": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("nickname")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.Nickname = 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 "picture": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("picture")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.Picture = data + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputDeleteEmailTemplateRequest(ctx context.Context, obj interface{}) (model.DeleteEmailTemplateRequest, error) { var it model.DeleteEmailTemplateRequest asMap := map[string]interface{}{} @@ -19123,6 +19355,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { out.Invalids++ } + case "_create_user": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation__create_user(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } case "_delete_user": out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { return ec._Mutation__delete_user(ctx, field) @@ -20699,6 +20938,11 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se return res } +func (ec *executionContext) unmarshalNCreateUserInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐCreateUserInput(ctx context.Context, v interface{}) (model.CreateUserInput, error) { + res, err := ec.unmarshalInputCreateUserInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalNDeleteEmailTemplateRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐDeleteEmailTemplateRequest(ctx context.Context, v interface{}) (model.DeleteEmailTemplateRequest, error) { res, err := ec.unmarshalInputDeleteEmailTemplateRequest(ctx, v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/server/graph/mod.go b/server/graph/mod.go new file mode 100644 index 0000000..8566596 --- /dev/null +++ b/server/graph/mod.go @@ -0,0 +1 @@ +package graph \ No newline at end of file diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index e96bfa1..567a566 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -36,6 +36,17 @@ type AuthResponse struct { User *User `json:"user,omitempty"` } +type CreateUserInput struct { + Email *string `json:"email,omitempty"` + Password *string `json:"password,omitempty"` + GivenName *string `json:"given_name,omitempty"` + FamilyName *string `json:"family_name,omitempty"` + MiddleName *string `json:"middle_name,omitempty"` + Nickname *string `json:"nickname,omitempty"` + PhoneNumber *string `json:"phone_number,omitempty"` + Picture *string `json:"picture,omitempty"` +} + type DeleteEmailTemplateRequest struct { ID string `json:"id"` } diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index b9c9600..4a51933 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -408,6 +408,18 @@ input UpdateProfileInput { app_data: Map } +input CreateUserInput { + email: String + password: String + given_name: String + family_name: String + middle_name: String + nickname: String + phone_number: String + picture: String +} + + input UpdateUserInput { id: ID! email: String @@ -589,6 +601,7 @@ type Mutation { resend_otp(params: ResendOTPRequest!): Response! deactivate_account: Response! # admin only apis + _create_user(params: CreateUserInput!): User! _delete_user(params: DeleteUserInput!): Response! _update_user(params: UpdateUserInput!): User! _admin_signup(params: AdminSignupInput!): Response! diff --git a/server/graph/schema.resolvers.go b/server/graph/schema.resolvers.go index 3390682..c0f6f8d 100644 --- a/server/graph/schema.resolvers.go +++ b/server/graph/schema.resolvers.go @@ -87,6 +87,11 @@ func (r *mutationResolver) DeactivateAccount(ctx context.Context) (*model.Respon return resolvers.DeactivateAccountResolver(ctx) } +// CreateUser is the resolver for the _create_user field. +func (r *mutationResolver) CreateUser(ctx context.Context, params model.CreateUserInput) (*model.User, error) { + return resolvers.CreateUserResolver(ctx, params) +} + // DeleteUser is the resolver for the _delete_user field. func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) { return resolvers.DeleteUserResolver(ctx, params) diff --git a/server/resolvers/create_user.go b/server/resolvers/create_user.go new file mode 100644 index 0000000..0a838a2 --- /dev/null +++ b/server/resolvers/create_user.go @@ -0,0 +1,135 @@ +package resolvers + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + log "github.com/sirupsen/logrus" + + "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" + "github.com/authorizerdev/authorizer/server/graph/model" + "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" +) + +// CreateUserResolver is a resolver for create user mutation +// This is admin only mutation +func CreateUserResolver(ctx context.Context, params model.CreateUserInput) (*model.User, error) { + var res *model.User + + gc, err := utils.GinContextFromContext(ctx) + if err != nil { + log.Debug("Failed to get GinContext: ", err) + return res, err + } + + if !token.IsSuperAdmin(gc) { + log.Debug("Not logged in as super admin") + return res, fmt.Errorf("unauthorized") + } + + log := log.New(); + + if params.PhoneNumber != nil { + // verify if phone number is unique + if _, err := db.Provider.GetUserByPhoneNumber(ctx, strings.TrimSpace(refs.StringValue(params.PhoneNumber))); err == nil { + log.Debug("user with given phone number already exists") + return nil, errors.New("user with given phone number already exists") + } + } + + if params.Email != nil { + // check if valid email + if !validators.IsValidEmail(*params.Email) { + log.Debug("Invalid email: ", *params.Email) + return res, fmt.Errorf("invalid email address") + } + newEmail := strings.ToLower(*params.Email) + // check if user with new email exists + _, err = db.Provider.GetUserByEmail(ctx, newEmail) + // err = nil means user exists + if err == nil { + log.Debug("User with email already exists: ", newEmail) + return res, fmt.Errorf("user with this email address already exists") + } + + hostname := parsers.GetHost(gc) + params.Email = &newEmail + // insert verification request + _, nonceHash, err := utils.GenerateNonce() + if err != nil { + log.Debug("Failed to generate nonce: ", err) + return res, err + } + verificationType := constants.VerificationTypeUpdateEmail + redirectURL := parsers.GetAppURL(gc) + verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname, nonceHash, redirectURL) + if err != nil { + log.Debug("Failed to create verification token: ", err) + } + _, err = db.Provider.AddVerificationRequest(ctx, &models.VerificationRequest{ + Token: verificationToken, + Identifier: verificationType, + ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), + Email: newEmail, + Nonce: nonceHash, + RedirectURI: redirectURL, + }) + if err != nil { + log.Debug("Failed to add verification request: ", err) + return res, err + } + + // exec it as go routine so that we can reduce the api latency + go email.SendEmail([]string{*params.Email}, constants.VerificationTypeBasicAuthSignup, map[string]interface{}{ + "user": params, + "organization": utils.GetOrganization(), + "verification_url": utils.GetEmailVerificationURL(verificationToken, hostname, redirectURL), + }) + + } + + // json-typed model to store in database + userdata := models.User{ + Email: *params.Email, + Password: params.Password, + GivenName: params.GivenName, + FamilyName: params.FamilyName, + MiddleName: params.MiddleName, + Nickname: params.Nickname, // slug + PhoneNumber: params.PhoneNumber, + Picture: params.Picture, + } + + var user *models.User + user, err = db.Provider.AddUser(ctx, &userdata) + + if err != nil { + log.Debug("Failed to create user: ", err) + return res, err + } + + // Convert the user data to the appropriate type for the GraphQL response + res = &model.User{ + ID: user.ID, + Email: user.Email, + GivenName: user.GivenName, + FamilyName: user.FamilyName, + MiddleName: user.MiddleName, + Nickname: user.Nickname, + PhoneNumber: user.PhoneNumber, + Picture: user.Picture, + } + + + return res, nil +} From 38f9b4bf78331c25d6a9050b8e5f9ec1d9924bcb Mon Sep 17 00:00:00 2001 From: Untone Date: Tue, 24 Oct 2023 20:49:34 +0300 Subject: [PATCH 2/2] create_user-api --- server/graph/generated/generated.go | 42 +++++++++++++++++++++- server/graph/model/mod.go | 1 + server/graph/model/models_gen.go | 20 ++++++----- server/graph/schema.graphqls | 4 +++ server/resolvers/create_user.go | 54 ++++++++++++++++++----------- 5 files changed, 91 insertions(+), 30 deletions(-) create mode 100644 server/graph/model/mod.go diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index f07af78..20ccfbd 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -2679,6 +2679,8 @@ input UpdateProfileInput { input CreateUserInput { email: String + email_verified: Boolean + email_verified_at: Int64 password: String given_name: String family_name: String @@ -2686,6 +2688,8 @@ input CreateUserInput { nickname: String phone_number: String picture: String + created_at: Int64 + updated_at: Int64 } @@ -16256,7 +16260,7 @@ func (ec *executionContext) unmarshalInputCreateUserInput(ctx context.Context, o asMap[k] = v } - fieldsInOrder := [...]string{"email", "password", "given_name", "family_name", "middle_name", "nickname", "phone_number", "picture"} + fieldsInOrder := [...]string{"email", "email_verified", "email_verified_at", "password", "given_name", "family_name", "middle_name", "nickname", "phone_number", "picture", "created_at", "updated_at"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -16272,6 +16276,24 @@ func (ec *executionContext) unmarshalInputCreateUserInput(ctx context.Context, o return it, err } it.Email = data + case "email_verified": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email_verified")) + data, err := ec.unmarshalOBoolean2ᚖbool(ctx, v) + if err != nil { + return it, err + } + it.EmailVerified = data + case "email_verified_at": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email_verified_at")) + data, err := ec.unmarshalOInt642ᚖint64(ctx, v) + if err != nil { + return it, err + } + it.EmailVerifiedAt = data case "password": var err error @@ -16335,6 +16357,24 @@ func (ec *executionContext) unmarshalInputCreateUserInput(ctx context.Context, o return it, err } it.Picture = data + case "created_at": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("created_at")) + data, err := ec.unmarshalOInt642ᚖint64(ctx, v) + if err != nil { + return it, err + } + it.CreatedAt = data + case "updated_at": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("updated_at")) + data, err := ec.unmarshalOInt642ᚖint64(ctx, v) + if err != nil { + return it, err + } + it.UpdatedAt = data } } diff --git a/server/graph/model/mod.go b/server/graph/model/mod.go new file mode 100644 index 0000000..0c3b4f2 --- /dev/null +++ b/server/graph/model/mod.go @@ -0,0 +1 @@ +package model \ No newline at end of file diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index 567a566..72030de 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -37,14 +37,18 @@ type AuthResponse struct { } type CreateUserInput struct { - Email *string `json:"email,omitempty"` - Password *string `json:"password,omitempty"` - GivenName *string `json:"given_name,omitempty"` - FamilyName *string `json:"family_name,omitempty"` - MiddleName *string `json:"middle_name,omitempty"` - Nickname *string `json:"nickname,omitempty"` - PhoneNumber *string `json:"phone_number,omitempty"` - Picture *string `json:"picture,omitempty"` + Email *string `json:"email,omitempty"` + EmailVerified *bool `json:"email_verified,omitempty"` + EmailVerifiedAt *int64 `json:"email_verified_at,omitempty"` + Password *string `json:"password,omitempty"` + GivenName *string `json:"given_name,omitempty"` + FamilyName *string `json:"family_name,omitempty"` + MiddleName *string `json:"middle_name,omitempty"` + Nickname *string `json:"nickname,omitempty"` + PhoneNumber *string `json:"phone_number,omitempty"` + Picture *string `json:"picture,omitempty"` + CreatedAt *int64 `json:"created_at,omitempty"` + UpdatedAt *int64 `json:"updated_at,omitempty"` } type DeleteEmailTemplateRequest struct { diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index 4a51933..eb1570c 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -410,6 +410,8 @@ input UpdateProfileInput { input CreateUserInput { email: String + email_verified: Boolean + email_verified_at: Int64 password: String given_name: String family_name: String @@ -417,6 +419,8 @@ input CreateUserInput { nickname: String phone_number: String picture: String + created_at: Int64 + updated_at: Int64 } diff --git a/server/resolvers/create_user.go b/server/resolvers/create_user.go index 0a838a2..07da02d 100644 --- a/server/resolvers/create_user.go +++ b/server/resolvers/create_user.go @@ -37,7 +37,7 @@ func CreateUserResolver(ctx context.Context, params model.CreateUserInput) (*mod return res, fmt.Errorf("unauthorized") } - log := log.New(); + log := log.New() if params.PhoneNumber != nil { // verify if phone number is unique @@ -47,6 +47,15 @@ func CreateUserResolver(ctx context.Context, params model.CreateUserInput) (*mod } } + if params.EmailVerified != nil { + if *params.EmailVerified { + now := time.Now().Unix() + params.EmailVerifiedAt = &now + } else { + params.EmailVerifiedAt = nil + } + } + if params.Email != nil { // check if valid email if !validators.IsValidEmail(*params.Email) { @@ -64,6 +73,7 @@ func CreateUserResolver(ctx context.Context, params model.CreateUserInput) (*mod hostname := parsers.GetHost(gc) params.Email = &newEmail + params.EmailVerifiedAt = nil // insert verification request _, nonceHash, err := utils.GenerateNonce() if err != nil { @@ -100,36 +110,38 @@ func CreateUserResolver(ctx context.Context, params model.CreateUserInput) (*mod // json-typed model to store in database userdata := models.User{ - Email: *params.Email, - Password: params.Password, - GivenName: params.GivenName, - FamilyName: params.FamilyName, - MiddleName: params.MiddleName, - Nickname: params.Nickname, // slug - PhoneNumber: params.PhoneNumber, - Picture: params.Picture, + Email: *params.Email, + EmailVerifiedAt: params.EmailVerifiedAt, + CreatedAt: *params.CreatedAt, + UpdatedAt: *params.UpdatedAt, + Password: params.Password, + GivenName: params.GivenName, + FamilyName: params.FamilyName, + MiddleName: params.MiddleName, + Nickname: params.Nickname, // slug + PhoneNumber: params.PhoneNumber, + Picture: params.Picture, } var user *models.User user, err = db.Provider.AddUser(ctx, &userdata) if err != nil { - log.Debug("Failed to create user: ", err) + log.Debug("Failed to update user: ", err) return res, err } - // Convert the user data to the appropriate type for the GraphQL response + createdAt := user.CreatedAt + updatedAt := user.UpdatedAt res = &model.User{ - ID: user.ID, - Email: user.Email, - GivenName: user.GivenName, - FamilyName: user.FamilyName, - MiddleName: user.MiddleName, - Nickname: user.Nickname, - PhoneNumber: user.PhoneNumber, - Picture: user.Picture, + ID: user.ID, + Email: user.Email, + Picture: user.Picture, + GivenName: user.GivenName, + FamilyName: user.FamilyName, + Roles: strings.Split(user.Roles, ","), + CreatedAt: &createdAt, + UpdatedAt: &updatedAt, } - - return res, nil }