create-user
All checks were successful
deploy / deploy (push) Successful in 1m41s

This commit is contained in:
Untone 2023-10-24 17:01:05 +03:00
parent 739fcbfaae
commit ef5aa743bf
10 changed files with 457 additions and 0 deletions

View File

@ -229,6 +229,21 @@ mutation Logout {
}
}
mutation CreateUser {
_create_user(
params: {
email: "test@domain.com",
password: "<bcrypt-hashed-password>",
nickname: "test",
}
) {
id
email
roles
}
}
mutation UpdateUser {
_update_user(
params: {

View File

@ -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();

View File

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

View File

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

View File

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

1
server/graph/mod.go Normal file
View File

@ -0,0 +1 @@
package graph

View File

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

View File

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

View File

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

View File

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