summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHazelnoot <acomputerdog@gmail.com>2025-06-06 22:03:53 -0400
committerHazelnoot <acomputerdog@gmail.com>2025-06-09 11:03:51 -0400
commit424e163c6f305830fe2b8aeb6c9fecc2bf93c61a (patch)
treeca2fbae5320499f90d12f16215a2033780c7f85a
parentoutput log messages with correct level (diff)
downloadsharkey-424e163c6f305830fe2b8aeb6c9fecc2bf93c61a.tar.gz
sharkey-424e163c6f305830fe2b8aeb6c9fecc2bf93c61a.tar.bz2
sharkey-424e163c6f305830fe2b8aeb6c9fecc2bf93c61a.zip
fix type errors with JsonLdService and remove unused factory pattern
-rw-r--r--packages/backend/src/core/activitypub/ApRendererService.ts4
-rw-r--r--packages/backend/src/core/activitypub/JsonLdService.ts75
-rw-r--r--packages/backend/src/queue/processors/InboxProcessorService.ts15
-rw-r--r--packages/backend/test/unit/activitypub.ts4
4 files changed, 54 insertions, 44 deletions
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index 46a78687f3..6771d84bdd 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -936,9 +936,7 @@ export class ApRendererService {
const keypair = await this.userKeypairService.getUserKeypair(user.id);
- const jsonLd = this.jsonLdService.use();
- jsonLd.debug = false;
- activity = await jsonLd.signRsaSignature2017(activity, keypair.privateKey, `${this.config.url}/users/${user.id}#main-key`);
+ activity = await this.jsonLdService.signRsaSignature2017(activity, keypair.privateKey, `${this.config.url}/users/${user.id}#main-key`);
return activity;
}
diff --git a/packages/backend/src/core/activitypub/JsonLdService.ts b/packages/backend/src/core/activitypub/JsonLdService.ts
index 08ebeb6707..8f150ab201 100644
--- a/packages/backend/src/core/activitypub/JsonLdService.ts
+++ b/packages/backend/src/core/activitypub/JsonLdService.ts
@@ -13,23 +13,56 @@ import { LoggerService } from '@/core/LoggerService.js';
import { StatusError } from '@/misc/status-error.js';
import { CONTEXT, PRELOADED_CONTEXTS } from './misc/contexts.js';
import { validateContentTypeSetAsJsonLD } from './misc/validator.js';
-import type { JsonLdDocument } from 'jsonld';
+import type { ContextDefinition, JsonLdDocument } from 'jsonld';
import type { JsonLd as JsonLdObject, RemoteDocument } from 'jsonld/jsonld-spec.js';
+// https://stackoverflow.com/a/66252656
+type RemoveIndex<T> = {
+ [ K in keyof T as string extends K
+ ? never
+ : number extends K
+ ? never
+ : symbol extends K
+ ? never
+ : K
+ ] : T[K];
+};
+
+export type Document = RemoveIndex<JsonLdDocument>;
+
+export type Signature = {
+ id?: string;
+ type: string;
+ creator: string;
+ domain?: string;
+ nonce: string;
+ created: string;
+ signatureValue: string;
+};
+
+export type Signed<T extends Document> = T & {
+ signature: Signature;
+};
+
+export function isSigned<T extends Document>(doc: T): doc is Signed<T> {
+ return 'signature' in doc && typeof(doc.signature) === 'object';
+}
+
// RsaSignature2017 implementation is based on https://github.com/transmute-industries/RsaSignature2017
-class JsonLd {
- public preLoad = true;
- public loderTimeout = 5000;
+@Injectable()
+export class JsonLdService {
+ private readonly logger: Logger;
constructor(
private httpRequestService: HttpRequestService,
- private readonly logger: Logger,
+ loggerService: LoggerService,
) {
+ this.logger = loggerService.getLogger('json-ld');
}
@bindThis
- public async signRsaSignature2017(data: any, privateKey: string, creator: string, domain?: string, created?: Date): Promise<any> {
+ public async signRsaSignature2017<T extends Document>(data: T, privateKey: string, creator: string, domain?: string, created?: Date): Promise<Signed<T>> {
const options: {
type: string;
creator: string;
@@ -65,7 +98,7 @@ class JsonLd {
}
@bindThis
- public async verifyRsaSignature2017(data: any, publicKey: string): Promise<boolean> {
+ public async verifyRsaSignature2017(data: Signed<Document>, publicKey: string): Promise<boolean> {
const toBeSigned = await this.createVerifyData(data, data.signature);
const verifier = crypto.createVerify('sha256');
verifier.update(toBeSigned);
@@ -73,7 +106,7 @@ class JsonLd {
}
@bindThis
- public async createVerifyData(data: any, options: any): Promise<string> {
+ public async createVerifyData<T extends Document>(data: T, options: Partial<Signature>): Promise<string> {
const transformedOptions = {
...options,
'@context': 'https://w3id.org/identity/v1',
@@ -83,7 +116,7 @@ class JsonLd {
delete transformedOptions['signatureValue'];
const canonizedOptions = await this.normalize(transformedOptions);
const optionsHash = this.sha256(canonizedOptions.toString());
- const transformedData = { ...data };
+ const transformedData = { ...data } as T & { signature?: unknown };
delete transformedData['signature'];
const cannonidedData = await this.normalize(transformedData);
this.logger.debug('cannonidedData', cannonidedData);
@@ -93,7 +126,8 @@ class JsonLd {
}
@bindThis
- public async compact(data: any, context: any = CONTEXT): Promise<JsonLdDocument> {
+ // TODO our default CONTEXT isn't valid for the library, is this a bug?
+ public async compact(data: Document, context: ContextDefinition = CONTEXT as unknown as ContextDefinition): Promise<Document> {
const customLoader = this.getLoader();
// XXX: Importing jsonld dynamically since Jest frequently fails to import it statically
// https://github.com/misskey-dev/misskey/pull/9894#discussion_r1103753595
@@ -103,7 +137,7 @@ class JsonLd {
}
@bindThis
- public async normalize(data: JsonLdDocument): Promise<string> {
+ public async normalize(data: Document): Promise<string> {
const customLoader = this.getLoader();
return (await import('jsonld')).default.normalize(data, {
documentLoader: customLoader,
@@ -115,7 +149,7 @@ class JsonLd {
return async (url: string): Promise<RemoteDocument> => {
if (!/^https?:\/\//.test(url)) throw new UnrecoverableError(`Invalid URL: ${url}`);
- if (this.preLoad) {
+ {
if (url in PRELOADED_CONTEXTS) {
this.logger.debug(`Preload HIT: ${url}`);
return {
@@ -144,7 +178,6 @@ class JsonLd {
headers: {
Accept: 'application/ld+json, application/json',
},
- timeout: this.loderTimeout,
},
{
throwErrorWhenResponseNotOk: false,
@@ -168,19 +201,3 @@ class JsonLd {
return hash.digest('hex');
}
}
-
-@Injectable()
-export class JsonLdService {
- private readonly logger: Logger;
- constructor(
- private httpRequestService: HttpRequestService,
- loggerService: LoggerService,
- ) {
- this.logger = loggerService.getLogger('json-ld');
- }
-
- @bindThis
- public use(): JsonLd {
- return new JsonLd(this.httpRequestService, this.logger);
- }
-}
diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts
index 612b16dfbf..5f82d558b3 100644
--- a/packages/backend/src/queue/processors/InboxProcessorService.ts
+++ b/packages/backend/src/queue/processors/InboxProcessorService.ts
@@ -21,7 +21,7 @@ import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
import { StatusError } from '@/misc/status-error.js';
import { UtilityService } from '@/core/UtilityService.js';
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
-import { JsonLdService } from '@/core/activitypub/JsonLdService.js';
+import { isSigned, JsonLdService } from '@/core/activitypub/JsonLdService.js';
import { ApInboxService } from '@/core/activitypub/ApInboxService.js';
import { bindThis } from '@/decorators.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
@@ -179,8 +179,8 @@ export class InboxProcessorService implements OnApplicationShutdown {
// また、signatureのsignerは、activity.actorと一致する必要がある
if (!httpSignatureValidated || authUser.user.uri !== actorId) {
// 一致しなくても、でもLD-Signatureがありそうならそっちも見る
- const ldSignature = activity.signature;
- if (ldSignature) {
+ if (isSigned(activity)) {
+ const ldSignature = activity.signature;
if (ldSignature.type !== 'RsaSignature2017') {
throw new Bull.UnrecoverableError(`skip: unsupported LD-signature type ${ldSignature.type}`);
}
@@ -202,24 +202,21 @@ export class InboxProcessorService implements OnApplicationShutdown {
throw new Bull.UnrecoverableError('skip: LD-SignatureのユーザーはpublicKeyを持っていませんでした');
}
- const jsonLd = this.jsonLdService.use();
-
// LD-Signature検証
- const verified = await jsonLd.verifyRsaSignature2017(activity, authUser.key.keyPem).catch(() => false);
+ const verified = await this.jsonLdService.verifyRsaSignature2017(activity, authUser.key.keyPem).catch(() => false);
if (!verified) {
throw new Bull.UnrecoverableError('skip: LD-Signatureの検証に失敗しました');
}
// アクティビティを正規化
- delete activity.signature;
+ const copy = { ...activity, signature: undefined };
try {
- activity = await jsonLd.compact(activity) as IActivity;
+ activity = await this.jsonLdService.compact(copy) as IActivity;
} catch (e) {
throw new Bull.UnrecoverableError(`skip: failed to compact activity: ${e}`);
}
// TODO: 元のアクティビティと非互換な形に正規化される場合は転送をスキップする
// https://github.com/mastodon/mastodon/blob/664b0ca/app/services/activitypub/process_collection_service.rb#L24-L29
- activity.signature = ldSignature;
// もう一度actorチェック
if (authUser.user.uri !== actorId) {
diff --git a/packages/backend/test/unit/activitypub.ts b/packages/backend/test/unit/activitypub.ts
index 94dec16401..9abbb3e7a6 100644
--- a/packages/backend/test/unit/activitypub.ts
+++ b/packages/backend/test/unit/activitypub.ts
@@ -473,8 +473,6 @@ describe('ActivityPub', () => {
describe('JSON-LD', () => {
test('Compaction', async () => {
- const jsonLd = jsonLdService.use();
-
const object = {
'@context': [
'https://www.w3.org/ns/activitystreams',
@@ -493,7 +491,7 @@ describe('ActivityPub', () => {
unknown: 'test test bar',
undefined: 'test test baz',
};
- const compacted = await jsonLd.compact(object);
+ const compacted = await jsonLdService.compact(object);
assert.deepStrictEqual(compacted, {
'@context': CONTEXT,