summaryrefslogtreecommitdiff
path: root/packages/backend/test/e2e
diff options
context:
space:
mode:
authorかっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>2025-12-31 14:50:01 +0900
committerGitHub <noreply@github.com>2025-12-31 14:50:01 +0900
commit01aa56c60201aed3e278193a0aee7c13b1f0aef7 (patch)
tree85b75feb469be79f743a36efb47076156d2dac50 /packages/backend/test/e2e
parentrefactor(frontend): remove undefined css rules (#17051) (diff)
downloadmisskey-01aa56c60201aed3e278193a0aee7c13b1f0aef7.tar.gz
misskey-01aa56c60201aed3e278193a0aee7c13b1f0aef7.tar.bz2
misskey-01aa56c60201aed3e278193a0aee7c13b1f0aef7.zip
enhance(backend/oauth): Support client information discovery in the IndieAuth 11 July 2024 spec (#17030)
* enhance(backend): Support client information discovery in the IndieAuth 11 July 2024 spec * add tests * Update Changelog * Update Changelog * fix tests * fix test describe to align with the other describe format
Diffstat (limited to 'packages/backend/test/e2e')
-rw-r--r--packages/backend/test/e2e/oauth.ts422
1 files changed, 276 insertions, 146 deletions
diff --git a/packages/backend/test/e2e/oauth.ts b/packages/backend/test/e2e/oauth.ts
index 96a6311a5a..67a9026eb5 100644
--- a/packages/backend/test/e2e/oauth.ts
+++ b/packages/backend/test/e2e/oauth.ts
@@ -28,6 +28,7 @@ const host = `http://127.0.0.1:${port}`;
const clientPort = port + 1;
const redirect_uri = `http://127.0.0.1:${clientPort}/redirect`;
+const redirect_uri2 = `http://127.0.0.1:${clientPort}/redirect2`;
const basicAuthParams: AuthorizationParamsExtended = {
redirect_uri,
@@ -807,45 +808,193 @@ describe('OAuth', () => {
});
});
- // https://indieauth.spec.indieweb.org/#client-information-discovery
describe('Client Information Discovery', () => {
- describe('Redirection', () => {
- const tests: Record<string, (reply: FastifyReply) => void> = {
- 'Read HTTP header': reply => {
- reply.header('Link', '</redirect>; rel="redirect_uri"');
- reply.send(`
- <!DOCTYPE html>
- <div class="h-app"><a href="/" class="u-url p-name">Misklient
- `);
- },
- 'Mixed links': reply => {
- reply.header('Link', '</redirect>; rel="redirect_uri"');
- reply.send(`
- <!DOCTYPE html>
- <link rel="redirect_uri" href="/redirect2" />
- <div class="h-app"><a href="/" class="u-url p-name">Misklient
- `);
- },
- 'Multiple items in Link header': reply => {
- reply.header('Link', '</redirect2>; rel="redirect_uri",</redirect>; rel="redirect_uri"');
- reply.send(`
- <!DOCTYPE html>
- <div class="h-app"><a href="/" class="u-url p-name">Misklient
- `);
- },
- 'Multiple items in HTML': reply => {
- reply.send(`
- <!DOCTYPE html>
- <link rel="redirect_uri" href="/redirect2" />
- <link rel="redirect_uri" href="/redirect" />
- <div class="h-app"><a href="/" class="u-url p-name">Misklient
- `);
- },
- };
+ // https://indieauth.spec.indieweb.org/#client-information-discovery
+ describe('JSON client metadata (11 July 2024)', () => {
+ test('Read JSON document', async () => {
+ sender = (reply): void => {
+ reply.header('content-type', 'application/json');
+ reply.send({
+ client_id: `http://127.0.0.1:${clientPort}/`,
+ client_uri: `http://127.0.0.1:${clientPort}/`,
+ client_name: 'Misklient JSON',
+ logo_uri: '/logo.png',
+ redirect_uris: ['/redirect'],
+ });
+ };
- for (const [title, replyFunc] of Object.entries(tests)) {
- test(title, async () => {
- sender = replyFunc;
+ const client = new AuthorizationCode(clientConfig);
+
+ const response = await fetch(client.authorizeURL({
+ redirect_uri,
+ scope: 'write:notes',
+ state: 'state',
+ code_challenge: 'code',
+ code_challenge_method: 'S256',
+ } as AuthorizationParamsExtended));
+ assert.strictEqual(response.status, 200);
+ const meta = getMeta(await response.text());
+ assert.strictEqual(meta.clientName, 'Misklient JSON');
+ assert.strictEqual(meta.clientLogo, `http://127.0.0.1:${clientPort}/logo.png`);
+ });
+
+ test('Merge Link header redirect_uri with JSON redirect_uris', async () => {
+ sender = (reply): void => {
+ reply.header('Link', '</redirect2>; rel="redirect_uri"');
+ reply.header('content-type', 'application/json');
+ reply.send({
+ client_id: `http://127.0.0.1:${clientPort}/`,
+ client_uri: `http://127.0.0.1:${clientPort}/`,
+ client_name: 'Misklient JSON',
+ redirect_uris: ['/redirect'],
+ });
+ };
+
+ const client = new AuthorizationCode(clientConfig);
+
+ const ok1 = await fetch(client.authorizeURL({
+ redirect_uri,
+ scope: 'write:notes',
+ state: 'state',
+ code_challenge: 'code',
+ code_challenge_method: 'S256',
+ } as AuthorizationParamsExtended));
+ assert.strictEqual(ok1.status, 200);
+
+ const ok2 = await fetch(client.authorizeURL({
+ redirect_uri: redirect_uri2,
+ scope: 'write:notes',
+ state: 'state',
+ code_challenge: 'code',
+ code_challenge_method: 'S256',
+ } as AuthorizationParamsExtended));
+ assert.strictEqual(ok2.status, 200);
+ });
+
+ test('Reject when client_id does not match retrieved URL', async () => {
+ sender = (reply): void => {
+ reply.header('content-type', 'application/json');
+ reply.send({
+ client_id: `http://127.0.0.1:${clientPort}/mismatch`,
+ client_uri: `http://127.0.0.1:${clientPort}/`,
+ redirect_uris: ['/redirect'],
+ });
+ };
+
+ const client = new AuthorizationCode(clientConfig);
+ const response = await fetch(client.authorizeURL({
+ redirect_uri,
+ scope: 'write:notes',
+ state: 'state',
+ code_challenge: 'code',
+ code_challenge_method: 'S256',
+ } as AuthorizationParamsExtended));
+ await assertDirectError(response, 400, 'invalid_request');
+ });
+
+ test('Reject when client_uri is not a prefix of client_id', async () => {
+ sender = (reply): void => {
+ reply.header('content-type', 'application/json');
+ reply.send({
+ client_id: `http://127.0.0.1:${clientPort}/`,
+ client_uri: `http://127.0.0.1:${clientPort}/no-prefix/`,
+ redirect_uris: ['/redirect'],
+ });
+ };
+
+ const client = new AuthorizationCode(clientConfig);
+ const response = await fetch(client.authorizeURL({
+ redirect_uri,
+ scope: 'write:notes',
+ state: 'state',
+ code_challenge: 'code',
+ code_challenge_method: 'S256',
+ } as AuthorizationParamsExtended));
+ await assertDirectError(response, 400, 'invalid_request');
+ });
+
+ test('Reject when JSON metadata has no redirect_uris and no Link header', async () => {
+ sender = (reply): void => {
+ reply.header('content-type', 'application/json');
+ reply.send({
+ client_id: `http://127.0.0.1:${clientPort}/`,
+ client_uri: `http://127.0.0.1:${clientPort}/`,
+ client_name: 'Misklient JSON',
+ });
+ };
+
+ const client = new AuthorizationCode(clientConfig);
+ const response = await fetch(client.authorizeURL({
+ redirect_uri,
+ scope: 'write:notes',
+ state: 'state',
+ code_challenge: 'code',
+ code_challenge_method: 'S256',
+ } as AuthorizationParamsExtended));
+ await assertDirectError(response, 400, 'invalid_request');
+ });
+ });
+
+ // https://indieauth.spec.indieweb.org/20220212/#client-information-discovery
+ describe('HTML link client metadata (12 Feb 2022)', () => {
+ describe('Redirection', () => {
+ const tests: Record<string, (reply: FastifyReply) => void> = {
+ 'Read HTTP header': reply => {
+ reply.header('Link', '</redirect>; rel="redirect_uri"');
+ reply.send(`
+ <!DOCTYPE html>
+ <div class="h-app"><a href="/" class="u-url p-name">Misklient
+ `);
+ },
+ 'Mixed links': reply => {
+ reply.header('Link', '</redirect>; rel="redirect_uri"');
+ reply.send(`
+ <!DOCTYPE html>
+ <link rel="redirect_uri" href="/redirect2" />
+ <div class="h-app"><a href="/" class="u-url p-name">Misklient
+ `);
+ },
+ 'Multiple items in Link header': reply => {
+ reply.header('Link', '</redirect2>; rel="redirect_uri",</redirect>; rel="redirect_uri"');
+ reply.send(`
+ <!DOCTYPE html>
+ <div class="h-app"><a href="/" class="u-url p-name">Misklient
+ `);
+ },
+ 'Multiple items in HTML': reply => {
+ reply.send(`
+ <!DOCTYPE html>
+ <link rel="redirect_uri" href="/redirect2" />
+ <link rel="redirect_uri" href="/redirect" />
+ <div class="h-app"><a href="/" class="u-url p-name">Misklient
+ `);
+ },
+ };
+
+ for (const [title, replyFunc] of Object.entries(tests)) {
+ test(title, async () => {
+ sender = replyFunc;
+
+ const client = new AuthorizationCode(clientConfig);
+
+ const response = await fetch(client.authorizeURL({
+ redirect_uri,
+ scope: 'write:notes',
+ state: 'state',
+ code_challenge: 'code',
+ code_challenge_method: 'S256',
+ } as AuthorizationParamsExtended));
+ assert.strictEqual(response.status, 200);
+ });
+ }
+
+ test('No item', async () => {
+ sender = (reply): void => {
+ reply.send(`
+ <!DOCTYPE html>
+ <div class="h-app"><a href="/" class="u-url p-name">Misklient
+ `);
+ };
const client = new AuthorizationCode(clientConfig);
@@ -856,20 +1005,17 @@ describe('OAuth', () => {
code_challenge: 'code',
code_challenge_method: 'S256',
} as AuthorizationParamsExtended));
- assert.strictEqual(response.status, 200);
+
+ // direct error because there's no redirect URI to ping
+ await assertDirectError(response, 400, 'invalid_request');
});
- }
+ });
- test('No item', async () => {
- sender = (reply): void => {
- reply.send(`
- <!DOCTYPE html>
- <div class="h-app"><a href="/" class="u-url p-name">Misklient
- `);
- };
- const client = new AuthorizationCode(clientConfig);
+ test('Disallow loopback', async () => {
+ await sendEnvUpdateRequest({ key: 'MISSKEY_TEST_CHECK_IP_RANGE', value: '1' });
+ const client = new AuthorizationCode(clientConfig);
const response = await fetch(client.authorizeURL({
redirect_uri,
scope: 'write:notes',
@@ -877,119 +1023,103 @@ describe('OAuth', () => {
code_challenge: 'code',
code_challenge_method: 'S256',
} as AuthorizationParamsExtended));
-
- // direct error because there's no redirect URI to ping
await assertDirectError(response, 400, 'invalid_request');
});
- });
- test('Disallow loopback', async () => {
- await sendEnvUpdateRequest({ key: 'MISSKEY_TEST_CHECK_IP_RANGE', value: '1' });
-
- const client = new AuthorizationCode(clientConfig);
- const response = await fetch(client.authorizeURL({
- redirect_uri,
- scope: 'write:notes',
- state: 'state',
- code_challenge: 'code',
- code_challenge_method: 'S256',
- } as AuthorizationParamsExtended));
- await assertDirectError(response, 400, 'invalid_request');
- });
-
- test('Missing name', async () => {
- sender = (reply): void => {
- reply.header('Link', '</redirect>; rel="redirect_uri"');
- reply.send();
- };
+ test('Missing name', async () => {
+ sender = (reply): void => {
+ reply.header('Link', '</redirect>; rel="redirect_uri"');
+ reply.send();
+ };
- const client = new AuthorizationCode(clientConfig);
+ const client = new AuthorizationCode(clientConfig);
- const response = await fetch(client.authorizeURL({
- redirect_uri,
- scope: 'write:notes',
- state: 'state',
- code_challenge: 'code',
- code_challenge_method: 'S256',
- } as AuthorizationParamsExtended));
- assert.strictEqual(response.status, 200);
- assert.strictEqual(getMeta(await response.text()).clientName, `http://127.0.0.1:${clientPort}/`);
- });
+ const response = await fetch(client.authorizeURL({
+ redirect_uri,
+ scope: 'write:notes',
+ state: 'state',
+ code_challenge: 'code',
+ code_challenge_method: 'S256',
+ } as AuthorizationParamsExtended));
+ assert.strictEqual(response.status, 200);
+ assert.strictEqual(getMeta(await response.text()).clientName, `http://127.0.0.1:${clientPort}/`);
+ });
- test('With Logo', async () => {
- sender = (reply): void => {
- reply.header('Link', '</redirect>; rel="redirect_uri"');
- reply.send(`
- <!DOCTYPE html>
- <div class="h-app">
- <a href="/" class="u-url p-name">Misklient</a>
- <img src="/logo.png" class="u-logo" />
- </div>
- `);
- reply.send();
- };
+ test('With Logo', async () => {
+ sender = (reply): void => {
+ reply.header('Link', '</redirect>; rel="redirect_uri"');
+ reply.send(`
+ <!DOCTYPE html>
+ <div class="h-app">
+ <a href="/" class="u-url p-name">Misklient</a>
+ <img src="/logo.png" class="u-logo" />
+ </div>
+ `);
+ reply.send();
+ };
- const client = new AuthorizationCode(clientConfig);
+ const client = new AuthorizationCode(clientConfig);
- const response = await fetch(client.authorizeURL({
- redirect_uri,
- scope: 'write:notes',
- state: 'state',
- code_challenge: 'code',
- code_challenge_method: 'S256',
- } as AuthorizationParamsExtended));
- assert.strictEqual(response.status, 200);
- const meta = getMeta(await response.text());
- assert.strictEqual(meta.clientName, 'Misklient');
- assert.strictEqual(meta.clientLogo, `http://127.0.0.1:${clientPort}/logo.png`);
- });
+ const response = await fetch(client.authorizeURL({
+ redirect_uri,
+ scope: 'write:notes',
+ state: 'state',
+ code_challenge: 'code',
+ code_challenge_method: 'S256',
+ } as AuthorizationParamsExtended));
+ assert.strictEqual(response.status, 200);
+ const meta = getMeta(await response.text());
+ assert.strictEqual(meta.clientName, 'Misklient');
+ assert.strictEqual(meta.clientLogo, `http://127.0.0.1:${clientPort}/logo.png`);
+ });
- test('Missing Logo', async () => {
- sender = (reply): void => {
- reply.header('Link', '</redirect>; rel="redirect_uri"');
- reply.send(`
- <!DOCTYPE html>
- <div class="h-app"><a href="/" class="u-url p-name">Misklient
- `);
- reply.send();
- };
+ test('Missing Logo', async () => {
+ sender = (reply): void => {
+ reply.header('Link', '</redirect>; rel="redirect_uri"');
+ reply.send(`
+ <!DOCTYPE html>
+ <div class="h-app"><a href="/" class="u-url p-name">Misklient
+ `);
+ reply.send();
+ };
- const client = new AuthorizationCode(clientConfig);
+ const client = new AuthorizationCode(clientConfig);
- const response = await fetch(client.authorizeURL({
- redirect_uri,
- scope: 'write:notes',
- state: 'state',
- code_challenge: 'code',
- code_challenge_method: 'S256',
- } as AuthorizationParamsExtended));
- assert.strictEqual(response.status, 200);
- const meta = getMeta(await response.text());
- assert.strictEqual(meta.clientName, 'Misklient');
- assert.strictEqual(meta.clientLogo, undefined);
- });
+ const response = await fetch(client.authorizeURL({
+ redirect_uri,
+ scope: 'write:notes',
+ state: 'state',
+ code_challenge: 'code',
+ code_challenge_method: 'S256',
+ } as AuthorizationParamsExtended));
+ assert.strictEqual(response.status, 200);
+ const meta = getMeta(await response.text());
+ assert.strictEqual(meta.clientName, 'Misklient');
+ assert.strictEqual(meta.clientLogo, undefined);
+ });
- test('Mismatching URL in h-app', async () => {
- sender = (reply): void => {
- reply.header('Link', '</redirect>; rel="redirect_uri"');
- reply.send(`
- <!DOCTYPE html>
- <div class="h-app"><a href="/foo" class="u-url p-name">Misklient
- `);
- reply.send();
- };
+ test('Mismatching URL in h-app', async () => {
+ sender = (reply): void => {
+ reply.header('Link', '</redirect>; rel="redirect_uri"');
+ reply.send(`
+ <!DOCTYPE html>
+ <div class="h-app"><a href="/foo" class="u-url p-name">Misklient
+ `);
+ reply.send();
+ };
- const client = new AuthorizationCode(clientConfig);
+ const client = new AuthorizationCode(clientConfig);
- const response = await fetch(client.authorizeURL({
- redirect_uri,
- scope: 'write:notes',
- state: 'state',
- code_challenge: 'code',
- code_challenge_method: 'S256',
- } as AuthorizationParamsExtended));
- assert.strictEqual(response.status, 200);
- assert.strictEqual(getMeta(await response.text()).clientName, `http://127.0.0.1:${clientPort}/`);
+ const response = await fetch(client.authorizeURL({
+ redirect_uri,
+ scope: 'write:notes',
+ state: 'state',
+ code_challenge: 'code',
+ code_challenge_method: 'S256',
+ } as AuthorizationParamsExtended));
+ assert.strictEqual(response.status, 200);
+ assert.strictEqual(getMeta(await response.text()).clientName, `http://127.0.0.1:${clientPort}/`);
+ });
});
});