Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(backend): restore ap-request tests #9997

Merged
merged 2 commits into from
Feb 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 34 additions & 38 deletions packages/backend/src/core/activitypub/ApRequestService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,39 +28,23 @@ type PrivateKey = {
keyId: string;
};

@Injectable()
export class ApRequestService {
private logger: Logger;

constructor(
@Inject(DI.config)
private config: Config,

private userKeypairStoreService: UserKeypairStoreService,
private httpRequestService: HttpRequestService,
private loggerService: LoggerService,
) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
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<string, string> }): Signed {
export class ApRequestCreator {
static createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record<string, string> }): 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({
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']);
const result = this.#signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'digest']);

return {
request,
Expand All @@ -70,21 +54,20 @@ export class ApRequestService {
};
}

@bindThis
private createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record<string, string> }): Signed {
static createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record<string, string> }): Signed {
const u = new URL(args.url);

const request: Request = {
url: u.href,
method: 'GET',
headers: this.objectAssignWithLcKey({
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']);
const result = this.#signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'accept']);

return {
request,
Expand All @@ -94,13 +77,12 @@ export class ApRequestService {
};
}

@bindThis
private signToRequest(request: Request, key: PrivateKey, includeHeaders: string[]): Signed {
const signingString = this.genSigningString(request, includeHeaders);
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, {
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!
Expand All @@ -114,9 +96,8 @@ export class ApRequestService {
};
}

@bindThis
private genSigningString(request: Request, includeHeaders: string[]): string {
request.headers = this.lcObjectKey(request.headers);
static #genSigningString(request: Request, includeHeaders: string[]): string {
request.headers = this.#lcObjectKey(request.headers);

const results: string[] = [];

Expand All @@ -131,16 +112,31 @@ export class ApRequestService {
return results.join('\n');
}

@bindThis
private lcObjectKey(src: Record<string, string>): Record<string, string> {
static #lcObjectKey(src: Record<string, string>): Record<string, string> {
const dst: Record<string, string> = {};
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<string, string>, b: Record<string, string>): Record<string, string> {
return Object.assign(this.lcObjectKey(a), this.lcObjectKey(b));
static #objectAssignWithLcKey(a: Record<string, string>, b: Record<string, string>): Record<string, string> {
return Object.assign(this.#lcObjectKey(a), this.#lcObjectKey(b));
}
}

@Injectable()
export class ApRequestService {
private logger: Logger;

constructor(
@Inject(DI.config)
private config: Config,

private userKeypairStoreService: UserKeypairStoreService,
private httpRequestService: HttpRequestService,
private loggerService: LoggerService,
) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
this.logger = this.loggerService?.getLogger('ap-request'); // なぜか TypeError: Cannot read properties of undefined (reading 'getLogger') と言われる
}

@bindThis
Expand All @@ -149,7 +145,7 @@ export class ApRequestService {

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`,
Expand All @@ -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`,
Expand Down
3 changes: 2 additions & 1 deletion packages/backend/test/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
},
"compileOnSave": false,
"include": [
"./**/*.ts"
"./**/*.ts",
"../src/@types/**/*.ts",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

上にtypeRootsとかありますがモデュールには効かない (microsoft/TypeScript#13581 (comment))

]
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
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 {
scheme: 'Signature',
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,
Expand All @@ -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');

Expand All @@ -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');

Expand Down