diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index 56c816d..35ce4e6 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -3,7 +3,7 @@ ## cognito-idp
- 29% implemented + 30% implemented - [ ] AddCustomAttributes - [ ] AdminAddUserToGroup @@ -47,7 +47,7 @@ - [ ] DeleteGroup - [ ] DeleteIdentityProvider - [ ] DeleteResourceServer -- [ ] DeleteUser +- [x] DeleteUser - [x] DeleteUserAttributes - [x] DeleteUserPool - [x] DeleteUserPoolClient diff --git a/server/api/controller.ts b/server/api/controller.ts index a8e2735..5806c7b 100644 --- a/server/api/controller.ts +++ b/server/api/controller.ts @@ -4,6 +4,7 @@ import { authUseCase } from 'domain/user/useCase/authUseCase'; import { mfaUseCase } from 'domain/user/useCase/mfaUseCase'; import { signInUseCase } from 'domain/user/useCase/signInUseCase'; import { signUpUseCase } from 'domain/user/useCase/signUpUseCase'; +import { userUseCase } from 'domain/user/useCase/userUseCase'; import { userPoolUseCase } from 'domain/userPool/useCase/userPoolUseCase'; import { returnPostError } from 'service/returnStatus'; import type { AmzTargets } from '../common/types/auth'; @@ -28,6 +29,7 @@ const useCases: { 'AWSCognitoIdentityProviderService.ListUserPoolClients': userPoolUseCase.listUserPoolClients, 'AWSCognitoIdentityProviderService.CreateUserPool': userPoolUseCase.createUserPool, 'AWSCognitoIdentityProviderService.CreateUserPoolClient': userPoolUseCase.createUserPoolClient, + 'AWSCognitoIdentityProviderService.DeleteUser': userUseCase.deleteUser, 'AWSCognitoIdentityProviderService.DeleteUserPool': userPoolUseCase.deleteUserPool, 'AWSCognitoIdentityProviderService.DeleteUserPoolClient': userPoolUseCase.deleteUserPoolClient, 'AWSCognitoIdentityProviderService.ListUsers': authUseCase.listUsers, diff --git a/server/common/types/auth.ts b/server/common/types/auth.ts index 4cee8d4..13573ae 100644 --- a/server/common/types/auth.ts +++ b/server/common/types/auth.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-lines */ import type { AdminCreateUserRequest, AdminCreateUserResponse, @@ -23,6 +24,7 @@ import type { DeleteUserAttributesResponse, DeleteUserPoolClientRequest, DeleteUserPoolRequest, + DeleteUserRequest, GetUserResponse, ListUserPoolClientsRequest, ListUserPoolClientsResponse, @@ -81,6 +83,8 @@ export type CreateUserPoolClientTarget = TargetBody< CreateUserPoolClientResponse >; +export type DeleteUserTarget = TargetBody>; + export type DeleteUserPoolTarget = TargetBody>; export type DeleteUserPoolClientTarget = TargetBody< @@ -177,6 +181,7 @@ export type AmzTargets = { 'AWSCognitoIdentityProviderService.ListUserPoolClients': ListUserPoolClientsTarget; 'AWSCognitoIdentityProviderService.CreateUserPool': CreateUserPoolTarget; 'AWSCognitoIdentityProviderService.CreateUserPoolClient': CreateUserPoolClientTarget; + 'AWSCognitoIdentityProviderService.DeleteUser': DeleteUserTarget; 'AWSCognitoIdentityProviderService.DeleteUserPool': DeleteUserPoolTarget; 'AWSCognitoIdentityProviderService.DeleteUserPoolClient': DeleteUserPoolClientTarget; 'AWSCognitoIdentityProviderService.AdminGetUser': AdminGetUserTarget; diff --git a/server/domain/user/model/userMethod.ts b/server/domain/user/model/userMethod.ts new file mode 100644 index 0000000..3c0fe3d --- /dev/null +++ b/server/domain/user/model/userMethod.ts @@ -0,0 +1,9 @@ +import type { EntityId } from 'common/types/brandedId'; +import type { UserEntity } from 'common/types/user'; +import { brandedId } from 'service/brandedId'; + +export const userMethod = { + delete: (user: UserEntity): EntityId['deletableUser'] => { + return brandedId.deletableUser.entity.parse(user.id); + }, +}; diff --git a/server/domain/user/repository/userCommand.ts b/server/domain/user/repository/userCommand.ts index 9be095e..56ff868 100644 --- a/server/domain/user/repository/userCommand.ts +++ b/server/domain/user/repository/userCommand.ts @@ -1,6 +1,6 @@ import type { Prisma } from '@prisma/client'; import type { EntityId } from 'common/types/brandedId'; -import type { UserAttributeEntity, UserEntity } from 'common/types/user'; +import type { UserEntity } from 'common/types/user'; export const userCommand = { // eslint-disable-next-line complexity @@ -61,9 +61,8 @@ export const userCommand = { delete: async ( tx: Prisma.TransactionClient, deletableUserId: EntityId['deletableUser'], - attributes: UserAttributeEntity[], ): Promise => { - await tx.userAttribute.deleteMany({ where: { id: { in: attributes.map((attr) => attr.id) } } }); + await tx.userAttribute.deleteMany({ where: { userId: deletableUserId } }); await tx.user.delete({ where: { id: deletableUserId } }); }, }; diff --git a/server/domain/user/useCase/adminUseCase.ts b/server/domain/user/useCase/adminUseCase.ts index 7a3ec69..80ea249 100644 --- a/server/domain/user/useCase/adminUseCase.ts +++ b/server/domain/user/useCase/adminUseCase.ts @@ -73,7 +73,7 @@ export const adminUseCase = { const user = await userQuery.findByName(tx, req.Username); const deletableId = adminMethod.deleteUser(user, req.UserPoolId); - await userCommand.delete(tx, deletableId, user.attributes); + await userCommand.delete(tx, deletableId); return {}; }), diff --git a/server/domain/user/useCase/userUseCase.ts b/server/domain/user/useCase/userUseCase.ts new file mode 100644 index 0000000..8310243 --- /dev/null +++ b/server/domain/user/useCase/userUseCase.ts @@ -0,0 +1,26 @@ +import assert from 'assert'; +import type { DeleteUserTarget } from 'common/types/auth'; +import { jwtDecode } from 'jwt-decode'; +import { transaction } from 'service/prismaClient'; +import type { AccessTokenJwt } from 'service/types'; +import { userMethod } from '../model/userMethod'; +import { userCommand } from '../repository/userCommand'; +import { userQuery } from '../repository/userQuery'; + +export const userUseCase = { + deleteUser: (req: DeleteUserTarget['reqBody']): Promise => + transaction(async (tx) => { + assert(req.AccessToken); + + const decoded = jwtDecode(req.AccessToken); + const user = await userQuery.findById(tx, decoded.sub); + + assert(user.kind === 'cognito'); + + const deletableId = userMethod.delete(user); + + await userCommand.delete(tx, deletableId); + + return {}; + }), +}; diff --git a/server/tests/sdk/pool.test.ts b/server/tests/sdk/pool.test.ts index 61a95eb..f268a09 100644 --- a/server/tests/sdk/pool.test.ts +++ b/server/tests/sdk/pool.test.ts @@ -20,7 +20,6 @@ test(CreateUserPoolCommand.name, async () => { expect(client.UserPoolClient?.ClientName === 'testClient').toBeTruthy(); }); -// eslint-disable-next-line complexity test(DeleteUserPoolCommand.name, async () => { const pool = await cognitoClient.send(new CreateUserPoolCommand({ PoolName: 'testPool' })); @@ -41,8 +40,8 @@ test(DeleteUserPoolCommand.name, async () => { const res2 = await cognitoClient.send(new ListUserPoolsCommand({ MaxResults: 100 })); - expect(res1.UserPools?.length).toBe(2); - expect(res2.UserPools?.length).toBe(1); + expect(res1.UserPools).toHaveLength(2); + expect(res2.UserPools).toHaveLength(1); }); // eslint-disable-next-line complexity @@ -67,6 +66,6 @@ test(DeleteUserPoolClientCommand.name, async () => { new ListUserPoolClientsCommand({ UserPoolId: pool.UserPool?.Id, MaxResults: 100 }), ); - expect(res1.UserPoolClients?.length).toBe(1); - expect(res2.UserPoolClients?.length).toBe(0); + expect(res1.UserPoolClients).toHaveLength(1); + expect(res2.UserPoolClients).toHaveLength(0); }); diff --git a/server/tests/sdk/user.test.ts b/server/tests/sdk/user.test.ts index 1af04c5..a3d13f5 100644 --- a/server/tests/sdk/user.test.ts +++ b/server/tests/sdk/user.test.ts @@ -1,10 +1,13 @@ import { DeleteUserAttributesCommand, + DeleteUserCommand, GetUserCommand, + ListUsersCommand, UpdateUserAttributesCommand, VerifyUserAttributeCommand, } from '@aws-sdk/client-cognito-identity-provider'; import { cognitoClient } from 'service/cognito'; +import { DEFAULT_USER_POOL_ID } from 'service/envValues'; import { createCognitoUserAndToken, createSocialUserAndToken, @@ -106,6 +109,19 @@ test(VerifyUserAttributeCommand.name, async () => { expect(emailAttr?.Value).toBe(newEmail); }); +test(DeleteUserCommand.name, async () => { + const token = await createCognitoUserAndToken(); + + const res1 = await cognitoClient.send(new ListUsersCommand({ UserPoolId: DEFAULT_USER_POOL_ID })); + + await cognitoClient.send(new DeleteUserCommand(token)); + + const res2 = await cognitoClient.send(new ListUsersCommand({ UserPoolId: DEFAULT_USER_POOL_ID })); + + expect(res1.Users).toHaveLength(1); + expect(res2.Users).toHaveLength(0); +}); + test(DeleteUserAttributesCommand.name, async () => { const token = await createCognitoUserAndToken(); const attrName1 = 'custom:test1';