spec(backend): IdentifiableErrorの場合もエラーメッセージが正常に表示されるように (MisskeyIO#463)

This commit is contained in:
まっちゃとーにゅ 2024-02-18 06:21:58 +09:00 committed by GitHub
parent 82cc3987c1
commit 4eb645403f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 67 additions and 37 deletions

View file

@ -523,7 +523,7 @@ export class DriveService {
// If usage limit exceeded // If usage limit exceeded
if (driveCapacity < usage + info.size) { if (driveCapacity < usage + info.size) {
if (isLocalUser) { 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); await this.expireOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as MiRemoteUser, driveCapacity - info.size);
} }

View file

@ -271,7 +271,7 @@ export class NoteCreateService implements OnApplicationShutdown {
if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', meta.prohibitedWords)) { 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 }); 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); const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host);

View file

@ -108,7 +108,7 @@ export class ReactionService {
if (note.userId !== user.id) { if (note.userId !== user.id) {
const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id); const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id);
if (blocked) { 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); await this.noteReactionsRepository.insert(record);
} else { } 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 { } else {
throw e; throw e;
@ -288,14 +288,14 @@ export class ReactionService {
}); });
if (exist == null) { 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 // Delete reaction
const result = await this.noteReactionsRepository.delete(exist.id); const result = await this.noteReactionsRepository.delete(exist.id);
if (result.affected !== 1) { 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 // Decrement reactions count

View file

@ -36,7 +36,7 @@ export class UserAuthService {
}); });
if (delta === null) { 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.');
} }
} }
} }

View file

@ -124,8 +124,8 @@ export class UserFollowingService implements OnModuleInit {
await this.userBlockingService.unblock(follower, followee); await this.userBlockingService.unblock(follower, followee);
} else { } else {
// それ以外は単純に例外 // それ以外は単純に例外
if (blocking) throw new IdentifiableError('710e8fb0-b8c3-4922-be49-d5d93d8e6a6e', 'blocking'); 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', 'blocked'); 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 }); const followeeProfile = await this.userProfilesRepository.findOneByOrFail({ userId: followee.id });
@ -538,7 +538,7 @@ export class UserFollowingService implements OnModuleInit {
}); });
if (!requestExist) { 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({ await this.followRequestsRepository.delete({
@ -564,7 +564,7 @@ export class UserFollowingService implements OnModuleInit {
}); });
if (request == null) { 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); await this.insertFollowingDoc(followee, follower, false, request.withReplies);

View file

@ -107,7 +107,7 @@ export class WebAuthnService {
const challenge = await this.redisClient.get(`webauthn:challenge:${userId}`); const challenge = await this.redisClient.get(`webauthn:challenge:${userId}`);
if (!challenge) { 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}`); await this.redisClient.del(`webauthn:challenge:${userId}`);
@ -125,13 +125,13 @@ export class WebAuthnService {
}); });
} catch (error) { } catch (error) {
this.logger.error('Failed to verify registration response', { 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; const { verified } = verification;
if (!verified || !verification.registrationInfo) { 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; const { registrationInfo } = verification;
@ -156,7 +156,7 @@ export class WebAuthnService {
}); });
if (keys.length === 0) { 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({ const authenticationOptions = await generateAuthenticationOptions({
@ -178,7 +178,7 @@ export class WebAuthnService {
const challenge = await this.redisClient.get(`webauthn:challenge:${userId}`); const challenge = await this.redisClient.get(`webauthn:challenge:${userId}`);
if (!challenge) { 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}`); await this.redisClient.del(`webauthn:challenge:${userId}`);
@ -189,7 +189,7 @@ export class WebAuthnService {
}); });
if (!key) { 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) { } catch (error) {
this.logger.error('Failed to verify authentication response', { 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; const { verified, authenticationInfo } = verification;

View file

@ -10,9 +10,9 @@ export class IdentifiableError extends Error {
public message: string; public message: string;
public id: string; public id: string;
constructor(id: string, message?: string) { constructor(id: string, message: string) {
super(message); super(message);
this.message = message ?? ''; this.message = message;
this.id = id; this.id = id;
} }
} }

View file

@ -85,7 +85,12 @@ export class ApiCallService implements OnApplicationShutdown {
id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14',
})); }));
} else { } 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 // API invoking
return await ep.exec(data, user, token, file, request.ip, request.headers).catch((err: Error) => { 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; 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 { } else {
const errId = randomUUID(); const errId = randomUUID();
this.logger.error(`Internal error occurred in ${ep.name}: ${err.message}`, { this.logger.error(`Internal error occurred in ${ep.name}: ${err.message}`, {
@ -378,13 +408,21 @@ export class ApiCallService implements OnApplicationShutdown {
stack: err.stack, stack: err.stack,
}, },
}); });
throw new ApiError(null, { throw new ApiError(
e: { {
message: err.message, message: 'Internal error occurred. Please contact us if the error persists.',
code: err.name, code: 'INTERNAL_ERROR',
id: errId, id: '5d37dbcb-891e-41ca-a3d6-e690c97775ac',
kind: 'server',
}, },
}); {
e: {
message: err.message,
code: err.name,
id: errId,
},
},
);
} }
}); });
} }

View file

@ -13,15 +13,7 @@ export class ApiError extends Error {
public httpStatusCode?: number; public httpStatusCode?: number;
public info?: any; public info?: any;
constructor(err?: E | null | undefined, info?: any | null | undefined) { constructor(err: E, 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,
};
super(err.message); super(err.message);
this.message = err.message; this.message = err.message;
this.code = err.code; this.code = err.code;