use microformats-parser

This commit is contained in:
Kagami Sascha Rosylight 2023-06-29 12:07:52 +02:00
parent 4f35af5590
commit c1d2f72e61
4 changed files with 84 additions and 35 deletions

View File

@ -61,7 +61,7 @@
"@fastify/accepts": "4.2.0", "@fastify/accepts": "4.2.0",
"@fastify/cookie": "8.3.0", "@fastify/cookie": "8.3.0",
"@fastify/cors": "8.3.0", "@fastify/cors": "8.3.0",
"@fastify/express": "^2.3.0", "@fastify/express": "2.3.0",
"@fastify/http-proxy": "9.2.1", "@fastify/http-proxy": "9.2.1",
"@fastify/multipart": "7.7.0", "@fastify/multipart": "7.7.0",
"@fastify/static": "6.10.2", "@fastify/static": "6.10.2",
@ -79,7 +79,7 @@
"autwh": "0.1.0", "autwh": "0.1.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "2.0.5", "blurhash": "2.0.5",
"body-parser": "^1.20.2", "body-parser": "1.20.2",
"bullmq": "4.1.0", "bullmq": "4.1.0",
"cacheable-lookup": "7.0.0", "cacheable-lookup": "7.0.0",
"cbor": "9.0.0", "cbor": "9.0.0",
@ -100,7 +100,7 @@
"got": "13.0.0", "got": "13.0.0",
"happy-dom": "9.20.3", "happy-dom": "9.20.3",
"hpagent": "1.2.0", "hpagent": "1.2.0",
"http-link-header": "^1.1.0", "http-link-header": "1.1.0",
"ioredis": "5.3.2", "ioredis": "5.3.2",
"ip-cidr": "3.1.0", "ip-cidr": "3.1.0",
"ipaddr.js": "2.1.0", "ipaddr.js": "2.1.0",
@ -112,6 +112,7 @@
"jsrsasign": "10.8.6", "jsrsasign": "10.8.6",
"meilisearch": "0.33.0", "meilisearch": "0.33.0",
"mfm-js": "0.23.3", "mfm-js": "0.23.3",
"microformats-parser": "1.4.1",
"mime-types": "2.1.35", "mime-types": "2.1.35",
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
"ms": "3.0.0-canary.1", "ms": "3.0.0-canary.1",
@ -120,13 +121,13 @@
"nodemailer": "6.9.3", "nodemailer": "6.9.3",
"nsfwjs": "2.4.2", "nsfwjs": "2.4.2",
"oauth": "0.10.0", "oauth": "0.10.0",
"oauth2orize": "^1.11.1", "oauth2orize": "1.11.1",
"oauth2orize-pkce": "^0.1.2", "oauth2orize-pkce": "0.1.2",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"otpauth": "9.1.2", "otpauth": "9.1.2",
"parse5": "7.1.2", "parse5": "7.1.2",
"pg": "8.11.0", "pg": "8.11.0",
"pkce-challenge": "^4.0.1", "pkce-challenge": "4.0.1",
"probe-image-size": "7.2.3", "probe-image-size": "7.2.3",
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
"pug": "3.0.2", "pug": "3.0.2",
@ -172,25 +173,25 @@
"@types/accepts": "1.3.5", "@types/accepts": "1.3.5",
"@types/archiver": "5.3.2", "@types/archiver": "5.3.2",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/body-parser": "^1.19.2", "@types/body-parser": "1.19.2",
"@types/cbor": "6.0.0", "@types/cbor": "6.0.0",
"@types/color-convert": "2.0.0", "@types/color-convert": "2.0.0",
"@types/content-disposition": "0.5.5", "@types/content-disposition": "0.5.5",
"@types/escape-regexp": "0.0.1", "@types/escape-regexp": "0.0.1",
"@types/fluent-ffmpeg": "2.1.21", "@types/fluent-ffmpeg": "2.1.21",
"@types/http-link-header": "^1.0.3", "@types/http-link-header": "1.0.3",
"@types/jest": "29.5.2", "@types/jest": "29.5.2",
"@types/js-yaml": "4.0.5", "@types/js-yaml": "4.0.5",
"@types/jsdom": "21.1.1", "@types/jsdom": "21.1.1",
"@types/jsonld": "1.5.9", "@types/jsonld": "1.5.9",
"@types/jsrsasign": "10.5.8", "@types/jsrsasign": "10.5.8",
"@types/mime-types": "2.1.1", "@types/mime-types": "2.1.1",
"@types/ms": "^0.7.31", "@types/ms": "0.7.31",
"@types/node": "20.3.1", "@types/node": "20.3.1",
"@types/node-fetch": "3.0.3", "@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.8", "@types/nodemailer": "6.4.8",
"@types/oauth": "0.9.1", "@types/oauth": "0.9.1",
"@types/oauth2orize": "^1.11.0", "@types/oauth2orize": "1.11.0",
"@types/pg": "8.10.2", "@types/pg": "8.10.2",
"@types/pug": "2.0.6", "@types/pug": "2.0.6",
"@types/punycode": "2.1.0", "@types/punycode": "2.1.0",
@ -202,7 +203,7 @@
"@types/sanitize-html": "2.9.0", "@types/sanitize-html": "2.9.0",
"@types/semver": "7.5.0", "@types/semver": "7.5.0",
"@types/sharp": "0.32.0", "@types/sharp": "0.32.0",
"@types/simple-oauth2": "^5.0.4", "@types/simple-oauth2": "5.0.4",
"@types/sinonjs__fake-timers": "8.1.2", "@types/sinonjs__fake-timers": "8.1.2",
"@types/tinycolor2": "1.4.3", "@types/tinycolor2": "1.4.3",
"@types/tmp": "0.2.3", "@types/tmp": "0.2.3",
@ -221,6 +222,6 @@
"execa": "6.1.0", "execa": "6.1.0",
"jest": "29.5.0", "jest": "29.5.0",
"jest-mock": "29.5.0", "jest-mock": "29.5.0",
"simple-oauth2": "^5.0.0" "simple-oauth2": "5.0.0"
} }
} }

View File

@ -11,6 +11,7 @@ import pug from 'pug';
import bodyParser from 'body-parser'; import bodyParser from 'body-parser';
import fastifyExpress from '@fastify/express'; import fastifyExpress from '@fastify/express';
import { verifyChallenge } from 'pkce-challenge'; import { verifyChallenge } from 'pkce-challenge';
import { mf2 } from 'microformats-parser';
import { secureRndstr } from '@/misc/secure-rndstr.js'; import { secureRndstr } from '@/misc/secure-rndstr.js';
import { HttpRequestService } from '@/core/HttpRequestService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js';
import { kinds } from '@/misc/api-permissions.js'; import { kinds } from '@/misc/api-permissions.js';
@ -24,6 +25,7 @@ import type { LocalUser } from '@/models/entities/User.js';
import { MemoryKVCache } from '@/misc/cache.js'; import { MemoryKVCache } from '@/misc/cache.js';
import { LoggerService } from '@/core/LoggerService.js'; import { LoggerService } from '@/core/LoggerService.js';
import Logger from '@/logger.js'; import Logger from '@/logger.js';
import type { StatusError } from '@/misc/status-error.js';
import type { ServerResponse } from 'node:http'; import type { ServerResponse } from 'node:http';
import type { FastifyInstance } from 'fastify'; import type { FastifyInstance } from 'fastify';
@ -111,20 +113,33 @@ async function discoverClientInformation(logger: Logger, httpRequestService: Htt
redirectUris.push(...httpLinkHeader.parse(linkHeader).get('rel', 'redirect_uri').map(r => r.uri)); redirectUris.push(...httpLinkHeader.parse(linkHeader).get('rel', 'redirect_uri').map(r => r.uri));
} }
const fragment = JSDOM.fragment(await res.text()); const text = await res.text();
const fragment = JSDOM.fragment(text);
redirectUris.push(...[...fragment.querySelectorAll<HTMLLinkElement>('link[rel=redirect_uri][href]')].map(el => el.href)); redirectUris.push(...[...fragment.querySelectorAll<HTMLLinkElement>('link[rel=redirect_uri][href]')].map(el => el.href));
const name = fragment.querySelector<HTMLElement>('.h-app .p-name')?.textContent?.trim() ?? id; let name = id;
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;
}
}
return { return {
id, id,
redirectUris: redirectUris.map(uri => new URL(uri, res.url).toString()), redirectUris: redirectUris.map(uri => new URL(uri, res.url).toString()),
name, name: typeof name === 'string' ? name : id,
}; };
} catch (err) { } catch (err) {
logger.error('Failed to fetch client information', { err }); console.error(err);
throw new AuthorizationError('Failed to fetch client information', 'server_error'); logger.error('Error while fetching client information', { err });
if (err instanceof StatusError) {
throw new AuthorizationError('Failed to fetch client information', 'invalid_request');
} else {
throw new AuthorizationError('Failed to parse client information', 'server_error');
}
} }
} }

View File

@ -168,7 +168,7 @@ describe('OAuth', () => {
reply.send(` reply.send(`
<!DOCTYPE html> <!DOCTYPE html>
<link rel="redirect_uri" href="/redirect" /> <link rel="redirect_uri" href="/redirect" />
<div class="h-app"><div class="p-name">Misklient <div class="h-app"><a href="/" class="u-url p-name">Misklient
`); `);
}; };
}); });
@ -807,7 +807,7 @@ describe('OAuth', () => {
reply.header('Link', '</redirect>; rel="redirect_uri"'); reply.header('Link', '</redirect>; rel="redirect_uri"');
reply.send(` reply.send(`
<!DOCTYPE html> <!DOCTYPE html>
<div class="h-app"><div class="p-name">Misklient <div class="h-app"><a href="/" class="u-url p-name">Misklient
`); `);
}, },
'Mixed links': reply => { 'Mixed links': reply => {
@ -815,14 +815,14 @@ describe('OAuth', () => {
reply.send(` reply.send(`
<!DOCTYPE html> <!DOCTYPE html>
<link rel="redirect_uri" href="/redirect2" /> <link rel="redirect_uri" href="/redirect2" />
<div class="h-app"><div class="p-name">Misklient <div class="h-app"><a href="/" class="u-url p-name">Misklient
`); `);
}, },
'Multiple items in Link header': reply => { 'Multiple items in Link header': reply => {
reply.header('Link', '</redirect2>; rel="redirect_uri",</redirect>; rel="redirect_uri"'); reply.header('Link', '</redirect2>; rel="redirect_uri",</redirect>; rel="redirect_uri"');
reply.send(` reply.send(`
<!DOCTYPE html> <!DOCTYPE html>
<div class="h-app"><div class="p-name">Misklient <div class="h-app"><a href="/" class="u-url p-name">Misklient
`); `);
}, },
'Multiple items in HTML': reply => { 'Multiple items in HTML': reply => {
@ -830,7 +830,7 @@ describe('OAuth', () => {
<!DOCTYPE html> <!DOCTYPE html>
<link rel="redirect_uri" href="/redirect2" /> <link rel="redirect_uri" href="/redirect2" />
<link rel="redirect_uri" href="/redirect" /> <link rel="redirect_uri" href="/redirect" />
<div class="h-app"><div class="p-name">Misklient <div class="h-app"><a href="/" class="u-url p-name">Misklient
`); `);
}, },
}; };
@ -856,7 +856,7 @@ describe('OAuth', () => {
sender = (reply): void => { sender = (reply): void => {
reply.send(` reply.send(`
<!DOCTYPE html> <!DOCTYPE html>
<div class="h-app"><div class="p-name">Misklient <div class="h-app"><a href="/" class="u-url p-name">Misklient
`); `);
}; };
@ -907,6 +907,29 @@ describe('OAuth', () => {
assert.strictEqual(response.status, 200); assert.strictEqual(response.status, 200);
assert.strictEqual(getMeta(await response.text()).clientName, `http://127.0.0.1:${clientPort}/`); assert.strictEqual(getMeta(await response.text()).clientName, `http://127.0.0.1:${clientPort}/`);
}); });
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 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('Unknown OAuth endpoint', async () => { test('Unknown OAuth endpoint', async () => {

View File

@ -99,7 +99,7 @@ importers:
specifier: 8.3.0 specifier: 8.3.0
version: 8.3.0 version: 8.3.0
'@fastify/express': '@fastify/express':
specifier: ^2.3.0 specifier: 2.3.0
version: 2.3.0 version: 2.3.0
'@fastify/http-proxy': '@fastify/http-proxy':
specifier: 9.2.1 specifier: 9.2.1
@ -153,7 +153,7 @@ importers:
specifier: 2.0.5 specifier: 2.0.5
version: 2.0.5 version: 2.0.5
body-parser: body-parser:
specifier: ^1.20.2 specifier: 1.20.2
version: 1.20.2 version: 1.20.2
bullmq: bullmq:
specifier: 4.1.0 specifier: 4.1.0
@ -216,7 +216,7 @@ importers:
specifier: 1.2.0 specifier: 1.2.0
version: 1.2.0 version: 1.2.0
http-link-header: http-link-header:
specifier: ^1.1.0 specifier: 1.1.0
version: 1.1.0 version: 1.1.0
ioredis: ioredis:
specifier: 5.3.2 specifier: 5.3.2
@ -251,6 +251,9 @@ importers:
mfm-js: mfm-js:
specifier: 0.23.3 specifier: 0.23.3
version: 0.23.3 version: 0.23.3
microformats-parser:
specifier: 1.4.1
version: 1.4.1
mime-types: mime-types:
specifier: 2.1.35 specifier: 2.1.35
version: 2.1.35 version: 2.1.35
@ -276,10 +279,10 @@ importers:
specifier: 0.10.0 specifier: 0.10.0
version: 0.10.0 version: 0.10.0
oauth2orize: oauth2orize:
specifier: ^1.11.1 specifier: 1.11.1
version: 1.11.1 version: 1.11.1
oauth2orize-pkce: oauth2orize-pkce:
specifier: ^0.1.2 specifier: 0.1.2
version: 0.1.2 version: 0.1.2
os-utils: os-utils:
specifier: 0.0.14 specifier: 0.0.14
@ -294,7 +297,7 @@ importers:
specifier: 8.11.0 specifier: 8.11.0
version: 8.11.0 version: 8.11.0
pkce-challenge: pkce-challenge:
specifier: ^4.0.1 specifier: 4.0.1
version: 4.0.1 version: 4.0.1
probe-image-size: probe-image-size:
specifier: 7.2.3 specifier: 7.2.3
@ -509,7 +512,7 @@ importers:
specifier: 2.4.2 specifier: 2.4.2
version: 2.4.2 version: 2.4.2
'@types/body-parser': '@types/body-parser':
specifier: ^1.19.2 specifier: 1.19.2
version: 1.19.2 version: 1.19.2
'@types/cbor': '@types/cbor':
specifier: 6.0.0 specifier: 6.0.0
@ -527,7 +530,7 @@ importers:
specifier: 2.1.21 specifier: 2.1.21
version: 2.1.21 version: 2.1.21
'@types/http-link-header': '@types/http-link-header':
specifier: ^1.0.3 specifier: 1.0.3
version: 1.0.3 version: 1.0.3
'@types/jest': '@types/jest':
specifier: 29.5.2 specifier: 29.5.2
@ -548,7 +551,7 @@ importers:
specifier: 2.1.1 specifier: 2.1.1
version: 2.1.1 version: 2.1.1
'@types/ms': '@types/ms':
specifier: ^0.7.31 specifier: 0.7.31
version: 0.7.31 version: 0.7.31
'@types/node': '@types/node':
specifier: 20.3.1 specifier: 20.3.1
@ -563,7 +566,7 @@ importers:
specifier: 0.9.1 specifier: 0.9.1
version: 0.9.1 version: 0.9.1
'@types/oauth2orize': '@types/oauth2orize':
specifier: ^1.11.0 specifier: 1.11.0
version: 1.11.0 version: 1.11.0
'@types/pg': '@types/pg':
specifier: 8.10.2 specifier: 8.10.2
@ -599,7 +602,7 @@ importers:
specifier: 0.32.0 specifier: 0.32.0
version: 0.32.0 version: 0.32.0
'@types/simple-oauth2': '@types/simple-oauth2':
specifier: ^5.0.4 specifier: 5.0.4
version: 5.0.4 version: 5.0.4
'@types/sinonjs__fake-timers': '@types/sinonjs__fake-timers':
specifier: 8.1.2 specifier: 8.1.2
@ -656,7 +659,7 @@ importers:
specifier: 29.5.0 specifier: 29.5.0
version: 29.5.0 version: 29.5.0
simple-oauth2: simple-oauth2:
specifier: ^5.0.0 specifier: 5.0.0
version: 5.0.0 version: 5.0.0
packages/frontend: packages/frontend:
@ -15528,6 +15531,13 @@ packages:
twemoji-parser: 14.0.0 twemoji-parser: 14.0.0
dev: false dev: false
/microformats-parser@1.4.1:
resolution: {integrity: sha512-BSg9Y/Aik8hvvme/fkxnXMRvTKuVwOeTapeZdaPQ+92DEubyM31iMtwbgFZ1383om643UvfYY5G23E9s1FY2KQ==}
engines: {node: '>=10'}
dependencies:
parse5: 6.0.1
dev: false
/micromatch@3.1.10: /micromatch@3.1.10:
resolution: {integrity: sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==} resolution: {integrity: sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}