summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorかっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>2025-03-06 17:05:14 +0900
committerGitHub <noreply@github.com>2025-03-06 08:05:14 +0000
commit22228b6756ff534a94930774d9b6744dfb5961fe (patch)
tree4d555e637756c0e40310a078d20c2ba040fce043
parentfix(backend): S3互換オブジェクトストレージでファイルのア... (diff)
downloadmisskey-22228b6756ff534a94930774d9b6744dfb5961fe.tar.gz
misskey-22228b6756ff534a94930774d9b6744dfb5961fe.tar.bz2
misskey-22228b6756ff534a94930774d9b6744dfb5961fe.zip
enhance: OAuth2 (IndieAuth) でロゴが提供されている場合は表示するように (#15578)
* enhance: OAuthでロゴが提供されている場合は表示するように * Update Changelog * refactor * fix * fix test
-rw-r--r--CHANGELOG.md2
-rw-r--r--packages/backend/src/server/oauth/OAuth2ProviderService.ts17
-rw-r--r--packages/backend/src/server/web/views/oauth.pug2
-rw-r--r--packages/backend/test/e2e/oauth.ts56
-rw-r--r--packages/frontend/src/pages/oauth.vue2
5 files changed, 75 insertions, 4 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 21c589376b..197de5aec7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,8 @@
### General
- Enhance: プロキシアカウントをシステムアカウントとして作成するように
+- Enhance: OAuthで外部アプリからロゴが提供されている場合、それを表示できるように
+ 書式は https://indieauth.spec.indieweb.org/20220212/#example-2 に準じます。
- Fix: システムアカウントが削除できる問題を修正
### Client
diff --git a/packages/backend/src/server/oauth/OAuth2ProviderService.ts b/packages/backend/src/server/oauth/OAuth2ProviderService.ts
index e065c451f1..cdd7102666 100644
--- a/packages/backend/src/server/oauth/OAuth2ProviderService.ts
+++ b/packages/backend/src/server/oauth/OAuth2ProviderService.ts
@@ -95,6 +95,7 @@ interface ClientInformation {
id: string;
redirectUris: string[];
name: string;
+ logo: string | null;
}
// https://indieauth.spec.indieweb.org/#client-information-discovery
@@ -124,11 +125,19 @@ async function discoverClientInformation(logger: Logger, httpRequestService: Htt
redirectUris.push(...[...fragment.querySelectorAll<HTMLLinkElement>('link[rel=redirect_uri][href]')].map(el => el.href));
let name = id;
+ let logo: string | null = null;
if (text) {
const microformats = mf2(text, { baseUrl: res.url });
- const nameProperty = microformats.items.find(item => item.type?.includes('h-app') && item.properties.url.includes(id))?.properties.name[0];
- if (typeof nameProperty === 'string') {
- name = nameProperty;
+ const correspondingProperties = microformats.items.find(item => item.type?.includes('h-app') && item.properties.url.includes(id));
+ if (correspondingProperties) {
+ const nameProperty = correspondingProperties.properties.name?.[0];
+ if (typeof nameProperty === 'string') {
+ name = nameProperty;
+ }
+ const logoProperty = correspondingProperties.properties.logo?.[0];
+ if (typeof logoProperty === 'string') {
+ logo = logoProperty;
+ }
}
}
@@ -136,6 +145,7 @@ async function discoverClientInformation(logger: Logger, httpRequestService: Htt
id,
redirectUris: redirectUris.map(uri => new URL(uri, res.url).toString()),
name: typeof name === 'string' ? name : id,
+ logo,
};
} catch (err) {
console.error(err);
@@ -379,6 +389,7 @@ export class OAuth2ProviderService {
return await reply.view('oauth', {
transactionId: oauth2.transactionID,
clientName: oauth2.client.name,
+ clientLogo: oauth2.client.logo,
scope: oauth2.req.scope.join(' '),
});
});
diff --git a/packages/backend/src/server/web/views/oauth.pug b/packages/backend/src/server/web/views/oauth.pug
index 1470dbfbdf..4195ccc3a3 100644
--- a/packages/backend/src/server/web/views/oauth.pug
+++ b/packages/backend/src/server/web/views/oauth.pug
@@ -6,4 +6,6 @@ block meta
//- XXX: Remove navigation bar in auth page?
meta(name='misskey:oauth:transaction-id' content=transactionId)
meta(name='misskey:oauth:client-name' content=clientName)
+ if clientLogo
+ meta(name='misskey:oauth:client-logo' content=clientLogo)
meta(name='misskey:oauth:scope' content=scope)
diff --git a/packages/backend/test/e2e/oauth.ts b/packages/backend/test/e2e/oauth.ts
index ef7a6a579d..f639f90ea6 100644
--- a/packages/backend/test/e2e/oauth.ts
+++ b/packages/backend/test/e2e/oauth.ts
@@ -72,11 +72,12 @@ const clientConfig: ModuleOptions<'client_id'> = {
},
};
-function getMeta(html: string): { transactionId: string | undefined, clientName: string | undefined } {
+function getMeta(html: string): { transactionId: string | undefined, clientName: string | undefined, clientLogo: string | undefined } {
const fragment = JSDOM.fragment(html);
return {
transactionId: fragment.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:transaction-id"]')?.content,
clientName: fragment.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:client-name"]')?.content,
+ clientLogo: fragment.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:client-logo"]')?.content,
};
}
@@ -915,6 +916,59 @@ describe('OAuth', () => {
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();
+ };
+
+ 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`);
+ });
+
+ 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 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"');
diff --git a/packages/frontend/src/pages/oauth.vue b/packages/frontend/src/pages/oauth.vue
index 8719a769e5..860c884d13 100644
--- a/packages/frontend/src/pages/oauth.vue
+++ b/packages/frontend/src/pages/oauth.vue
@@ -11,6 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkAuthConfirm
ref="authRoot"
:name="name"
+ :icon="logo"
:permissions="permissions"
:waitOnDeny="true"
@accept="onAccept"
@@ -33,6 +34,7 @@ if (transactionIdMeta) {
}
const name = document.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:client-name"]')?.content;
+const logo = document.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:client-logo"]')?.content;
const permissions = document.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:scope"]')?.content.split(' ').filter((p): p is typeof Misskey.permissions[number] => (Misskey.permissions as readonly string[]).includes(p)) ?? [];
function doPost(token: string, decision: 'accept' | 'deny') {