Add api to deactivate user account

This commit is contained in:
Lakhan Samani 2023-10-11 00:16:53 +05:30
parent ad41bcf792
commit 843bc022fe
7 changed files with 180 additions and 0 deletions

View File

@ -15,4 +15,6 @@ const (
UserAccessEnabledWebhookEvent = `user.access_enabled` UserAccessEnabledWebhookEvent = `user.access_enabled`
// UserDeletedWebhookEvent name for user deleted event // UserDeletedWebhookEvent name for user deleted event
UserDeletedWebhookEvent = `user.deleted` UserDeletedWebhookEvent = `user.deleted`
// UserDeactivatedWebhookEvent name for user deactivated event
UserDeactivatedWebhookEvent = `user.deactivated`
) )

View File

@ -175,6 +175,7 @@ type ComplexityRoot struct {
AdminLogin func(childComplexity int, params model.AdminLoginInput) int AdminLogin func(childComplexity int, params model.AdminLoginInput) int
AdminLogout func(childComplexity int) int AdminLogout func(childComplexity int) int
AdminSignup func(childComplexity int, params model.AdminSignupInput) int AdminSignup func(childComplexity int, params model.AdminSignupInput) int
DeactivateAccount func(childComplexity int) int
DeleteEmailTemplate func(childComplexity int, params model.DeleteEmailTemplateRequest) int DeleteEmailTemplate func(childComplexity int, params model.DeleteEmailTemplateRequest) int
DeleteUser func(childComplexity int, params model.DeleteUserInput) int DeleteUser func(childComplexity int, params model.DeleteUserInput) int
DeleteWebhook func(childComplexity int, params model.WebhookRequest) int DeleteWebhook func(childComplexity int, params model.WebhookRequest) int
@ -347,6 +348,7 @@ type MutationResolver interface {
Revoke(ctx context.Context, params model.OAuthRevokeInput) (*model.Response, error) Revoke(ctx context.Context, params model.OAuthRevokeInput) (*model.Response, error)
VerifyOtp(ctx context.Context, params model.VerifyOTPRequest) (*model.AuthResponse, error) VerifyOtp(ctx context.Context, params model.VerifyOTPRequest) (*model.AuthResponse, error)
ResendOtp(ctx context.Context, params model.ResendOTPRequest) (*model.Response, error) ResendOtp(ctx context.Context, params model.ResendOTPRequest) (*model.Response, error)
DeactivateAccount(ctx context.Context) (*model.Response, error)
DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error)
UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, error) UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, error)
AdminSignup(ctx context.Context, params model.AdminSignupInput) (*model.Response, error) AdminSignup(ctx context.Context, params model.AdminSignupInput) (*model.Response, error)
@ -1159,6 +1161,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.AdminSignup(childComplexity, args["params"].(model.AdminSignupInput)), true return e.complexity.Mutation.AdminSignup(childComplexity, args["params"].(model.AdminSignupInput)), true
case "Mutation.deactivate_account":
if e.complexity.Mutation.DeactivateAccount == nil {
break
}
return e.complexity.Mutation.DeactivateAccount(childComplexity), true
case "Mutation._delete_email_template": case "Mutation._delete_email_template":
if e.complexity.Mutation.DeleteEmailTemplate == nil { if e.complexity.Mutation.DeleteEmailTemplate == nil {
break break
@ -2789,6 +2798,7 @@ type Mutation {
revoke(params: OAuthRevokeInput!): Response! revoke(params: OAuthRevokeInput!): Response!
verify_otp(params: VerifyOTPRequest!): AuthResponse! verify_otp(params: VerifyOTPRequest!): AuthResponse!
resend_otp(params: ResendOTPRequest!): Response! resend_otp(params: ResendOTPRequest!): Response!
deactivate_account: Response!
# admin only apis # admin only apis
_delete_user(params: DeleteUserInput!): Response! _delete_user(params: DeleteUserInput!): Response!
_update_user(params: UpdateUserInput!): User! _update_user(params: UpdateUserInput!): User!
@ -8745,6 +8755,54 @@ func (ec *executionContext) fieldContext_Mutation_resend_otp(ctx context.Context
return fc, nil return fc, nil
} }
func (ec *executionContext) _Mutation_deactivate_account(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_deactivate_account(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().DeactivateAccount(rctx)
})
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ᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Mutation_deactivate_account(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 "message":
return ec.fieldContext_Response_message(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type Response", field.Name)
},
}
return fc, nil
}
func (ec *executionContext) _Mutation__delete_user(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation__delete_user(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation__delete_user(ctx, field) fc, err := ec.fieldContext_Mutation__delete_user(ctx, field)
if err != nil { if err != nil {
@ -18942,6 +19000,15 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
return ec._Mutation_resend_otp(ctx, field) return ec._Mutation_resend_otp(ctx, field)
}) })
if out.Values[i] == graphql.Null {
invalids++
}
case "deactivate_account":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_deactivate_account(ctx, field)
})
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }

View File

@ -587,6 +587,7 @@ type Mutation {
revoke(params: OAuthRevokeInput!): Response! revoke(params: OAuthRevokeInput!): Response!
verify_otp(params: VerifyOTPRequest!): AuthResponse! verify_otp(params: VerifyOTPRequest!): AuthResponse!
resend_otp(params: ResendOTPRequest!): Response! resend_otp(params: ResendOTPRequest!): Response!
deactivate_account: Response!
# admin only apis # admin only apis
_delete_user(params: DeleteUserInput!): Response! _delete_user(params: DeleteUserInput!): Response!
_update_user(params: UpdateUserInput!): User! _update_user(params: UpdateUserInput!): User!

View File

@ -5,6 +5,7 @@ package graph
import ( import (
"context" "context"
"fmt"
"github.com/authorizerdev/authorizer/server/graph/generated" "github.com/authorizerdev/authorizer/server/graph/generated"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
@ -81,6 +82,11 @@ func (r *mutationResolver) ResendOtp(ctx context.Context, params model.ResendOTP
return resolvers.ResendOTPResolver(ctx, params) return resolvers.ResendOTPResolver(ctx, params)
} }
// DeactivateAccount is the resolver for the deactivate_account field.
func (r *mutationResolver) DeactivateAccount(ctx context.Context) (*model.Response, error) {
panic(fmt.Errorf("not implemented: DeactivateAccount - deactivate_account"))
}
// DeleteUser is the resolver for the _delete_user field. // DeleteUser is the resolver for the _delete_user field.
func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) { func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) {
return resolvers.DeleteUserResolver(ctx, params) return resolvers.DeleteUserResolver(ctx, params)

View File

@ -0,0 +1,58 @@
package resolvers
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
log "github.com/sirupsen/logrus"
)
// DeactivateAccountResolver is the resolver for the deactivate_account field.
func DeactivateAccountResolver(ctx context.Context) (*model.Response, error) {
var res *model.Response
gc, err := utils.GinContextFromContext(ctx)
if err != nil {
log.Debug("Failed to get GinContext: ", err)
return res, err
}
accessToken, err := token.GetAccessToken(gc)
if err != nil {
log.Debug("Failed to get access token: ", err)
return res, err
}
claims, err := token.ValidateAccessToken(gc, accessToken)
if err != nil {
log.Debug("Failed to validate access token: ", err)
return res, err
}
userID := claims["sub"].(string)
log := log.WithFields(log.Fields{
"user_id": userID,
})
user, err := db.Provider.GetUserByID(ctx, userID)
if err != nil {
log.Debug("Failed to get user by id: ", err)
return res, err
}
now := time.Now().Unix()
user.RevokedTimestamp = &now
user, err = db.Provider.UpdateUser(ctx, user)
if err != nil {
log.Debug("Failed to update user: ", err)
return res, err
}
go func() {
memorystore.Provider.DeleteAllUserSessions(user.ID)
utils.RegisterEvent(ctx, constants.UserAccessRevokedWebhookEvent, "", user)
}()
res = &model.Response{
Message: `user account deactivated successfully`,
}
return res, nil
}

View File

@ -0,0 +1,45 @@
package test
import (
"context"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func deactivateAccountTests(t *testing.T, s TestSetup) {
t.Helper()
t.Run(`should deactiavte the user account with access token only`, func(t *testing.T) {
req, ctx := createContext(s)
email := "deactiavte_account." + s.TestInfo.Email
resolvers.SignupResolver(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
_, err := resolvers.DeactivateAccountResolver(ctx)
assert.NotNil(t, err, "unauthorized")
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup)
assert.NoError(t, err)
assert.NotNil(t, verificationRequest)
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
assert.NoError(t, err)
assert.NotNil(t, verifyRes)
s.GinContext.Request.Header.Set("Authorization", "Bearer "+*verifyRes.AccessToken)
ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext)
_, err = resolvers.DeactivateAccountResolver(ctx)
assert.NoError(t, err)
s.GinContext.Request.Header.Set("Authorization", "")
assert.Nil(t, err)
_, err = resolvers.ProfileResolver(ctx)
assert.NotNil(t, err, "unauthorized")
cleanData(email)
})
}

View File

@ -143,6 +143,7 @@ func TestResolvers(t *testing.T) {
verifyOTPTest(t, s) verifyOTPTest(t, s)
resendOTPTest(t, s) resendOTPTest(t, s)
validateSessionTests(t, s) validateSessionTests(t, s)
deactivateAccountTests(t, s)
updateAllUsersTest(t, s) updateAllUsersTest(t, s)
webhookLogsTest(t, s) // get logs after above resolver tests are done webhookLogsTest(t, s) // get logs after above resolver tests are done