mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-06-25 17:10:43 +00:00
Merge ebc5df9cf1 into 8186742c0f
This commit is contained in:
commit
5bad514910
6 changed files with 83 additions and 32 deletions
|
|
@ -58,6 +58,11 @@ import { CollapsedQueue } from '@/misc/collapsed-queue.js';
|
|||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||
|
||||
// TODO: 他のUUIDも定数化が揃った段階で core/errors/note.ts 等に切り出す
|
||||
export const NOTE_CREATE_ERRORS = {
|
||||
PROHIBITED_WORDS: { id: '689ee33f-f97c-479a-ac49-1b9f8140af99' },
|
||||
} as const;
|
||||
|
||||
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
||||
|
||||
class NotificationManager {
|
||||
|
|
@ -485,7 +490,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
}, this.meta.prohibitedWords);
|
||||
|
||||
if (hasProhibitedWords) {
|
||||
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
|
||||
throw new IdentifiableError(NOTE_CREATE_ERRORS.PROHIBITED_WORDS.id, 'Note contains prohibited words');
|
||||
}
|
||||
|
||||
const inSilencedInstance = this.utilityService.isSilencedHost(this.meta.silencedHosts, user.host);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import { bindThis } from '@/decorators.js';
|
|||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { AP_RESOLVER_ERRORS, apResolverErr } from './errors.js';
|
||||
import type { ICollection, IObject, IOrderedCollection } from './type.js';
|
||||
import { isCollectionOrOrderedCollection } from './type.js';
|
||||
import { ApDbResolverService } from './ApDbResolverService.js';
|
||||
|
|
@ -91,7 +91,7 @@ export class Resolver {
|
|||
if (isCollectionOrOrderedCollection(collection)) {
|
||||
return collection;
|
||||
} else {
|
||||
throw new IdentifiableError('f100eccf-f347-43fb-9b45-96a0831fb635', `unrecognized collection type: ${collection.type}`);
|
||||
throw apResolverErr(AP_RESOLVER_ERRORS.UNRECOGNIZED_COLLECTION_TYPE, `unrecognized collection type: ${collection.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,15 +105,15 @@ export class Resolver {
|
|||
// URLs with fragment parts cannot be resolved correctly because
|
||||
// the fragment part does not get transmitted over HTTP(S).
|
||||
// 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 apResolverErr(AP_RESOLVER_ERRORS.URL_WITH_FRAGMENT, `cannot resolve URL with fragment: ${value}`);
|
||||
}
|
||||
|
||||
if (this.history.has(value)) {
|
||||
throw new IdentifiableError('0dc86cf6-7cd6-4e56-b1e6-5903d62d7ea5', 'cannot resolve already resolved one');
|
||||
throw apResolverErr(AP_RESOLVER_ERRORS.ALREADY_RESOLVED, 'cannot resolve already resolved one');
|
||||
}
|
||||
|
||||
if (this.history.size > this.recursionLimit) {
|
||||
throw new IdentifiableError('d592da9f-822f-4d91-83d7-4ceefabcf3d2', `hit recursion limit: ${this.utilityService.extractDbHost(value)}`);
|
||||
throw apResolverErr(AP_RESOLVER_ERRORS.RECURSION_LIMIT, `hit recursion limit: ${this.utilityService.extractDbHost(value)}`);
|
||||
}
|
||||
|
||||
this.history.add(value);
|
||||
|
|
@ -124,7 +124,7 @@ export class Resolver {
|
|||
}
|
||||
|
||||
if (!this.utilityService.isFederationAllowedHost(host)) {
|
||||
throw new IdentifiableError('09d79f9e-64f1-4316-9cfa-e75c4d091574', 'Instance is blocked');
|
||||
throw apResolverErr(AP_RESOLVER_ERRORS.INSTANCE_BLOCKED, 'Instance is blocked');
|
||||
}
|
||||
|
||||
if (this.meta.signToActivityPubGet && !this.user) {
|
||||
|
|
@ -140,7 +140,7 @@ export class Resolver {
|
|||
!(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') :
|
||||
object['@context'] !== 'https://www.w3.org/ns/activitystreams'
|
||||
) {
|
||||
throw new IdentifiableError('72180409-793c-4973-868e-5a118eb5519b', 'invalid response');
|
||||
throw apResolverErr(AP_RESOLVER_ERRORS.INVALID_RESPONSE, 'invalid response');
|
||||
}
|
||||
|
||||
return object;
|
||||
|
|
@ -149,7 +149,7 @@ export class Resolver {
|
|||
@bindThis
|
||||
private resolveLocal(url: string): Promise<IObject> {
|
||||
const parsed = this.apDbResolverService.parseUri(url);
|
||||
if (!parsed.local) throw new IdentifiableError('02b40cd0-fa92-4b0c-acc9-fb2ada952ab8', 'resolveLocal: not local');
|
||||
if (!parsed.local) throw apResolverErr(AP_RESOLVER_ERRORS.NOT_LOCAL, 'resolveLocal: not local');
|
||||
|
||||
switch (parsed.type) {
|
||||
case 'notes':
|
||||
|
|
@ -178,7 +178,7 @@ export class Resolver {
|
|||
case 'follows':
|
||||
return this.followRequestsRepository.findOneBy({ id: parsed.id })
|
||||
.then(async followRequest => {
|
||||
if (followRequest == null) throw new IdentifiableError('a9d946e5-d276-47f8-95fb-f04230289bb0', 'resolveLocal: invalid follow request ID');
|
||||
if (followRequest == null) throw apResolverErr(AP_RESOLVER_ERRORS.INVALID_FOLLOW_REQUEST_ID, 'resolveLocal: invalid follow request ID');
|
||||
const [follower, followee] = await Promise.all([
|
||||
this.usersRepository.findOneBy({
|
||||
id: followRequest.followerId,
|
||||
|
|
@ -190,12 +190,12 @@ export class Resolver {
|
|||
}),
|
||||
]);
|
||||
if (follower == null || followee == null) {
|
||||
throw new IdentifiableError('06ae3170-1796-4d93-a697-2611ea6d83b6', 'resolveLocal: follower or followee does not exist');
|
||||
throw apResolverErr(AP_RESOLVER_ERRORS.FOLLOWER_OR_FOLLOWEE_NOT_FOUND, 'resolveLocal: follower or followee does not exist');
|
||||
}
|
||||
return this.apRendererService.addContext(this.apRendererService.renderFollow(follower as MiLocalUser | MiRemoteUser, followee as MiLocalUser | MiRemoteUser, url));
|
||||
});
|
||||
default:
|
||||
throw new IdentifiableError('7a5d2fc0-94bc-4db6-b8b8-1bf24a2e23d0', `resolveLocal: type ${parsed.type} unhandled`);
|
||||
throw apResolverErr(AP_RESOLVER_ERRORS.UNHANDLED_TYPE, `resolveLocal: type ${parsed.type} unhandled`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
40
packages/backend/src/core/activitypub/errors.ts
Normal file
40
packages/backend/src/core/activitypub/errors.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
|
||||
type ApErrorDef = { readonly id: string };
|
||||
|
||||
function mkErr(def: ApErrorDef, message?: string): IdentifiableError {
|
||||
return new IdentifiableError(def.id, message);
|
||||
}
|
||||
|
||||
// Resolver
|
||||
export const AP_RESOLVER_ERRORS = {
|
||||
UNRECOGNIZED_COLLECTION_TYPE: { id: 'f100eccf-f347-43fb-9b45-96a0831fb635' },
|
||||
URL_WITH_FRAGMENT: { id: 'b94fd5b1-0e3b-4678-9df2-dad4cd515ab2' },
|
||||
ALREADY_RESOLVED: { id: '0dc86cf6-7cd6-4e56-b1e6-5903d62d7ea5' },
|
||||
RECURSION_LIMIT: { id: 'd592da9f-822f-4d91-83d7-4ceefabcf3d2' },
|
||||
INSTANCE_BLOCKED: { id: '09d79f9e-64f1-4316-9cfa-e75c4d091574' },
|
||||
INVALID_RESPONSE: { id: '72180409-793c-4973-868e-5a118eb5519b' },
|
||||
NOT_LOCAL: { id: '02b40cd0-fa92-4b0c-acc9-fb2ada952ab8' },
|
||||
INVALID_FOLLOW_REQUEST_ID: { id: 'a9d946e5-d276-47f8-95fb-f04230289bb0' },
|
||||
FOLLOWER_OR_FOLLOWEE_NOT_FOUND: { id: '06ae3170-1796-4d93-a697-2611ea6d83b6' },
|
||||
UNHANDLED_TYPE: { id: '7a5d2fc0-94bc-4db6-b8b8-1bf24a2e23d0' },
|
||||
} as const satisfies Record<string, ApErrorDef>;
|
||||
|
||||
// ApNoteService
|
||||
export const AP_NOTE_ERRORS = {
|
||||
INVALID_NOTE: { id: 'd450b8a9-48e4-4dab-ae36-f4db763fda7c' },
|
||||
ACTOR_SUSPENDED: { id: '85ab9bd7-3a41-4530-959d-f07073900109' },
|
||||
} as const satisfies Record<string, ApErrorDef>;
|
||||
|
||||
export function apResolverErr(def: typeof AP_RESOLVER_ERRORS[keyof typeof AP_RESOLVER_ERRORS], message?: string): IdentifiableError {
|
||||
return mkErr(def, message);
|
||||
}
|
||||
|
||||
export function apNoteErr(def: typeof AP_NOTE_ERRORS[keyof typeof AP_NOTE_ERRORS], message?: string): IdentifiableError {
|
||||
return mkErr(def, message);
|
||||
}
|
||||
|
|
@ -24,6 +24,8 @@ import { UtilityService } from '@/core/UtilityService.js';
|
|||
import { bindThis } from '@/decorators.js';
|
||||
import { checkHttps } from '@/misc/check-https.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { NOTE_CREATE_ERRORS } from '@/core/NoteCreateService.js';
|
||||
import { AP_NOTE_ERRORS, apNoteErr } from '../errors.js';
|
||||
import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js';
|
||||
import { ApLoggerService } from '../ApLoggerService.js';
|
||||
import { ApMfmService } from '../ApMfmService.js';
|
||||
|
|
@ -85,27 +87,27 @@ export class ApNoteService {
|
|||
const apType = getApType(object);
|
||||
|
||||
if (apType == null || !validPost.includes(apType)) {
|
||||
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: invalid object type ${apType ?? 'undefined'}`);
|
||||
return apNoteErr(AP_NOTE_ERRORS.INVALID_NOTE, `invalid Note: invalid object type ${apType ?? 'undefined'}`);
|
||||
}
|
||||
|
||||
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 apNoteErr(AP_NOTE_ERRORS.INVALID_NOTE, `invalid Note: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`);
|
||||
}
|
||||
|
||||
const actualHost = object.attributedTo && this.utilityService.extractDbHost(getOneApId(object.attributedTo));
|
||||
if (object.attributedTo && actualHost !== expectHost) {
|
||||
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`);
|
||||
return apNoteErr(AP_NOTE_ERRORS.INVALID_NOTE, `invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`);
|
||||
}
|
||||
|
||||
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 apNoteErr(AP_NOTE_ERRORS.INVALID_NOTE, 'invalid Note: published timestamp is malformed');
|
||||
}
|
||||
|
||||
if (actor) {
|
||||
const attribution = (object.attributedTo) ? getOneApId(object.attributedTo) : 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 apNoteErr(AP_NOTE_ERRORS.INVALID_NOTE, `invalid Note: attribution does not match the actor that send it. attribution: ${attribution}, actor: ${actor.uri}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -174,7 +176,7 @@ export class ApNoteService {
|
|||
// eslint-disable-next-line no-param-reassign
|
||||
actor ??= await this.apPersonService.fetchPerson(uri) as MiRemoteUser | undefined;
|
||||
if (actor && actor.isSuspended) {
|
||||
throw new IdentifiableError('85ab9bd7-3a41-4530-959d-f07073900109', 'actor has been suspended');
|
||||
throw apNoteErr(AP_NOTE_ERRORS.ACTOR_SUSPENDED, 'actor has been suspended');
|
||||
}
|
||||
|
||||
const apMentionRawCount = new Set(this.apMentionService.extractApMentionObjects(note.tag).map(x => x.href)).size;
|
||||
|
|
@ -202,7 +204,9 @@ export class ApNoteService {
|
|||
*/
|
||||
const hasProhibitedWords = this.noteCreateService.checkProhibitedWordsContain({ cw, text, pollChoices: poll?.choices });
|
||||
if (hasProhibitedWords) {
|
||||
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
|
||||
// NoteCreateService側でも同じエラーコードを使用しているため、こちらでも同じIDを使用する.
|
||||
// FIXME: checkProhibitedWordsContainの中にthrowを押し込んでもいいかもしれない
|
||||
throw new IdentifiableError(NOTE_CREATE_ERRORS.PROHIBITED_WORDS.id, 'Note contains prohibited words');
|
||||
}
|
||||
//#endregion
|
||||
|
||||
|
|
@ -211,7 +215,7 @@ export class ApNoteService {
|
|||
|
||||
// 解決した投稿者が凍結されていたらスキップ
|
||||
if (actor.isSuspended) {
|
||||
throw new IdentifiableError('85ab9bd7-3a41-4530-959d-f07073900109', 'actor has been suspended');
|
||||
throw apNoteErr(AP_NOTE_ERRORS.ACTOR_SUSPENDED, 'actor has been suspended');
|
||||
}
|
||||
|
||||
const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver);
|
||||
|
|
@ -414,7 +418,7 @@ export class ApNoteService {
|
|||
publicUrl: tag.icon.url,
|
||||
updatedAt: new Date(),
|
||||
// _misskey_license が存在しなければ `null`
|
||||
license: (tag._misskey_license?.freeText ?? null)
|
||||
license: (tag._misskey_license?.freeText ?? null),
|
||||
});
|
||||
|
||||
const emoji = await this.emojisRepository.findOneBy({ host, name });
|
||||
|
|
@ -437,7 +441,7 @@ export class ApNoteService {
|
|||
updatedAt: new Date(),
|
||||
aliases: [],
|
||||
// _misskey_license が存在しなければ `null`
|
||||
license: (tag._misskey_license?.freeText ?? null)
|
||||
license: (tag._misskey_license?.freeText ?? null),
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
|||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { AP_RESOLVER_ERRORS } from '@/core/activitypub/errors.js';
|
||||
import { FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
|
|
@ -154,23 +155,23 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
if (err instanceof IdentifiableError) {
|
||||
switch (err.id) {
|
||||
// resolve
|
||||
case 'b94fd5b1-0e3b-4678-9df2-dad4cd515ab2':
|
||||
case AP_RESOLVER_ERRORS.URL_WITH_FRAGMENT.id:
|
||||
throw new ApiError(meta.errors.uriInvalid);
|
||||
case '0dc86cf6-7cd6-4e56-b1e6-5903d62d7ea5':
|
||||
case 'd592da9f-822f-4d91-83d7-4ceefabcf3d2':
|
||||
case AP_RESOLVER_ERRORS.ALREADY_RESOLVED.id:
|
||||
case AP_RESOLVER_ERRORS.RECURSION_LIMIT.id:
|
||||
throw new ApiError(meta.errors.requestFailed);
|
||||
case '09d79f9e-64f1-4316-9cfa-e75c4d091574':
|
||||
case AP_RESOLVER_ERRORS.INSTANCE_BLOCKED.id:
|
||||
throw new ApiError(meta.errors.federationNotAllowed);
|
||||
case '72180409-793c-4973-868e-5a118eb5519b':
|
||||
case AP_RESOLVER_ERRORS.INVALID_RESPONSE.id:
|
||||
throw new ApiError(meta.errors.responseInvalid);
|
||||
|
||||
// resolveLocal
|
||||
case '02b40cd0-fa92-4b0c-acc9-fb2ada952ab8':
|
||||
case AP_RESOLVER_ERRORS.NOT_LOCAL.id:
|
||||
throw new ApiError(meta.errors.uriInvalid);
|
||||
case 'a9d946e5-d276-47f8-95fb-f04230289bb0':
|
||||
case '06ae3170-1796-4d93-a697-2611ea6d83b6':
|
||||
case AP_RESOLVER_ERRORS.INVALID_FOLLOW_REQUEST_ID.id:
|
||||
case AP_RESOLVER_ERRORS.FOLLOWER_OR_FOLLOWEE_NOT_FOUND.id:
|
||||
throw new ApiError(meta.errors.noSuchObject);
|
||||
case '7a5d2fc0-94bc-4db6-b8b8-1bf24a2e23d0':
|
||||
case AP_RESOLVER_ERRORS.UNHANDLED_TYPE.id:
|
||||
throw new ApiError(meta.errors.responseInvalid);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
|
|||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { NoteCreateService } from '@/core/NoteCreateService.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { NOTE_CREATE_ERRORS } from '@/core/NoteCreateService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
|
|
@ -249,7 +250,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
} catch (err) {
|
||||
// TODO: 他のErrorもここでキャッチしてエラーメッセージを当てるようにしたい
|
||||
if (err instanceof IdentifiableError) {
|
||||
if (err.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') {
|
||||
if (err.id === NOTE_CREATE_ERRORS.PROHIBITED_WORDS.id) {
|
||||
throw new ApiError(meta.errors.containsProhibitedWords);
|
||||
} else if (err.id === '9f466dab-c856-48cd-9e65-ff90ff750580') {
|
||||
throw new ApiError(meta.errors.containsTooManyMentions);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue