/.well-known 周りをいい感じに (#4141)
* Enhance /.well-known and their friends * Fix bug
This commit is contained in:
parent
2f4434b0d8
commit
9dd06a7621
9 changed files with 202 additions and 89 deletions
|
@ -4,6 +4,10 @@
|
|||
"version": "10.81.0",
|
||||
"clientVersion": "2.0.14026",
|
||||
"codename": "nighthike",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/syuilo/misskey.git"
|
||||
},
|
||||
"main": "./index.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
|
9
src/@types/package.json.d.ts
vendored
9
src/@types/package.json.d.ts
vendored
|
@ -1,3 +1,10 @@
|
|||
declare module '*/package.json' {
|
||||
const version: string;
|
||||
interface IRepository {
|
||||
type: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export const name: string;
|
||||
export const version: string;
|
||||
export const repository: IRepository;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export default (acct: string) => {
|
||||
import Acct from './type';
|
||||
|
||||
export default (acct: string): Acct => {
|
||||
if (acct.startsWith('@')) acct = acct.substr(1);
|
||||
const split = acct.split('@', 2);
|
||||
return { username: split[0], host: split[1] || null };
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
type UserLike = {
|
||||
host: string;
|
||||
username: string;
|
||||
};
|
||||
import Acct from './type';
|
||||
|
||||
export default (user: UserLike) => {
|
||||
export default (user: Acct) => {
|
||||
return user.host === null ? user.username : `${user.username}@${user.host}`;
|
||||
};
|
||||
|
|
6
src/misc/acct/type.ts
Normal file
6
src/misc/acct/type.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
type Acct = {
|
||||
username: string;
|
||||
host: string;
|
||||
};
|
||||
|
||||
export default Acct;
|
|
@ -16,7 +16,8 @@ import * as requestStats from 'request-stats';
|
|||
import * as slow from 'koa-slow';
|
||||
|
||||
import activityPub from './activitypub';
|
||||
import webFinger from './webfinger';
|
||||
import nodeinfo from './nodeinfo';
|
||||
import wellKnown from './well-known';
|
||||
import config from '../config';
|
||||
import networkChart from '../chart/network';
|
||||
import apiServer from './api';
|
||||
|
@ -68,7 +69,8 @@ const router = new Router();
|
|||
|
||||
// Routing
|
||||
router.use(activityPub.routes());
|
||||
router.use(webFinger.routes());
|
||||
router.use(nodeinfo.routes());
|
||||
router.use(wellKnown.routes());
|
||||
|
||||
router.get('/verify-email/:code', async ctx => {
|
||||
const user = await User.findOne({ emailVerifyCode: ctx.params.code });
|
||||
|
@ -88,11 +90,6 @@ router.get('/verify-email/:code', async ctx => {
|
|||
}
|
||||
});
|
||||
|
||||
// Return 404 for other .well-known
|
||||
router.all('/.well-known/*', async ctx => {
|
||||
ctx.status = 404;
|
||||
});
|
||||
|
||||
// Register router
|
||||
app.use(router.routes());
|
||||
|
||||
|
|
73
src/server/nodeinfo.ts
Normal file
73
src/server/nodeinfo.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
import * as Router from 'koa-router';
|
||||
import config from '../config';
|
||||
import fetchMeta from '../misc/fetch-meta';
|
||||
import User from '../models/user';
|
||||
import { name as softwareName, version, repository } from '../../package.json';
|
||||
import Note from '../models/note';
|
||||
|
||||
const router = new Router();
|
||||
|
||||
const nodeinfo2_1path = '/nodeinfo/2.1';
|
||||
const nodeinfo2_0path = '/nodeinfo/2.0';
|
||||
|
||||
export const links = [/* (awaiting release) {
|
||||
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1',
|
||||
href: config.url + nodeinfo2_1path
|
||||
}, */{
|
||||
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
|
||||
href: config.url + nodeinfo2_0path
|
||||
}];
|
||||
|
||||
const nodeinfo2 = async () => {
|
||||
const [
|
||||
{ name, description, maintainer, langs, broadcasts, disableRegistration, disableLocalTimeline, disableGlobalTimeline, enableRecaptcha, maxNoteTextLength, enableTwitterIntegration, enableGithubIntegration, enableDiscordIntegration, enableEmail, enableServiceWorker },
|
||||
total,
|
||||
activeHalfyear,
|
||||
activeMonth,
|
||||
localPosts,
|
||||
localComments
|
||||
] = await Promise.all([
|
||||
fetchMeta(),
|
||||
User.count({ host: null }),
|
||||
User.count({ host: null, updatedAt: { $gt: new Date(Date.now() - 15552000000) } }),
|
||||
User.count({ host: null, updatedAt: { $gt: new Date(Date.now() - 2592000000) } }),
|
||||
Note.count({ '_user.host': null, replyId: null }),
|
||||
Note.count({ '_user.host': null, replyId: { $ne: null } })
|
||||
]);
|
||||
|
||||
return {
|
||||
software: {
|
||||
name: softwareName,
|
||||
version,
|
||||
repository: repository.url
|
||||
},
|
||||
protocols: ['activitypub'],
|
||||
services: {
|
||||
inbound: [] as string[],
|
||||
outbound: ['atom1.0', 'rss2.0']
|
||||
},
|
||||
openRegistrations: !disableRegistration,
|
||||
usage: {
|
||||
users: { total, activeHalfyear, activeMonth },
|
||||
localPosts,
|
||||
localComments
|
||||
},
|
||||
metadata: { name, description, maintainer, langs, broadcasts, disableRegistration, disableLocalTimeline, disableGlobalTimeline, enableRecaptcha, maxNoteTextLength, enableTwitterIntegration, enableGithubIntegration, enableDiscordIntegration, enableEmail, enableServiceWorker }
|
||||
};
|
||||
};
|
||||
|
||||
router.get(nodeinfo2_1path, async ctx => {
|
||||
const base = await nodeinfo2();
|
||||
|
||||
ctx.body = { version: '2.1', ...base };
|
||||
});
|
||||
|
||||
router.get(nodeinfo2_0path, async ctx => {
|
||||
const base = await nodeinfo2();
|
||||
|
||||
delete base.software.repository;
|
||||
|
||||
ctx.body = { version: '2.0', ...base };
|
||||
});
|
||||
|
||||
export default router;
|
|
@ -1,75 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import * as Router from 'koa-router';
|
||||
|
||||
import config from '../config';
|
||||
import parseAcct from '../misc/acct/parse';
|
||||
import User, { IUser } from '../models/user';
|
||||
|
||||
// Init router
|
||||
const router = new Router();
|
||||
|
||||
router.get('/.well-known/webfinger', async ctx => {
|
||||
if (typeof ctx.query.resource !== 'string') {
|
||||
ctx.status = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
const resourceLower = ctx.query.resource.toLowerCase();
|
||||
let acctLower;
|
||||
let id;
|
||||
|
||||
if (resourceLower.startsWith(config.url.toLowerCase() + '/@')) {
|
||||
acctLower = resourceLower.split('/').pop();
|
||||
} else if (resourceLower.startsWith(config.url.toLowerCase() + '/users/')) {
|
||||
id = new mongo.ObjectID(resourceLower.split('/').pop());
|
||||
} else if (resourceLower.startsWith('acct:')) {
|
||||
acctLower = resourceLower.slice('acct:'.length);
|
||||
} else {
|
||||
acctLower = resourceLower;
|
||||
}
|
||||
|
||||
let user: IUser;
|
||||
|
||||
if (acctLower) {
|
||||
const parsedAcctLower = parseAcct(acctLower);
|
||||
if (![null, config.host.toLowerCase()].includes(parsedAcctLower.host)) {
|
||||
ctx.status = 422;
|
||||
return;
|
||||
}
|
||||
|
||||
user = await User.findOne({
|
||||
usernameLower: parsedAcctLower.username,
|
||||
host: null
|
||||
});
|
||||
} else {
|
||||
user = await User.findOne({
|
||||
_id: id,
|
||||
host: null
|
||||
});
|
||||
}
|
||||
|
||||
if (user === null) {
|
||||
ctx.status = 404;
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.body = {
|
||||
subject: `acct:${user.username}@${config.host}`,
|
||||
links: [{
|
||||
rel: 'self',
|
||||
type: 'application/activity+json',
|
||||
href: `${config.url}/users/${user._id}`
|
||||
}, {
|
||||
rel: 'http://webfinger.net/rel/profile-page',
|
||||
type: 'text/html',
|
||||
href: `${config.url}/@${user.username}`
|
||||
}, {
|
||||
rel: 'http://ostatus.org/schema/1.0/subscribe',
|
||||
template: `${config.url}/authorize-follow?acct={uri}`
|
||||
}]
|
||||
};
|
||||
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
});
|
||||
|
||||
export default router;
|
102
src/server/well-known.ts
Normal file
102
src/server/well-known.ts
Normal file
|
@ -0,0 +1,102 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import * as Router from 'koa-router';
|
||||
|
||||
import config from '../config';
|
||||
import parseAcct from '../misc/acct/parse';
|
||||
import User from '../models/user';
|
||||
import Acct from '../misc/acct/type';
|
||||
import { links } from './nodeinfo';
|
||||
|
||||
// Init router
|
||||
const router = new Router();
|
||||
|
||||
const webFingerPath = '/.well-known/webfinger';
|
||||
|
||||
router.get('/.well-known/host-meta', async ctx => {
|
||||
ctx.set('Content-Type', 'application/xrd+xml');
|
||||
ctx.body = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
|
||||
<Link rel="lrdd" type="application/xrd+xml" template="${config.url}${webFingerPath}?resource={uri}"/>
|
||||
</XRD>
|
||||
`;
|
||||
});
|
||||
|
||||
router.get('/.well-known/host-meta.json', async ctx => {
|
||||
ctx.set('Content-Type', 'application/jrd+json');
|
||||
ctx.body = {
|
||||
links: [{
|
||||
rel: 'lrdd',
|
||||
type: 'application/xrd+xml',
|
||||
template: `${config.url}${webFingerPath}?resource={uri}`
|
||||
}]
|
||||
};
|
||||
});
|
||||
|
||||
router.get('/.well-known/nodeinfo', async ctx => {
|
||||
ctx.body = { links };
|
||||
});
|
||||
|
||||
router.get(webFingerPath, async ctx => {
|
||||
const generateQuery = (resource: string) =>
|
||||
resource.startsWith(`${config.url.toLowerCase()}/users/`) ?
|
||||
fromId(new mongo.ObjectID(resource.split('/').pop())) :
|
||||
fromAcct(parseAcct(
|
||||
resource.startsWith(`${config.url.toLowerCase()}/@`) ? resource.split('/').pop() :
|
||||
resource.startsWith('acct:') ? resource.slice('acct:'.length) :
|
||||
resource));
|
||||
|
||||
const fromId = (_id: mongo.ObjectID): Record<string, any> => ({
|
||||
_id,
|
||||
host: null
|
||||
});
|
||||
|
||||
const fromAcct = (acct: Acct): Record<string, any> | number =>
|
||||
!acct.host || acct.host === config.host.toLowerCase() ? {
|
||||
usernameLower: acct.username,
|
||||
host: null
|
||||
} : 422;
|
||||
|
||||
if (typeof ctx.query.resource !== 'string') {
|
||||
ctx.status = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
const query = generateQuery(ctx.query.resource.toLowerCase());
|
||||
|
||||
if (typeof query === 'number') {
|
||||
ctx.status = query;
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await User.findOne(query);
|
||||
|
||||
if (user === null) {
|
||||
ctx.status = 404;
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.body = {
|
||||
subject: `acct:${user.username}@${config.host}`,
|
||||
links: [{
|
||||
rel: 'self',
|
||||
type: 'application/activity+json',
|
||||
href: `${config.url}/users/${user._id}`
|
||||
}, {
|
||||
rel: 'http://webfinger.net/rel/profile-page',
|
||||
type: 'text/html',
|
||||
href: `${config.url}/@${user.username}`
|
||||
}, {
|
||||
rel: 'http://ostatus.org/schema/1.0/subscribe',
|
||||
template: `${config.url}/authorize-follow?acct={uri}`
|
||||
}]
|
||||
};
|
||||
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
});
|
||||
|
||||
// Return 404 for other .well-known
|
||||
router.all('/.well-known/*', async ctx => {
|
||||
ctx.status = 404;
|
||||
});
|
||||
|
||||
export default router;
|
Loading…
Reference in a new issue