From d1d3b48e5164c2335e892d2ac0f520f066d4734b Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Fri, 24 Feb 2023 08:10:48 +0100 Subject: [PATCH] test(backend): restore ap-request tests (#9997) Co-authored-by: tamaina --- .../src/core/activitypub/ApRequestService.ts | 198 +++++++++--------- packages/backend/test/tsconfig.json | 3 +- .../test/{tests => unit}/ap-request.ts | 11 +- 3 files changed, 105 insertions(+), 107 deletions(-) rename packages/backend/test/{tests => unit}/ap-request.ts (78%) diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts index bfd53dfab..71fbc2947 100644 --- a/packages/backend/src/core/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/activitypub/ApRequestService.ts @@ -28,6 +28,101 @@ type PrivateKey = { keyId: string; }; +export class ApRequestCreator { + static createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record }): Signed { + const u = new URL(args.url); + const digestHeader = `SHA-256=${crypto.createHash('sha256').update(args.body).digest('base64')}`; + + const request: Request = { + url: u.href, + method: 'POST', + headers: this.#objectAssignWithLcKey({ + 'Date': new Date().toUTCString(), + 'Host': u.host, + 'Content-Type': 'application/activity+json', + 'Digest': digestHeader, + }, args.additionalHeaders), + }; + + const result = this.#signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'digest']); + + return { + request, + signingString: result.signingString, + signature: result.signature, + signatureHeader: result.signatureHeader, + }; + } + + static createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record }): Signed { + const u = new URL(args.url); + + const request: Request = { + url: u.href, + method: 'GET', + headers: this.#objectAssignWithLcKey({ + 'Accept': 'application/activity+json, application/ld+json', + 'Date': new Date().toUTCString(), + 'Host': new URL(args.url).host, + }, args.additionalHeaders), + }; + + const result = this.#signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'accept']); + + return { + request, + signingString: result.signingString, + signature: result.signature, + signatureHeader: result.signatureHeader, + }; + } + + static #signToRequest(request: Request, key: PrivateKey, includeHeaders: string[]): Signed { + const signingString = this.#genSigningString(request, includeHeaders); + const signature = crypto.sign('sha256', Buffer.from(signingString), key.privateKeyPem).toString('base64'); + const signatureHeader = `keyId="${key.keyId}",algorithm="rsa-sha256",headers="${includeHeaders.join(' ')}",signature="${signature}"`; + + request.headers = this.#objectAssignWithLcKey(request.headers, { + Signature: signatureHeader, + }); + // node-fetch will generate this for us. if we keep 'Host', it won't change with redirects! + delete request.headers['host']; + + return { + request, + signingString, + signature, + signatureHeader, + }; + } + + static #genSigningString(request: Request, includeHeaders: string[]): string { + request.headers = this.#lcObjectKey(request.headers); + + const results: string[] = []; + + for (const key of includeHeaders.map(x => x.toLowerCase())) { + if (key === '(request-target)') { + results.push(`(request-target): ${request.method.toLowerCase()} ${new URL(request.url).pathname}`); + } else { + results.push(`${key}: ${request.headers[key]}`); + } + } + + return results.join('\n'); + } + + static #lcObjectKey(src: Record): Record { + const dst: Record = {}; + for (const key of Object.keys(src).filter(x => x !== '__proto__' && typeof src[x] === 'string')) dst[key.toLowerCase()] = src[key]; + return dst; + } + + static #objectAssignWithLcKey(a: Record, b: Record): Record { + return Object.assign(this.#lcObjectKey(a), this.#lcObjectKey(b)); + } +} + @Injectable() export class ApRequestService { private logger: Logger; @@ -44,112 +139,13 @@ export class ApRequestService { this.logger = this.loggerService?.getLogger('ap-request'); // なぜか TypeError: Cannot read properties of undefined (reading 'getLogger') と言われる } - @bindThis - private createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record }): Signed { - const u = new URL(args.url); - const digestHeader = `SHA-256=${crypto.createHash('sha256').update(args.body).digest('base64')}`; - - const request: Request = { - url: u.href, - method: 'POST', - headers: this.objectAssignWithLcKey({ - 'Date': new Date().toUTCString(), - 'Host': u.host, - 'Content-Type': 'application/activity+json', - 'Digest': digestHeader, - }, args.additionalHeaders), - }; - - const result = this.signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'digest']); - - return { - request, - signingString: result.signingString, - signature: result.signature, - signatureHeader: result.signatureHeader, - }; - } - - @bindThis - private createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record }): Signed { - const u = new URL(args.url); - - const request: Request = { - url: u.href, - method: 'GET', - headers: this.objectAssignWithLcKey({ - 'Accept': 'application/activity+json, application/ld+json', - 'Date': new Date().toUTCString(), - 'Host': new URL(args.url).host, - }, args.additionalHeaders), - }; - - const result = this.signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'accept']); - - return { - request, - signingString: result.signingString, - signature: result.signature, - signatureHeader: result.signatureHeader, - }; - } - - @bindThis - private signToRequest(request: Request, key: PrivateKey, includeHeaders: string[]): Signed { - const signingString = this.genSigningString(request, includeHeaders); - const signature = crypto.sign('sha256', Buffer.from(signingString), key.privateKeyPem).toString('base64'); - const signatureHeader = `keyId="${key.keyId}",algorithm="rsa-sha256",headers="${includeHeaders.join(' ')}",signature="${signature}"`; - - request.headers = this.objectAssignWithLcKey(request.headers, { - Signature: signatureHeader, - }); - // node-fetch will generate this for us. if we keep 'Host', it won't change with redirects! - delete request.headers['host']; - - return { - request, - signingString, - signature, - signatureHeader, - }; - } - - @bindThis - private genSigningString(request: Request, includeHeaders: string[]): string { - request.headers = this.lcObjectKey(request.headers); - - const results: string[] = []; - - for (const key of includeHeaders.map(x => x.toLowerCase())) { - if (key === '(request-target)') { - results.push(`(request-target): ${request.method.toLowerCase()} ${new URL(request.url).pathname}`); - } else { - results.push(`${key}: ${request.headers[key]}`); - } - } - - return results.join('\n'); - } - - @bindThis - private lcObjectKey(src: Record): Record { - const dst: Record = {}; - for (const key of Object.keys(src).filter(x => x !== '__proto__' && typeof src[x] === 'string')) dst[key.toLowerCase()] = src[key]; - return dst; - } - - @bindThis - private objectAssignWithLcKey(a: Record, b: Record): Record { - return Object.assign(this.lcObjectKey(a), this.lcObjectKey(b)); - } - @bindThis public async signedPost(user: { id: User['id'] }, url: string, object: any) { const body = JSON.stringify(object); const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); - const req = this.createSignedPost({ + const req = ApRequestCreator.createSignedPost({ key: { privateKeyPem: keypair.privateKey, keyId: `${this.config.url}/users/${user.id}#main-key`, @@ -176,7 +172,7 @@ export class ApRequestService { public async signedGet(url: string, user: { id: User['id'] }) { const keypair = await this.userKeypairStoreService.getUserKeypair(user.id); - const req = this.createSignedGet({ + const req = ApRequestCreator.createSignedGet({ key: { privateKeyPem: keypair.privateKey, keyId: `${this.config.url}/users/${user.id}#main-key`, diff --git a/packages/backend/test/tsconfig.json b/packages/backend/test/tsconfig.json index 5d91d0923..da82ddc4a 100644 --- a/packages/backend/test/tsconfig.json +++ b/packages/backend/test/tsconfig.json @@ -37,6 +37,7 @@ }, "compileOnSave": false, "include": [ - "./**/*.ts" + "./**/*.ts", + "../src/@types/**/*.ts", ] } diff --git a/packages/backend/test/tests/ap-request.ts b/packages/backend/test/unit/ap-request.ts similarity index 78% rename from packages/backend/test/tests/ap-request.ts rename to packages/backend/test/unit/ap-request.ts index 8c586861a..98f352e1c 100644 --- a/packages/backend/test/tests/ap-request.ts +++ b/packages/backend/test/unit/ap-request.ts @@ -1,7 +1,8 @@ import * as assert from 'assert'; import httpSignature from '@peertube/http-signature'; -import { genRsaKeyPair } from '../../src/misc/gen-key-pair.js'; -import { createSignedPost, createSignedGet } from '../../src/activitypub/ap-request.js'; + +import { genRsaKeyPair } from '@/misc/gen-key-pair.js'; +import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js'; export const buildParsedSignature = (signingString: string, signature: string, algorithm: string) => { return { @@ -9,7 +10,7 @@ export const buildParsedSignature = (signingString: string, signature: string, a params: { keyId: 'KeyID', // dummy, not used for verify algorithm: algorithm, - headers: [ '(request-target)', 'date', 'host', 'digest' ], // dummy, not used for verify + headers: ['(request-target)', 'date', 'host', 'digest'], // dummy, not used for verify signature: signature, }, signingString: signingString, @@ -29,7 +30,7 @@ describe('ap-request', () => { 'User-Agent': 'UA', }; - const req = createSignedPost({ key, url, body, additionalHeaders: headers }); + const req = ApRequestCreator.createSignedPost({ key, url, body, additionalHeaders: headers }); const parsed = buildParsedSignature(req.signingString, req.signature, 'rsa-sha256'); @@ -45,7 +46,7 @@ describe('ap-request', () => { 'User-Agent': 'UA', }; - const req = createSignedGet({ key, url, additionalHeaders: headers }); + const req = ApRequestCreator.createSignedGet({ key, url, additionalHeaders: headers }); const parsed = buildParsedSignature(req.signingString, req.signature, 'rsa-sha256');