feat: allow admin to user profile (#51)

Resolves #49
This commit is contained in:
Lakhan Samani 2021-09-21 08:23:40 +05:30 committed by GitHub
parent 1ba53e2c49
commit b3a52c2466
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 319 additions and 4 deletions

View File

@ -17,6 +17,7 @@ type Manager interface {
UpdateUser(user User) (User, error) UpdateUser(user User) (User, error)
GetUsers() ([]User, error) GetUsers() ([]User, error)
GetUserByEmail(email string) (User, error) GetUserByEmail(email string) (User, error)
GetUserByID(email string) (User, error)
UpdateVerificationTime(verifiedAt int64, id uint) error UpdateVerificationTime(verifiedAt int64, id uint) error
AddVerification(verification VerificationRequest) (VerificationRequest, error) AddVerification(verification VerificationRequest) (VerificationRequest, error)
GetVerificationByToken(token string) (VerificationRequest, error) GetVerificationByToken(token string) (VerificationRequest, error)

View File

@ -2,6 +2,7 @@ package db
import ( import (
"log" "log"
"time"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
) )
@ -37,6 +38,7 @@ func (mgr *manager) SaveUser(user User) (User, error) {
// UpdateUser function to update user with ID conflict // UpdateUser function to update user with ID conflict
func (mgr *manager) UpdateUser(user User) (User, error) { func (mgr *manager) UpdateUser(user User) (User, error) {
user.UpdatedAt = time.Now().Unix()
result := mgr.db.Clauses( result := mgr.db.Clauses(
clause.OnConflict{ clause.OnConflict{
UpdateAll: true, UpdateAll: true,
@ -72,6 +74,17 @@ func (mgr *manager) GetUserByEmail(email string) (User, error) {
return user, nil return user, nil
} }
func (mgr *manager) GetUserByID(id string) (User, error) {
var user User
result := mgr.db.Where("id = ?", id).First(&user)
if result.Error != nil {
return user, result.Error
}
return user, nil
}
func (mgr *manager) UpdateVerificationTime(verifiedAt int64, id uint) error { func (mgr *manager) UpdateVerificationTime(verifiedAt int64, id uint) error {
user := &User{ user := &User{
ID: id, ID: id,

View File

@ -66,6 +66,7 @@ type ComplexityRoot struct {
} }
Mutation struct { Mutation struct {
AdminUpdateUser func(childComplexity int, params model.AdminUpdateUserInput) int
DeleteUser func(childComplexity int, params model.DeleteUserInput) int DeleteUser func(childComplexity int, params model.DeleteUserInput) int
ForgotPassword func(childComplexity int, params model.ForgotPasswordInput) int ForgotPassword func(childComplexity int, params model.ForgotPasswordInput) int
Login func(childComplexity int, params model.LoginInput) int Login func(childComplexity int, params model.LoginInput) int
@ -118,6 +119,7 @@ type MutationResolver interface {
Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, error) Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, error)
Logout(ctx context.Context) (*model.Response, error) Logout(ctx context.Context) (*model.Response, error)
UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model.Response, error) UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model.Response, error)
AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*model.User, error)
VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.AuthResponse, error) VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.AuthResponse, error)
ResendVerifyEmail(ctx context.Context, params model.ResendVerifyEmailInput) (*model.Response, error) ResendVerifyEmail(ctx context.Context, params model.ResendVerifyEmailInput) (*model.Response, error)
ForgotPassword(ctx context.Context, params model.ForgotPasswordInput) (*model.Response, error) ForgotPassword(ctx context.Context, params model.ForgotPasswordInput) (*model.Response, error)
@ -238,6 +240,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Meta.Version(childComplexity), true return e.complexity.Meta.Version(childComplexity), true
case "Mutation.adminUpdateUser":
if e.complexity.Mutation.AdminUpdateUser == nil {
break
}
args, err := ec.field_Mutation_adminUpdateUser_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.AdminUpdateUser(childComplexity, args["params"].(model.AdminUpdateUserInput)), true
case "Mutation.deleteUser": case "Mutation.deleteUser":
if e.complexity.Mutation.DeleteUser == nil { if e.complexity.Mutation.DeleteUser == nil {
break break
@ -662,6 +676,15 @@ input UpdateProfileInput {
# roles: [String] # roles: [String]
} }
input AdminUpdateUserInput {
id: ID!
email: String
firstName: String
lastName: String
image: String
roles: [String]
}
input ForgotPasswordInput { input ForgotPasswordInput {
email: String! email: String!
} }
@ -681,6 +704,7 @@ type Mutation {
login(params: LoginInput!): AuthResponse! login(params: LoginInput!): AuthResponse!
logout: Response! logout: Response!
updateProfile(params: UpdateProfileInput!): Response! updateProfile(params: UpdateProfileInput!): Response!
adminUpdateUser(params: AdminUpdateUserInput!): User!
verifyEmail(params: VerifyEmailInput!): AuthResponse! verifyEmail(params: VerifyEmailInput!): AuthResponse!
resendVerifyEmail(params: ResendVerifyEmailInput!): Response! resendVerifyEmail(params: ResendVerifyEmailInput!): Response!
forgotPassword(params: ForgotPasswordInput!): Response! forgotPassword(params: ForgotPasswordInput!): Response!
@ -703,6 +727,21 @@ var parsedSchema = gqlparser.MustLoadSchema(sources...)
// region ***************************** args.gotpl ***************************** // region ***************************** args.gotpl *****************************
func (ec *executionContext) field_Mutation_adminUpdateUser_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 model.AdminUpdateUserInput
if tmp, ok := rawArgs["params"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params"))
arg0, err = ec.unmarshalNAdminUpdateUserInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAdminUpdateUserInput(ctx, tmp)
if err != nil {
return nil, err
}
}
args["params"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_deleteUser_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { func (ec *executionContext) field_Mutation_deleteUser_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error var err error
args := map[string]interface{}{} args := map[string]interface{}{}
@ -1498,6 +1537,48 @@ func (ec *executionContext) _Mutation_updateProfile(ctx context.Context, field g
return ec.marshalNResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res) return ec.marshalNResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res)
} }
func (ec *executionContext) _Mutation_adminUpdateUser(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_adminUpdateUser_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().AdminUpdateUser(rctx, args["params"].(model.AdminUpdateUserInput))
})
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) _Mutation_verifyEmail(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation_verifyEmail(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -3639,6 +3720,66 @@ func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.Co
// region **************************** input.gotpl ***************************** // region **************************** input.gotpl *****************************
func (ec *executionContext) unmarshalInputAdminUpdateUserInput(ctx context.Context, obj interface{}) (model.AdminUpdateUserInput, error) {
var it model.AdminUpdateUserInput
var asMap = obj.(map[string]interface{})
for k, v := range asMap {
switch k {
case "id":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id"))
it.ID, err = ec.unmarshalNID2string(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
}
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 "roles":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("roles"))
it.Roles, err = ec.unmarshalOString2ᚕᚖstring(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputDeleteUserInput(ctx context.Context, obj interface{}) (model.DeleteUserInput, error) { func (ec *executionContext) unmarshalInputDeleteUserInput(ctx context.Context, obj interface{}) (model.DeleteUserInput, error) {
var it model.DeleteUserInput var it model.DeleteUserInput
var asMap = obj.(map[string]interface{}) var asMap = obj.(map[string]interface{})
@ -4092,6 +4233,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }
case "adminUpdateUser":
out.Values[i] = ec._Mutation_adminUpdateUser(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
case "verifyEmail": case "verifyEmail":
out.Values[i] = ec._Mutation_verifyEmail(ctx, field) out.Values[i] = ec._Mutation_verifyEmail(ctx, field)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
@ -4590,6 +4736,11 @@ func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, o
// region ***************************** type.gotpl ***************************** // region ***************************** type.gotpl *****************************
func (ec *executionContext) unmarshalNAdminUpdateUserInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAdminUpdateUserInput(ctx context.Context, v interface{}) (model.AdminUpdateUserInput, error) {
res, err := ec.unmarshalInputAdminUpdateUserInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNAuthResponse2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAuthResponse(ctx context.Context, sel ast.SelectionSet, v model.AuthResponse) graphql.Marshaler { func (ec *executionContext) marshalNAuthResponse2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAuthResponse(ctx context.Context, sel ast.SelectionSet, v model.AuthResponse) graphql.Marshaler {
return ec._AuthResponse(ctx, sel, &v) return ec._AuthResponse(ctx, sel, &v)
} }

View File

@ -2,6 +2,15 @@
package model package model
type AdminUpdateUserInput struct {
ID string `json:"id"`
Email *string `json:"email"`
FirstName *string `json:"firstName"`
LastName *string `json:"lastName"`
Image *string `json:"image"`
Roles []*string `json:"roles"`
}
type AuthResponse struct { type AuthResponse struct {
Message string `json:"message"` Message string `json:"message"`
AccessToken *string `json:"accessToken"` AccessToken *string `json:"accessToken"`

View File

@ -89,6 +89,15 @@ input UpdateProfileInput {
# roles: [String] # roles: [String]
} }
input AdminUpdateUserInput {
id: ID!
email: String
firstName: String
lastName: String
image: String
roles: [String]
}
input ForgotPasswordInput { input ForgotPasswordInput {
email: String! email: String!
} }
@ -108,6 +117,7 @@ type Mutation {
login(params: LoginInput!): AuthResponse! login(params: LoginInput!): AuthResponse!
logout: Response! logout: Response!
updateProfile(params: UpdateProfileInput!): Response! updateProfile(params: UpdateProfileInput!): Response!
adminUpdateUser(params: AdminUpdateUserInput!): User!
verifyEmail(params: VerifyEmailInput!): AuthResponse! verifyEmail(params: VerifyEmailInput!): AuthResponse!
resendVerifyEmail(params: ResendVerifyEmailInput!): Response! resendVerifyEmail(params: ResendVerifyEmailInput!): Response!
forgotPassword(params: ForgotPasswordInput!): Response! forgotPassword(params: ForgotPasswordInput!): Response!

View File

@ -27,6 +27,10 @@ func (r *mutationResolver) UpdateProfile(ctx context.Context, params model.Updat
return resolvers.UpdateProfile(ctx, params) return resolvers.UpdateProfile(ctx, params)
} }
func (r *mutationResolver) AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*model.User, error) {
return resolvers.AdminUpdateUser(ctx, params)
}
func (r *mutationResolver) VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.AuthResponse, error) { func (r *mutationResolver) VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.AuthResponse, error) {
return resolvers.VerifyEmail(ctx, params) return resolvers.VerifyEmail(ctx, params)
} }
@ -73,7 +77,5 @@ func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResol
// Query returns generated.QueryResolver implementation. // Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} } func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
type ( type mutationResolver struct{ *Resolver }
mutationResolver struct{ *Resolver } type queryResolver struct{ *Resolver }
queryResolver struct{ *Resolver }
)

View File

@ -0,0 +1,129 @@
package resolvers
import (
"context"
"fmt"
"log"
"strings"
"time"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/session"
"github.com/authorizerdev/authorizer/server/utils"
)
func AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*model.User, error) {
gc, err := utils.GinContextFromContext(ctx)
var res *model.User
if err != nil {
return res, err
}
if !utils.IsSuperAdmin(gc) {
return res, fmt.Errorf("unauthorized")
}
if params.FirstName == nil && params.LastName == nil && params.Image == nil && params.Email == nil && params.Roles == nil {
return res, fmt.Errorf("please enter atleast one param to update")
}
user, err := db.Mgr.GetUserByID(params.ID)
if err != nil {
return res, fmt.Errorf(`User not found`)
}
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.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("%v", user.ID))
utils.DeleteCookie(gc)
user.Email = newEmail
user.EmailVerifiedAt = 0
// 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.VerificationRequest{
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)
}()
}
rolesToSave := ""
if params.Roles != nil && len(params.Roles) > 0 {
currentRoles := strings.Split(user.Roles, ",")
inputRoles := []string{}
for _, item := range params.Roles {
inputRoles = append(inputRoles, *item)
}
if !utils.IsValidRolesArray(inputRoles) {
return res, fmt.Errorf("invalid list of roles")
}
if !utils.IsStringArrayEqual(inputRoles, currentRoles) {
rolesToSave = strings.Join(inputRoles, ",")
}
session.DeleteToken(fmt.Sprintf("%v", user.ID))
utils.DeleteCookie(gc)
}
if rolesToSave != "" {
user.Roles = rolesToSave
}
user, err = db.Mgr.UpdateUser(user)
if err != nil {
log.Println("Error updating user:", err)
return res, err
}
userIdStr := fmt.Sprintf("%v", user.ID)
res = &model.User{
ID: userIdStr,
Email: user.Email,
Image: &user.Image,
FirstName: &user.FirstName,
LastName: &user.LastName,
Roles: strings.Split(user.Roles, ","),
CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt,
}
return res, nil
}