diff --git a/eslint.config.mjs b/eslint.config.mjs index 59844af..3fe73c4 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -72,5 +72,6 @@ export default tseslint.config( files: ['**/*.js'], rules: { '@typescript-eslint/no-require-imports': ['off'] }, }, + { files: ['server/tests/**/*.ts'], rules: { 'max-lines': 'off' } }, prettierConfig, ); diff --git a/server/domain/user/repository/userQuery.ts b/server/domain/user/repository/userQuery.ts index e5e068b..9989579 100644 --- a/server/domain/user/repository/userQuery.ts +++ b/server/domain/user/repository/userQuery.ts @@ -1,8 +1,8 @@ import type { Prisma } from '@prisma/client'; import { USER_KINDS } from 'common/constants'; import type { MaybeId } from 'common/types/brandedId'; -import type { CognitoUserEntity, SocialUserEntity, UserEntity } from 'common/types/user'; -import { toCognitoUserEntity, toSocialUserEntity, toUserEntity } from './toUserEntity'; +import type { SocialUserEntity, UserEntity } from 'common/types/user'; +import { toSocialUserEntity, toUserEntity } from './toUserEntity'; export const userQuery = { countId: (tx: Prisma.TransactionClient, id: string): Promise => @@ -39,10 +39,8 @@ export const userQuery = { id: UserEntity['id'] | MaybeId['socialUser'], ): Promise => tx.user.findUniqueOrThrow({ where: { id }, include: { attributes: true } }).then(toUserEntity), - findByName: (tx: Prisma.TransactionClient, name: string): Promise => - tx.user - .findFirstOrThrow({ where: { name }, include: { attributes: true } }) - .then(toCognitoUserEntity), + findByName: (tx: Prisma.TransactionClient, name: string): Promise => + tx.user.findFirstOrThrow({ where: { name }, include: { attributes: true } }).then(toUserEntity), findByRefreshToken: (tx: Prisma.TransactionClient, refreshToken: string): Promise => tx.user .findFirstOrThrow({ where: { refreshToken }, include: { attributes: true } }) diff --git a/server/domain/user/useCase/adminUseCase.ts b/server/domain/user/useCase/adminUseCase.ts index 80ea249..6766d40 100644 --- a/server/domain/user/useCase/adminUseCase.ts +++ b/server/domain/user/useCase/adminUseCase.ts @@ -22,15 +22,6 @@ import { toAttributeTypes } from '../service/createAttributes'; import { genTokens } from '../service/genTokens'; import { sendTemporaryPassword } from '../service/sendAuthMail'; -const findUser = async ( - tx: Prisma.TransactionClient, - req: AdminCreateUserTarget['reqBody'], -): Promise => { - assert(req.Username); - - return await userQuery.findByName(tx, req.Username); -}; - const createUser = async ( tx: Prisma.TransactionClient, req: AdminCreateUserTarget['reqBody'], @@ -57,7 +48,13 @@ export const adminUseCase = { }, createUser: (req: AdminCreateUserTarget['reqBody']): Promise => transaction(async (tx) => { - const user = await (req.MessageAction === 'RESEND' ? findUser : createUser)(tx, req); + assert(req.Username); + + const user = await (req.MessageAction === 'RESEND' + ? userQuery.findByName(tx, req.Username) + : createUser(tx, req)); + + assert(user.kind === 'cognito'); if (req.MessageAction !== 'SUPPRESS') await sendTemporaryPassword(user); @@ -115,6 +112,8 @@ export const adminUseCase = { const user = await userQuery.findByName(tx, req.Username); + assert(user.kind === 'cognito'); + await userCommand.save(tx, adminMethod.setUserPassword(user, req)); return {}; @@ -127,6 +126,8 @@ export const adminUseCase = { const user = await userQuery.findByName(tx, req.Username); + assert(user.kind === 'cognito'); + await userCommand.save(tx, adminMethod.updateAttributes(user, req.UserAttributes)); return {}; diff --git a/server/domain/user/useCase/authUseCase.ts b/server/domain/user/useCase/authUseCase.ts index 71b57cc..16f2647 100644 --- a/server/domain/user/useCase/authUseCase.ts +++ b/server/domain/user/useCase/authUseCase.ts @@ -79,7 +79,9 @@ export const authUseCase = { transaction(async (tx) => { const poolClient = await userPoolQuery.findClientById(tx, req.ClientId); const user = await userQuery.findByName(tx, req.Username); + assert(poolClient.userPoolId === user.userPoolId); + assert(user.kind === 'cognito'); const forgotUser = cognitoUserMethod.forgotPassword(user); await userCommand.save(tx, forgotUser); @@ -93,6 +95,8 @@ export const authUseCase = { transaction(async (tx) => { const user = await userQuery.findByName(tx, req.Username); + assert(user.kind === 'cognito'); + await userCommand.save( tx, cognitoUserMethod.confirmForgotPassword({ diff --git a/server/domain/user/useCase/signInUseCase.ts b/server/domain/user/useCase/signInUseCase.ts index 766b971..f25e746 100644 --- a/server/domain/user/useCase/signInUseCase.ts +++ b/server/domain/user/useCase/signInUseCase.ts @@ -19,7 +19,9 @@ export const signInUseCase = { userSrpAuth: (req: UserSrpAuthTarget['reqBody']): Promise => transaction(async (tx) => { const user = await userQuery.findByName(tx, req.AuthParameters.USERNAME).catch(() => null); + cognitoAssert(user, 'Incorrect username or password.'); + assert(user.kind === 'cognito'); const { userWithChallenge, ChallengeParameters } = signInMethod.createChallenge( user, @@ -65,6 +67,7 @@ export const signInUseCase = { const jwks = await userPoolQuery.findJwks(tx, user.userPoolId); assert(pool.id === poolClient.userPoolId); + assert(user.kind === 'cognito'); if (req.ChallengeName === 'PASSWORD_VERIFIER') { assert(user.challenge); diff --git a/server/domain/user/useCase/signUpUseCase.ts b/server/domain/user/useCase/signUpUseCase.ts index f716725..5694392 100644 --- a/server/domain/user/useCase/signUpUseCase.ts +++ b/server/domain/user/useCase/signUpUseCase.ts @@ -43,6 +43,9 @@ export const signUpUseCase = { confirmSignUp: (req: ConfirmSignUpTarget['reqBody']): Promise => transaction(async (tx) => { const user = await userQuery.findByName(tx, req.Username); + + assert(user.kind === 'cognito'); + const confirmed = cognitoUserMethod.confirm(user, req.ConfirmationCode); await userCommand.save(tx, confirmed); @@ -57,6 +60,7 @@ export const signUpUseCase = { const user = await userQuery.findByName(tx, req.Username); assert(poolClient.userPoolId === user.userPoolId); + assert(user.kind === 'cognito'); await sendConfirmationCode(user); diff --git a/server/domain/user/useCase/userUseCase.ts b/server/domain/user/useCase/userUseCase.ts index 8310243..7050090 100644 --- a/server/domain/user/useCase/userUseCase.ts +++ b/server/domain/user/useCase/userUseCase.ts @@ -14,9 +14,6 @@ export const userUseCase = { 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); diff --git a/server/tests/api/utils.ts b/server/tests/api/utils.ts index 68a42e3..4cf544d 100644 --- a/server/tests/api/utils.ts +++ b/server/tests/api/utils.ts @@ -66,7 +66,7 @@ export const createSocialUserAndToken = async (): Promise<{ AccessToken: string const user = await noCookieClient.public.socialUsers.$post({ body: { provider: 'Google', - name: 'user1', + name: testUserName, email: `${ulid()}@example.com`, codeChallenge: createHash('sha256').update(codeVerifier).digest('base64url'), userPoolClientId: DEFAULT_USER_POOL_CLIENT_ID, diff --git a/server/tests/sdk/admin.test.ts b/server/tests/sdk/admin.test.ts index 1b2226f..f7bb92e 100644 --- a/server/tests/sdk/admin.test.ts +++ b/server/tests/sdk/admin.test.ts @@ -12,7 +12,12 @@ import assert from 'assert'; import { cognitoClient } from 'service/cognito'; import { DEFAULT_USER_POOL_CLIENT_ID, DEFAULT_USER_POOL_ID } from 'service/envValues'; import { createUserClient, testPassword, testUserName } from 'tests/api/apiClient'; -import { createCognitoUserAndToken, fetchMailBodyAndTrash, inbucketClient } from 'tests/api/utils'; +import { + createCognitoUserAndToken, + createSocialUserAndToken, + fetchMailBodyAndTrash, + inbucketClient, +} from 'tests/api/utils'; import { ulid } from 'ulid'; import { expect, test } from 'vitest'; @@ -107,7 +112,7 @@ test(`${AdminCreateUserCommand.name} - unset TemporaryPassword`, async () => { expect(res.UserStatus).toBe(UserStatusType.FORCE_CHANGE_PASSWORD); }); -test(AdminDeleteUserCommand.name, async () => { +test(`${AdminDeleteUserCommand.name} - cognito`, async () => { const userClient = await createCognitoUserAndToken().then(createUserClient); await cognitoClient.send( @@ -117,6 +122,16 @@ test(AdminDeleteUserCommand.name, async () => { await expect(userClient.private.me.get()).rejects.toThrow(); }); +test(`${AdminDeleteUserCommand.name} - social`, async () => { + const userClient = await createSocialUserAndToken().then(createUserClient); + + await cognitoClient.send( + new AdminDeleteUserCommand({ UserPoolId: DEFAULT_USER_POOL_ID, Username: testUserName }), + ); + + await expect(userClient.private.me.get()).rejects.toThrow(); +}); + test(AdminUpdateUserAttributesCommand.name, async () => { const newEmail = `${ulid()}@example.com`; const attrName1 = 'custom:test1'; diff --git a/server/tests/sdk/user.test.ts b/server/tests/sdk/user.test.ts index a3d13f5..a3861cf 100644 --- a/server/tests/sdk/user.test.ts +++ b/server/tests/sdk/user.test.ts @@ -109,9 +109,20 @@ test(VerifyUserAttributeCommand.name, async () => { expect(emailAttr?.Value).toBe(newEmail); }); -test(DeleteUserCommand.name, async () => { +test(`${DeleteUserCommand.name} - cognito`, 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(`${DeleteUserCommand.name} - social`, async () => { + const token = await createSocialUserAndToken(); const res1 = await cognitoClient.send(new ListUsersCommand({ UserPoolId: DEFAULT_USER_POOL_ID })); await cognitoClient.send(new DeleteUserCommand(token)); @@ -122,7 +133,7 @@ test(DeleteUserCommand.name, async () => { expect(res2.Users).toHaveLength(0); }); -test(DeleteUserAttributesCommand.name, async () => { +test(`${DeleteUserAttributesCommand.name} - cognito`, async () => { const token = await createCognitoUserAndToken(); const attrName1 = 'custom:test1'; const attrName2 = 'custom:test2'; @@ -146,3 +157,28 @@ test(DeleteUserAttributesCommand.name, async () => { expect(user.UserAttributes?.every((attr) => attr.Name !== attrName1)).toBeTruthy(); expect(user.UserAttributes?.some((attr) => attr.Name === attrName2)).toBeTruthy(); }); + +test(`${DeleteUserAttributesCommand.name} - social`, async () => { + const token = await createSocialUserAndToken(); + const attrName1 = 'custom:test1'; + const attrName2 = 'custom:test2'; + + await cognitoClient.send( + new UpdateUserAttributesCommand({ + ...token, + UserAttributes: [ + { Name: attrName1, Value: 'sample1' }, + { Name: attrName2, Value: 'sample2' }, + ], + }), + ); + + await cognitoClient.send( + new DeleteUserAttributesCommand({ ...token, UserAttributeNames: [attrName1] }), + ); + + const user = await cognitoClient.send(new GetUserCommand(token)); + + expect(user.UserAttributes?.every((attr) => attr.Name !== attrName1)).toBeTruthy(); + expect(user.UserAttributes?.some((attr) => attr.Name === attrName2)).toBeTruthy(); +});