mirror of
https://iceshrimp.dev/limepotato/jormungandr-bite.git
synced 2024-11-22 09:57:29 -07:00
/.well-known 周りをいい感じに (#4141)
* Enhance /.well-known and their friends * Fix bug
This commit is contained in:
parent
0236405970
commit
753684e7ea
9 changed files with 202 additions and 89 deletions
|
@ -4,6 +4,10 @@
|
||||||
"version": "10.81.0",
|
"version": "10.81.0",
|
||||||
"clientVersion": "2.0.14026",
|
"clientVersion": "2.0.14026",
|
||||||
"codename": "nighthike",
|
"codename": "nighthike",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/syuilo/misskey.git"
|
||||||
|
},
|
||||||
"main": "./index.js",
|
"main": "./index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"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' {
|
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);
|
if (acct.startsWith('@')) acct = acct.substr(1);
|
||||||
const split = acct.split('@', 2);
|
const split = acct.split('@', 2);
|
||||||
return { username: split[0], host: split[1] || null };
|
return { username: split[0], host: split[1] || null };
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
type UserLike = {
|
import Acct from './type';
|
||||||
host: string;
|
|
||||||
username: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default (user: UserLike) => {
|
export default (user: Acct) => {
|
||||||
return user.host === null ? user.username : `${user.username}@${user.host}`;
|
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 * as slow from 'koa-slow';
|
||||||
|
|
||||||
import activityPub from './activitypub';
|
import activityPub from './activitypub';
|
||||||
import webFinger from './webfinger';
|
import nodeinfo from './nodeinfo';
|
||||||
|
import wellKnown from './well-known';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import networkChart from '../chart/network';
|
import networkChart from '../chart/network';
|
||||||
import apiServer from './api';
|
import apiServer from './api';
|
||||||
|
@ -68,7 +69,8 @@ const router = new Router();
|
||||||
|
|
||||||
// Routing
|
// Routing
|
||||||
router.use(activityPub.routes());
|
router.use(activityPub.routes());
|
||||||
router.use(webFinger.routes());
|
router.use(nodeinfo.routes());
|
||||||
|
router.use(wellKnown.routes());
|
||||||
|
|
||||||
router.get('/verify-email/:code', async ctx => {
|
router.get('/verify-email/:code', async ctx => {
|
||||||
const user = await User.findOne({ emailVerifyCode: ctx.params.code });
|
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
|
// Register router
|
||||||
app.use(router.routes());
|
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