From 4eb645403ff4c2a65ad0143125c37d96cadddd5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=BE=E3=81=A3=E3=81=A1=E3=82=83=E3=81=A8=E3=83=BC?= =?UTF-8?q?=E3=81=AB=E3=82=85?= <17376330+u1-liquid@users.noreply.github.com> Date: Sun, 18 Feb 2024 06:21:58 +0900 Subject: [PATCH] =?UTF-8?q?spec(backend):=20IdentifiableError=E3=81=AE?= =?UTF-8?q?=E5=A0=B4=E5=90=88=E3=82=82=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=A1?= =?UTF-8?q?=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=81=8C=E6=AD=A3=E5=B8=B8?= =?UTF-8?q?=E3=81=AB=E8=A1=A8=E7=A4=BA=E3=81=95=E3=82=8C=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=20(MisskeyIO#463)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/core/DriveService.ts | 2 +- .../backend/src/core/NoteCreateService.ts | 2 +- packages/backend/src/core/ReactionService.ts | 8 +-- packages/backend/src/core/UserAuthService.ts | 2 +- .../backend/src/core/UserFollowingService.ts | 8 +-- packages/backend/src/core/WebAuthnService.ts | 14 ++--- .../backend/src/misc/identifiable-error.ts | 4 +- .../backend/src/server/api/ApiCallService.ts | 54 ++++++++++++++++--- packages/backend/src/server/api/error.ts | 10 +--- 9 files changed, 67 insertions(+), 37 deletions(-) diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index c0f2ff78e1..dd21f0ab00 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -523,7 +523,7 @@ export class DriveService { // If usage limit exceeded if (driveCapacity < usage + info.size) { if (isLocalUser) { - throw new IdentifiableError('c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6', 'No free space.'); + throw new IdentifiableError('c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6', 'No free space in drive.'); } await this.expireOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as MiRemoteUser, driveCapacity - info.size); } diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 58adce6c72..8c06e4af5a 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -271,7 +271,7 @@ export class NoteCreateService implements OnApplicationShutdown { if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', meta.prohibitedWords)) { this.logger.error('Request rejected because prohibited words are included', { user: user.id, note: data }); - throw new IdentifiableError('057d8d3e-b7ca-4f8b-b38c-dcdcbf34dc30'); + throw new IdentifiableError('057d8d3e-b7ca-4f8b-b38c-dcdcbf34dc30', 'Notes including prohibited words are not allowed.'); } const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host); diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index 3ac6cbe390..f55bad9931 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -108,7 +108,7 @@ export class ReactionService { if (note.userId !== user.id) { const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id); if (blocked) { - throw new IdentifiableError('e70412a4-7197-4726-8e74-f3e0deb92aa7'); + throw new IdentifiableError('e70412a4-7197-4726-8e74-f3e0deb92aa7', 'You are blocked by the user.'); } } @@ -181,7 +181,7 @@ export class ReactionService { await this.noteReactionsRepository.insert(record); } else { // 同じリアクションがすでにされていたらエラー - throw new IdentifiableError('51c42bb4-931a-456b-bff7-e5a8a70dd298'); + throw new IdentifiableError('51c42bb4-931a-456b-bff7-e5a8a70dd298', 'You already reacted to this note with the same reaction.'); } } else { throw e; @@ -288,14 +288,14 @@ export class ReactionService { }); if (exist == null) { - throw new IdentifiableError('60527ec9-b4cb-4a88-a6bd-32d3ad26817d', 'not reacted'); + throw new IdentifiableError('60527ec9-b4cb-4a88-a6bd-32d3ad26817d', 'You have not reacted to this note.'); } // Delete reaction const result = await this.noteReactionsRepository.delete(exist.id); if (result.affected !== 1) { - throw new IdentifiableError('60527ec9-b4cb-4a88-a6bd-32d3ad26817d', 'not reacted'); + throw new IdentifiableError('60527ec9-b4cb-4a88-a6bd-32d3ad26817d', 'You have not reacted to this note.'); } // Decrement reactions count diff --git a/packages/backend/src/core/UserAuthService.ts b/packages/backend/src/core/UserAuthService.ts index 375e245dc9..39eb771d6c 100644 --- a/packages/backend/src/core/UserAuthService.ts +++ b/packages/backend/src/core/UserAuthService.ts @@ -36,7 +36,7 @@ export class UserAuthService { }); if (delta === null) { - throw new IdentifiableError('7d0a7d85-206c-4d16-8cf3-8af92249a082', 'authentication failed'); + throw new IdentifiableError('7d0a7d85-206c-4d16-8cf3-8af92249a082', 'Two-factor authentication failed.'); } } } diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index cf350745cf..8d3cb0241d 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -124,8 +124,8 @@ export class UserFollowingService implements OnModuleInit { await this.userBlockingService.unblock(follower, followee); } else { // それ以外は単純に例外 - if (blocking) throw new IdentifiableError('710e8fb0-b8c3-4922-be49-d5d93d8e6a6e', 'blocking'); - if (blocked) throw new IdentifiableError('3338392a-f764-498d-8855-db939dcf8c48', 'blocked'); + if (blocking) throw new IdentifiableError('710e8fb0-b8c3-4922-be49-d5d93d8e6a6e', 'You have blocked this user.'); + if (blocked) throw new IdentifiableError('3338392a-f764-498d-8855-db939dcf8c48', 'You have been blocked by this user.'); } const followeeProfile = await this.userProfilesRepository.findOneByOrFail({ userId: followee.id }); @@ -538,7 +538,7 @@ export class UserFollowingService implements OnModuleInit { }); if (!requestExist) { - throw new IdentifiableError('17447091-ce07-46dd-b331-c1fd4f15b1e7', 'request not found'); + throw new IdentifiableError('17447091-ce07-46dd-b331-c1fd4f15b1e7', 'No such follow request.'); } await this.followRequestsRepository.delete({ @@ -564,7 +564,7 @@ export class UserFollowingService implements OnModuleInit { }); if (request == null) { - throw new IdentifiableError('8884c2dd-5795-4ac9-b27e-6a01d38190f9', 'No follow request.'); + throw new IdentifiableError('8884c2dd-5795-4ac9-b27e-6a01d38190f9', 'No such follow request.'); } await this.insertFollowingDoc(followee, follower, false, request.withReplies); diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts index da8866b4e9..8c7e3a1a70 100644 --- a/packages/backend/src/core/WebAuthnService.ts +++ b/packages/backend/src/core/WebAuthnService.ts @@ -107,7 +107,7 @@ export class WebAuthnService { const challenge = await this.redisClient.get(`webauthn:challenge:${userId}`); if (!challenge) { - throw new IdentifiableError('7dbfb66c-9216-4e2b-9c27-cef2ac8efb84', 'challenge not found'); + throw new IdentifiableError('7dbfb66c-9216-4e2b-9c27-cef2ac8efb84', 'Unable to find registration challenge. Please try again.'); } await this.redisClient.del(`webauthn:challenge:${userId}`); @@ -125,13 +125,13 @@ export class WebAuthnService { }); } catch (error) { this.logger.error('Failed to verify registration response', { error }); - throw new IdentifiableError('5c1446f8-8ca7-4d31-9f39-656afe9c5d87', 'verification failed'); + throw new IdentifiableError('5c1446f8-8ca7-4d31-9f39-656afe9c5d87', 'Unable to verify registration response. Please try again.'); } const { verified } = verification; if (!verified || !verification.registrationInfo) { - throw new IdentifiableError('bb333667-3832-4a80-8bb5-c505be7d710d', 'verification failed'); + throw new IdentifiableError('bb333667-3832-4a80-8bb5-c505be7d710d', 'Unable to verify registration response. Please try again.'); } const { registrationInfo } = verification; @@ -156,7 +156,7 @@ export class WebAuthnService { }); if (keys.length === 0) { - throw new IdentifiableError('f27fd449-9af4-4841-9249-1f989b9fa4a4', 'no keys found'); + throw new IdentifiableError('f27fd449-9af4-4841-9249-1f989b9fa4a4', 'You have no registered security keys or passkeys yet.'); } const authenticationOptions = await generateAuthenticationOptions({ @@ -178,7 +178,7 @@ export class WebAuthnService { const challenge = await this.redisClient.get(`webauthn:challenge:${userId}`); if (!challenge) { - throw new IdentifiableError('2d16e51c-007b-4edd-afd2-f7dd02c947f6', 'challenge not found'); + throw new IdentifiableError('2d16e51c-007b-4edd-afd2-f7dd02c947f6', 'Unable to find authentication challenge. Please try again.'); } await this.redisClient.del(`webauthn:challenge:${userId}`); @@ -189,7 +189,7 @@ export class WebAuthnService { }); if (!key) { - throw new IdentifiableError('36b96a7d-b547-412d-aeed-2d611cdc8cdc', 'unknown key'); + throw new IdentifiableError('36b96a7d-b547-412d-aeed-2d611cdc8cdc', 'Unable to identify your security key. Please try with another key.'); } // マイグレーション @@ -235,7 +235,7 @@ export class WebAuthnService { }); } catch (error) { this.logger.error('Failed to verify authentication response', { error }); - throw new IdentifiableError('b18c89a7-5b5e-4cec-bb5b-0419f332d430', 'verification failed'); + throw new IdentifiableError('b18c89a7-5b5e-4cec-bb5b-0419f332d430', 'Unable to verify authentication response. Please try again.'); } const { verified, authenticationInfo } = verification; diff --git a/packages/backend/src/misc/identifiable-error.ts b/packages/backend/src/misc/identifiable-error.ts index 13c41f1e3b..02a7433d3e 100644 --- a/packages/backend/src/misc/identifiable-error.ts +++ b/packages/backend/src/misc/identifiable-error.ts @@ -10,9 +10,9 @@ export class IdentifiableError extends Error { public message: string; public id: string; - constructor(id: string, message?: string) { + constructor(id: string, message: string) { super(message); - this.message = message ?? ''; + this.message = message; this.id = id; } } diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index 8e407346b2..1954d435ce 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -85,7 +85,12 @@ export class ApiCallService implements OnApplicationShutdown { id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', })); } else { - this.send(reply, 500, new ApiError()); + this.#sendApiError(reply, new ApiError({ + message: 'Internal error occurred. Please contact us if the error persists.', + code: 'INTERNAL_ERROR', + id: '5d37dbcb-891e-41ca-a3d6-e690c97775ac', + kind: 'server', + })); } } @@ -364,8 +369,33 @@ export class ApiCallService implements OnApplicationShutdown { // API invoking return await ep.exec(data, user, token, file, request.ip, request.headers).catch((err: Error) => { - if (err instanceof ApiError || err instanceof IdentifiableError || err instanceof AuthenticationError) { + if (err instanceof ApiError || err instanceof AuthenticationError) { throw err; + } else if (err instanceof IdentifiableError) { + this.logger.error(`Internal error occurred in ${ep.name}: ${err.message}`, { + ep: ep.name, + ps: data, + id: err.id, + error: { + message: err.message, + code: 'INTERNAL_ERROR', + stack: err.stack, + }, + }); + throw new ApiError( + { + message: err.message, + code: 'INTERNAL_ERROR', + id: err.id, + }, + { + e: { + message: err.message, + code: err.name, + id: err.id, + }, + }, + ); } else { const errId = randomUUID(); this.logger.error(`Internal error occurred in ${ep.name}: ${err.message}`, { @@ -378,13 +408,21 @@ export class ApiCallService implements OnApplicationShutdown { stack: err.stack, }, }); - throw new ApiError(null, { - e: { - message: err.message, - code: err.name, - id: errId, + throw new ApiError( + { + message: 'Internal error occurred. Please contact us if the error persists.', + code: 'INTERNAL_ERROR', + id: '5d37dbcb-891e-41ca-a3d6-e690c97775ac', + kind: 'server', }, - }); + { + e: { + message: err.message, + code: err.name, + id: errId, + }, + }, + ); } }); } diff --git a/packages/backend/src/server/api/error.ts b/packages/backend/src/server/api/error.ts index 2f8322a568..67a819e8df 100644 --- a/packages/backend/src/server/api/error.ts +++ b/packages/backend/src/server/api/error.ts @@ -13,15 +13,7 @@ export class ApiError extends Error { public httpStatusCode?: number; public info?: any; - constructor(err?: E | null | undefined, info?: any | null | undefined) { - if (err == null) err = { - message: 'Internal error occurred. Please contact us if the error persists.', - code: 'INTERNAL_ERROR', - id: '5d37dbcb-891e-41ca-a3d6-e690c97775ac', - kind: 'server', - httpStatusCode: 500, - }; - + constructor(err: E, info?: any | null | undefined) { super(err.message); this.message = err.message; this.code = err.code;