mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-04 20:06:06 +02:00
* wip * update fake-timers and migrate * fix * remove jest-mock * fix * fix * fix * fix * attempt to fix unit tests * attempt to fix e2e tests * fix federation test [ci skip] * attempt to fix e2e tests * fix typecheck * fix unit tests * fix * attempt to fix e2e * fix * Revert "attempt to fix e2e" This reverts commitb7b7b05d85. * attempt to fix e2e * revert attempt to fix e2e * update deps * update vitest * migrate * attempt to fix e2e * update * fix * remove vite swc plugin as oxc parser can handle decorators * attempt to fix drive/files/create test * Revert "attempt to fix drive/files/create test" This reverts commit4715153375. * fix: エンドポイントにまつわるテストをunitからe2eに移動 * attempt to fix e2e * remove swc * attempt to fix e2e * Revert "attempt to fix e2e" This reverts commit9fb86a4076. * add logs for debug * attempt to fix e2e * Partially revert "attempt to fix e2e" This reverts commitfb0008c85a. * attempt to fix test * fix: attempt to fix test * Revert "fix: attempt to fix test" This reverts commited2f5c40e8. * Revert "attempt to fix test" This reverts commitd7329c46f1. * attempt to fix e2e * fix: surpass eventemitter warning by increasing defaultMaxListeners * attempt to fix e2e * fix * fix e2e not ending properly * exp: add hanging-process reporter for investigation * Revert "exp: add hanging-process reporter for investigation" This reverts commit26851f8282. * update changelog
173 lines
6.3 KiB
TypeScript
173 lines
6.3 KiB
TypeScript
/*
|
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
import { describe, test } from 'vitest';
|
|
import * as assert from 'assert';
|
|
import httpSignature from '@peertube/http-signature';
|
|
|
|
import { genRsaKeyPair } from '@/misc/gen-key-pair.js';
|
|
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
|
|
import { assertActivityMatchesUrl, FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
|
|
import { IObject } from '@/core/activitypub/type.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
|
|
signature: signature,
|
|
},
|
|
signingString: signingString,
|
|
algorithm: algorithm.toUpperCase(),
|
|
keyId: 'KeyID', // dummy, not used for verify
|
|
};
|
|
};
|
|
|
|
function cartesianProduct<T, U>(a: T[], b: U[]): [T, U][] {
|
|
return a.flatMap(a => b.map(b => [a, b] as [T, U]));
|
|
}
|
|
|
|
describe('ap-request', () => {
|
|
test('createSignedPost with verify', async () => {
|
|
const keypair = await genRsaKeyPair();
|
|
const key = { keyId: 'x', 'privateKeyPem': keypair.privateKey };
|
|
const url = 'https://example.com/inbox';
|
|
const activity = { a: 1 };
|
|
const body = JSON.stringify(activity);
|
|
const headers = {
|
|
'User-Agent': 'UA',
|
|
};
|
|
|
|
const req = ApRequestCreator.createSignedPost({ key, url, body, additionalHeaders: headers });
|
|
|
|
const parsed = buildParsedSignature(req.signingString, req.signature, 'rsa-sha256');
|
|
|
|
const result = httpSignature.verifySignature(parsed, keypair.publicKey);
|
|
assert.deepStrictEqual(result, true);
|
|
});
|
|
|
|
test('createSignedGet with verify', async () => {
|
|
const keypair = await genRsaKeyPair();
|
|
const key = { keyId: 'x', 'privateKeyPem': keypair.privateKey };
|
|
const url = 'https://example.com/outbox';
|
|
const headers = {
|
|
'User-Agent': 'UA',
|
|
};
|
|
|
|
const req = ApRequestCreator.createSignedGet({ key, url, additionalHeaders: headers });
|
|
|
|
const parsed = buildParsedSignature(req.signingString, req.signature, 'rsa-sha256');
|
|
|
|
const result = httpSignature.verifySignature(parsed, keypair.publicKey);
|
|
assert.deepStrictEqual(result, true);
|
|
});
|
|
|
|
test('rejects non matching domain', () => {
|
|
assert.doesNotThrow(() => assertActivityMatchesUrl(
|
|
'https://alice.example.com/abc',
|
|
{ id: 'https://alice.example.com/abc' } as IObject,
|
|
'https://alice.example.com/abc',
|
|
FetchAllowSoftFailMask.Strict,
|
|
), 'validation should pass base case');
|
|
assert.throws(() => assertActivityMatchesUrl(
|
|
'https://alice.example.com/abc',
|
|
{ id: 'https://bob.example.com/abc' } as IObject,
|
|
'https://alice.example.com/abc',
|
|
FetchAllowSoftFailMask.Any,
|
|
), 'validation should fail no matter what if the response URL is inconsistent with the object ID');
|
|
|
|
assert.doesNotThrow(() => assertActivityMatchesUrl(
|
|
'https://alice.example.com/abc#test',
|
|
{ id: 'https://alice.example.com/abc' } as IObject,
|
|
'https://alice.example.com/abc',
|
|
FetchAllowSoftFailMask.Strict,
|
|
), 'validation should pass with hash in request URL');
|
|
|
|
// fix issues like threads
|
|
// https://github.com/misskey-dev/misskey/issues/15039
|
|
const withOrWithoutWWW = [
|
|
'https://alice.example.com/abc',
|
|
'https://www.alice.example.com/abc',
|
|
];
|
|
|
|
cartesianProduct(
|
|
cartesianProduct(
|
|
withOrWithoutWWW,
|
|
withOrWithoutWWW,
|
|
),
|
|
withOrWithoutWWW,
|
|
).forEach(([[a, b], c]) => {
|
|
assert.doesNotThrow(() => assertActivityMatchesUrl(
|
|
a,
|
|
{ id: b } as IObject,
|
|
c,
|
|
FetchAllowSoftFailMask.Strict,
|
|
), 'validation should pass with or without www. subdomain');
|
|
});
|
|
});
|
|
|
|
test('cross origin lookup', () => {
|
|
assert.doesNotThrow(() => assertActivityMatchesUrl(
|
|
'https://alice.example.com/abc',
|
|
{ id: 'https://bob.example.com/abc' } as IObject,
|
|
'https://bob.example.com/abc',
|
|
FetchAllowSoftFailMask.CrossOrigin | FetchAllowSoftFailMask.NonCanonicalId,
|
|
), 'validation should pass if the response is otherwise consistent and cross-origin is allowed');
|
|
assert.throws(() => assertActivityMatchesUrl(
|
|
'https://alice.example.com/abc',
|
|
{ id: 'https://bob.example.com/abc' } as IObject,
|
|
'https://bob.example.com/abc',
|
|
FetchAllowSoftFailMask.Strict,
|
|
), 'validation should fail if the response is otherwise consistent and cross-origin is not allowed');
|
|
});
|
|
|
|
test('rejects non-canonical ID', () => {
|
|
assert.throws(() => assertActivityMatchesUrl(
|
|
'https://alice.example.com/@alice',
|
|
{ id: 'https://alice.example.com/users/alice' } as IObject,
|
|
'https://alice.example.com/users/alice',
|
|
FetchAllowSoftFailMask.Strict,
|
|
), 'throws if the response ID did not exactly match the expected ID');
|
|
assert.doesNotThrow(() => assertActivityMatchesUrl(
|
|
'https://alice.example.com/@alice',
|
|
{ id: 'https://alice.example.com/users/alice' } as IObject,
|
|
'https://alice.example.com/users/alice',
|
|
FetchAllowSoftFailMask.NonCanonicalId,
|
|
), 'does not throw if non-canonical ID is allowed');
|
|
});
|
|
|
|
test('origin relaxed alignment', () => {
|
|
assert.doesNotThrow(() => assertActivityMatchesUrl(
|
|
'https://alice.example.com/abc',
|
|
{ id: 'https://ap.alice.example.com/abc' } as IObject,
|
|
'https://ap.alice.example.com/abc',
|
|
FetchAllowSoftFailMask.MisalignedOrigin | FetchAllowSoftFailMask.NonCanonicalId,
|
|
), 'validation should pass if response is a subdomain of the expected origin');
|
|
assert.throws(() => assertActivityMatchesUrl(
|
|
'https://alice.multi-tenant.example.com/abc',
|
|
{ id: 'https://alice.multi-tenant.example.com/abc' } as IObject,
|
|
'https://bob.multi-tenant.example.com/abc',
|
|
FetchAllowSoftFailMask.MisalignedOrigin | FetchAllowSoftFailMask.NonCanonicalId,
|
|
), 'validation should fail if response is a disjoint domain of the expected origin');
|
|
assert.throws(() => assertActivityMatchesUrl(
|
|
'https://alice.example.com/abc',
|
|
{ id: 'https://ap.alice.example.com/abc' } as IObject,
|
|
'https://ap.alice.example.com/abc',
|
|
FetchAllowSoftFailMask.Strict,
|
|
), 'throws if relaxed origin is forbidden');
|
|
});
|
|
|
|
test('resist HTTP downgrade', () => {
|
|
assert.throws(() => assertActivityMatchesUrl(
|
|
'https://alice.example.com/abc',
|
|
{ id: 'https://alice.example.com/abc' } as IObject,
|
|
'http://alice.example.com/abc',
|
|
FetchAllowSoftFailMask.Strict,
|
|
), 'throws if HTTP downgrade is detected');
|
|
});
|
|
});
|