add more details to IdentifiableErrors

This commit is contained in:
Hazelnoot 2025-05-22 11:10:45 -04:00
parent 687de6f2f0
commit 4540614f7b
8 changed files with 40 additions and 40 deletions

View file

@ -80,9 +80,9 @@ export class BunnyService {
}); });
req.on('error', (error) => { req.on('error', (error) => {
this.bunnyCdnLogger.error(error); this.bunnyCdnLogger.error('Unhandled error', error);
data.destroy(); data.destroy();
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140bf91', 'An error has occured during the connectiong to BunnyCDN'); throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140bf91', 'An error has occurred while connecting to BunnyCDN', true, error);
}); });
data.pipe(req).on('finish', () => { data.pipe(req).on('finish', () => {

View file

@ -61,7 +61,7 @@ export class NotePiningService {
}); });
if (note == null) { if (note == null) {
throw new IdentifiableError('70c4e51f-5bea-449c-a030-53bee3cce202', 'No such note.'); throw new IdentifiableError('70c4e51f-5bea-449c-a030-53bee3cce202', `Note ${noteId} does not exist`);
} }
await this.db.transaction(async tem => { await this.db.transaction(async tem => {
@ -102,7 +102,7 @@ export class NotePiningService {
}); });
if (note == null) { if (note == null) {
throw new IdentifiableError('b302d4cf-c050-400a-bbb3-be208681f40c', 'No such note.'); throw new IdentifiableError('b302d4cf-c050-400a-bbb3-be208681f40c', `Note ${noteId} does not exist`);
} }
this.userNotePiningsRepository.delete({ this.userNotePiningsRepository.delete({

View file

@ -117,7 +117,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', 'Note not accessible for you.');
} }
} }
@ -322,14 +322,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', 'reaction does not exist');
} }
// 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', 'reaction does not exist');
} }
// Decrement reactions count // Decrement reactions count

View file

@ -79,7 +79,7 @@ export class Resolver {
if (isCollectionOrOrderedCollection(collection)) { if (isCollectionOrOrderedCollection(collection)) {
return collection; return collection;
} else { } else {
throw new IdentifiableError('f100eccf-f347-43fb-9b45-96a0831fb635', `unrecognized collection type: ${collection.type}`); throw new IdentifiableError('f100eccf-f347-43fb-9b45-96a0831fb635', `collection ${getApId(value)} has unsupported type: ${collection.type}`);
} }
} }
@ -276,15 +276,15 @@ export class Resolver {
// URLs with fragment parts cannot be resolved correctly because // URLs with fragment parts cannot be resolved correctly because
// the fragment part does not get transmitted over HTTP(S). // the fragment part does not get transmitted over HTTP(S).
// Avoid strange behaviour by not trying to resolve these at all. // Avoid strange behaviour by not trying to resolve these at all.
throw new IdentifiableError('b94fd5b1-0e3b-4678-9df2-dad4cd515ab2', `cannot resolve URL with fragment: ${value}`); throw new IdentifiableError('b94fd5b1-0e3b-4678-9df2-dad4cd515ab2', `failed to resolve ${value}: URL contains fragment`);
} }
if (this.history.has(value)) { if (this.history.has(value)) {
throw new IdentifiableError('0dc86cf6-7cd6-4e56-b1e6-5903d62d7ea5', `cannot resolve already resolved URL: ${value}`); throw new IdentifiableError('0dc86cf6-7cd6-4e56-b1e6-5903d62d7ea5', `failed to resolve ${value}: recursive resolution blocked`);
} }
if (this.history.size > this.recursionLimit) { if (this.history.size > this.recursionLimit) {
throw new IdentifiableError('d592da9f-822f-4d91-83d7-4ceefabcf3d2', `hit recursion limit: ${value}`); throw new IdentifiableError('d592da9f-822f-4d91-83d7-4ceefabcf3d2', `failed to resolve ${value}: hit recursion limit`);
} }
this.history.add(value); this.history.add(value);
@ -294,7 +294,7 @@ export class Resolver {
} }
if (!this.utilityService.isFederationAllowedHost(host)) { if (!this.utilityService.isFederationAllowedHost(host)) {
throw new IdentifiableError('09d79f9e-64f1-4316-9cfa-e75c4d091574', `cannot fetch AP object ${value}: blocked instance ${host}`); throw new IdentifiableError('09d79f9e-64f1-4316-9cfa-e75c4d091574', `failed to resolve ${value}: instance ${host} is blocked`);
} }
if (this.config.signToActivityPubGet && !this.user) { if (this.config.signToActivityPubGet && !this.user) {
@ -324,7 +324,7 @@ export class Resolver {
!(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') : !(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') :
object['@context'] !== 'https://www.w3.org/ns/activitystreams' object['@context'] !== 'https://www.w3.org/ns/activitystreams'
) { ) {
throw new IdentifiableError('72180409-793c-4973-868e-5a118eb5519b', `invalid AP object ${value}: does not have ActivityStreams context`); throw new IdentifiableError('72180409-793c-4973-868e-5a118eb5519b', `failed to resolve ${value}: response does not have ActivityStreams context`);
} }
// The object ID is already validated to match the final URL's authority by signedGet / getActivityJson. // The object ID is already validated to match the final URL's authority by signedGet / getActivityJson.
@ -340,7 +340,7 @@ export class Resolver {
// Check if the redirect bounce from [allowed domain] to [blocked domain]. // Check if the redirect bounce from [allowed domain] to [blocked domain].
if (!this.utilityService.isFederationAllowedHost(finalHost)) { if (!this.utilityService.isFederationAllowedHost(finalHost)) {
throw new IdentifiableError('0a72bf24-2d9b-4f1d-886b-15aaa31adeda', `cannot fetch AP object ${value}: redirected to blocked instance ${finalHost}`); throw new IdentifiableError('0a72bf24-2d9b-4f1d-886b-15aaa31adeda', `failed to resolve ${value}: redirected to blocked instance ${finalHost}`);
} }
} }
@ -350,7 +350,7 @@ export class Resolver {
@bindThis @bindThis
private resolveLocal(url: string): Promise<IObjectWithId> { private resolveLocal(url: string): Promise<IObjectWithId> {
const parsed = this.apDbResolverService.parseUri(url); const parsed = this.apDbResolverService.parseUri(url);
if (!parsed.local) throw new IdentifiableError('02b40cd0-fa92-4b0c-acc9-fb2ada952ab8', `resolveLocal - not a local URL: ${url}`); if (!parsed.local) throw new IdentifiableError('02b40cd0-fa92-4b0c-acc9-fb2ada952ab8', `failed to resolve local ${url}: not a local URL`);
switch (parsed.type) { switch (parsed.type) {
case 'notes': case 'notes':
@ -380,7 +380,7 @@ export class Resolver {
case 'follows': case 'follows':
return this.followRequestsRepository.findOneBy({ id: parsed.id }) return this.followRequestsRepository.findOneBy({ id: parsed.id })
.then(async followRequest => { .then(async followRequest => {
if (followRequest == null) throw new IdentifiableError('a9d946e5-d276-47f8-95fb-f04230289bb0', `resolveLocal - invalid follow request ID ${parsed.id}: ${url}`); if (followRequest == null) throw new IdentifiableError('a9d946e5-d276-47f8-95fb-f04230289bb0', `failed to resolve local ${url}: invalid follow request ID`);
const [follower, followee] = await Promise.all([ const [follower, followee] = await Promise.all([
this.usersRepository.findOneBy({ this.usersRepository.findOneBy({
id: followRequest.followerId, id: followRequest.followerId,
@ -392,12 +392,12 @@ export class Resolver {
}), }),
]); ]);
if (follower == null || followee == null) { if (follower == null || followee == null) {
throw new IdentifiableError('06ae3170-1796-4d93-a697-2611ea6d83b6', `resolveLocal - follower or followee does not exist: ${url}`); throw new IdentifiableError('06ae3170-1796-4d93-a697-2611ea6d83b6', `failed to resolve local ${url}: follower or followee does not exist`);
} }
return this.apRendererService.addContext(this.apRendererService.renderFollow(follower as MiLocalUser | MiRemoteUser, followee as MiLocalUser | MiRemoteUser, url)); return this.apRendererService.addContext(this.apRendererService.renderFollow(follower as MiLocalUser | MiRemoteUser, followee as MiLocalUser | MiRemoteUser, url));
}); });
default: default:
throw new IdentifiableError('7a5d2fc0-94bc-4db6-b8b8-1bf24a2e23d0', `resolveLocal: type ${parsed.type} unhandled: ${url}`); throw new IdentifiableError('7a5d2fc0-94bc-4db6-b8b8-1bf24a2e23d0', `failed to resolve local ${url}: unsupported type ${parsed.type}`);
} }
} }
} }

View file

@ -12,7 +12,7 @@ export function validateContentTypeSetAsActivityPub(response: Response): void {
const contentType = (response.headers.get('content-type') ?? '').toLowerCase(); const contentType = (response.headers.get('content-type') ?? '').toLowerCase();
if (contentType === '') { if (contentType === '') {
throw new IdentifiableError('d09dc850-b76c-4f45-875a-7389339d78b8', `invalid content type of AP response - no content-type header: ${response.url}`, true); throw new IdentifiableError('d09dc850-b76c-4f45-875a-7389339d78b8', `invalid AP response from ${response.url}: no content-type header`, true);
} }
if ( if (
contentType.startsWith('application/activity+json') || contentType.startsWith('application/activity+json') ||
@ -20,7 +20,7 @@ export function validateContentTypeSetAsActivityPub(response: Response): void {
) { ) {
return; return;
} }
throw new IdentifiableError('dc110060-a5f2-461d-808b-39c62702ca64', `invalid content type of AP response - content type "${contentType}" is not application/activity+json or application/ld+json: ${response.url}`); throw new IdentifiableError('dc110060-a5f2-461d-808b-39c62702ca64', `invalid AP response from ${response.url}: content type "${contentType}" is not application/activity+json or application/ld+json`);
} }
const plusJsonSuffixRegex = /^\s*(application|text)\/[a-zA-Z0-9\.\-\+]+\+json\s*(;|$)/; const plusJsonSuffixRegex = /^\s*(application|text)\/[a-zA-Z0-9\.\-\+]+\+json\s*(;|$)/;
@ -29,7 +29,7 @@ export function validateContentTypeSetAsJsonLD(response: Response): void {
const contentType = (response.headers.get('content-type') ?? '').toLowerCase(); const contentType = (response.headers.get('content-type') ?? '').toLowerCase();
if (contentType === '') { if (contentType === '') {
throw new IdentifiableError('45793ab7-7648-4886-b503-429f8a0d0f73', `invalid content type of JSON LD - no content-type header: ${response.url}`, true); throw new IdentifiableError('45793ab7-7648-4886-b503-429f8a0d0f73', `invalid AP response from ${response.url}: no content-type header`, true);
} }
if ( if (
contentType.startsWith('application/ld+json') || contentType.startsWith('application/ld+json') ||
@ -38,5 +38,5 @@ export function validateContentTypeSetAsJsonLD(response: Response): void {
) { ) {
return; return;
} }
throw new IdentifiableError('4bf8f36b-4d33-4ac9-ad76-63fa11f354e9', `invalid content type of JSON LD - content type "${contentType}" is not application/ld+json or application/json: ${response.url}`); throw new IdentifiableError('4bf8f36b-4d33-4ac9-ad76-63fa11f354e9', `invalid AP response from ${response.url}: content type "${contentType}" is not application/ld+json or application/json`);
} }

View file

@ -101,29 +101,29 @@ export class ApNoteService {
const apType = getApType(object); const apType = getApType(object);
if (apType == null || !validPost.includes(apType)) { if (apType == null || !validPost.includes(apType)) {
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: invalid object type ${apType ?? 'undefined'}`); return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note from ${uri}: invalid object type ${apType ?? 'undefined'}`);
} }
if (object.id && this.utilityService.extractDbHost(object.id) !== expectHost) { if (object.id && this.utilityService.extractDbHost(object.id) !== expectHost) {
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`); return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note from ${uri}: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`);
} }
const actualHost = object.attributedTo && this.utilityService.extractDbHost(getOneApId(object.attributedTo)); const actualHost = object.attributedTo && this.utilityService.extractDbHost(getOneApId(object.attributedTo));
if (object.attributedTo && actualHost !== expectHost) { if (object.attributedTo && actualHost !== expectHost) {
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`); return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note from ${uri}: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`);
} }
if (object.published && !this.idService.isSafeT(new Date(object.published).valueOf())) { if (object.published && !this.idService.isSafeT(new Date(object.published).valueOf())) {
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', 'invalid Note: published timestamp is malformed'); return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', 'invalid Note from ${uri}: published timestamp is malformed');
} }
if (actor) { if (actor) {
const attribution = (object.attributedTo) ? getOneApId(object.attributedTo) : actor.uri; const attribution = (object.attributedTo) ? getOneApId(object.attributedTo) : actor.uri;
if (attribution !== actor.uri) { if (attribution !== actor.uri) {
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: attribution does not match the actor that send it. attribution: ${attribution}, actor: ${actor.uri}`); return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note from ${uri}: attribution does not match the actor that send it. attribution: ${attribution}, actor: ${actor.uri}`);
} }
if (user && attribution !== user.uri) { if (user && attribution !== user.uri) {
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: updated attribution does not match original attribution. updated attribution: ${user.uri}, original attribution: ${attribution}`); return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note from ${uri}: updated attribution does not match original attribution. updated attribution: ${user.uri}, original attribution: ${attribution}`);
} }
} }
@ -197,7 +197,7 @@ export class ApNoteService {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
actor ??= await this.apPersonService.fetchPerson(uri) as MiRemoteUser | undefined; actor ??= await this.apPersonService.fetchPerson(uri) as MiRemoteUser | undefined;
if (actor && actor.isSuspended) { if (actor && actor.isSuspended) {
throw new IdentifiableError('85ab9bd7-3a41-4530-959d-f07073900109', `actor ${uri} has been suspended: ${entryUri}`); throw new IdentifiableError('85ab9bd7-3a41-4530-959d-f07073900109', `failed to create note ${entryUri}: actor ${uri} has been suspended`);
} }
const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver); const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver);
@ -224,7 +224,7 @@ export class ApNoteService {
*/ */
const hasProhibitedWords = this.noteCreateService.checkProhibitedWordsContain({ cw, text, pollChoices: poll?.choices }); const hasProhibitedWords = this.noteCreateService.checkProhibitedWordsContain({ cw, text, pollChoices: poll?.choices });
if (hasProhibitedWords) { if (hasProhibitedWords) {
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', `Note contains prohibited words: ${entryUri}`); throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', `failed to create note ${entryUri}: contains prohibited words`);
} }
//#endregion //#endregion
@ -233,7 +233,7 @@ export class ApNoteService {
// 解決した投稿者が凍結されていたらスキップ // 解決した投稿者が凍結されていたらスキップ
if (actor.isSuspended) { if (actor.isSuspended) {
throw new IdentifiableError('85ab9bd7-3a41-4530-959d-f07073900109', `actor has been suspended: ${entryUri}`); throw new IdentifiableError('85ab9bd7-3a41-4530-959d-f07073900109', `failed to create note ${entryUri}: actor ${actor.id} has been suspended`);
} }
const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver); const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver);
@ -409,7 +409,7 @@ export class ApNoteService {
this.logger.info(`Creating the Note: ${note.id}`); this.logger.info(`Creating the Note: ${note.id}`);
if (actor.isSuspended) { if (actor.isSuspended) {
throw new IdentifiableError('85ab9bd7-3a41-4530-959d-f07073900109', `actor ${actor.id} has been suspended: ${noteUri}`); throw new IdentifiableError('85ab9bd7-3a41-4530-959d-f07073900109', `failed to update note ${entryUri}: actor ${actor.id} has been suspended`);
} }
const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver); const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver);
@ -436,7 +436,7 @@ export class ApNoteService {
*/ */
const hasProhibitedWords = this.noteCreateService.checkProhibitedWordsContain({ cw, text, pollChoices: poll?.choices }); const hasProhibitedWords = this.noteCreateService.checkProhibitedWordsContain({ cw, text, pollChoices: poll?.choices });
if (hasProhibitedWords) { if (hasProhibitedWords) {
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', `Note contains prohibited words: ${noteUri}`); throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', `failed to update note ${noteUri}: contains prohibited words`);
} }
//#endregion //#endregion

View file

@ -15,8 +15,8 @@ export class IdentifiableError extends Error {
*/ */
public readonly isRetryable: boolean; public readonly isRetryable: boolean;
constructor(id: string, message?: string, isRetryable = false) { constructor(id: string, message?: string, isRetryable = false, options?: ErrorOptions) {
super(message); super(message, options);
this.message = message ?? ''; this.message = message ?? '';
this.id = id; this.id = id;
this.isRetryable = isRetryable; this.isRetryable = isRetryable;

View file

@ -36,7 +36,7 @@ export class GetterService {
const note = await this.notesRepository.findOneBy({ id: noteId }); const note = await this.notesRepository.findOneBy({ id: noteId });
if (note == null) { if (note == null) {
throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', `Note ${noteId} does not exist`);
} }
return note; return note;
@ -47,7 +47,7 @@ export class GetterService {
const note = await this.notesRepository.findOne({ where: { id: noteId }, relations: ['user'] }); const note = await this.notesRepository.findOne({ where: { id: noteId }, relations: ['user'] });
if (note == null) { if (note == null) {
throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', `Note ${noteId} does not exist`);
} }
return note; return note;
@ -59,7 +59,7 @@ export class GetterService {
@bindThis @bindThis
public async getEdits(noteId: MiNote['id']) { public async getEdits(noteId: MiNote['id']) {
const edits = await this.noteEditRepository.findBy({ noteId: noteId }).catch(() => { const edits = await this.noteEditRepository.findBy({ noteId: noteId }).catch(() => {
throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', `Note ${noteId} does not exist`);
}); });
return edits; return edits;
@ -73,7 +73,7 @@ export class GetterService {
const user = await this.usersRepository.findOneBy({ id: userId }); const user = await this.usersRepository.findOneBy({ id: userId });
if (user == null) { if (user == null) {
throw new IdentifiableError('15348ddd-432d-49c2-8a5a-8069753becff', 'No such user.'); throw new IdentifiableError('15348ddd-432d-49c2-8a5a-8069753becff', `User ${userId} does not exist`);
} }
return user as MiLocalUser | MiRemoteUser; return user as MiLocalUser | MiRemoteUser;