Add Secure Mode and Private Mode
- Add instance actor - Add private mode, which uses an allowlist - Add Secure Mode, restricts access to blocked instances
This commit is contained in:
parent
7887082622
commit
a03f13fb3f
11 changed files with 289 additions and 13 deletions
|
@ -77,6 +77,21 @@ export class Meta {
|
||||||
})
|
})
|
||||||
public blockedHosts: string[];
|
public blockedHosts: string[];
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false
|
||||||
|
})
|
||||||
|
public secureMode: boolean;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false
|
||||||
|
})
|
||||||
|
public privateMode: boolean;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 256, array: true, default: '{}'
|
||||||
|
})
|
||||||
|
public allowedHosts: string[];
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 512, array: true, default: '{/featured,/channels,/explore,/pages,/about-misskey}',
|
length: 512, array: true, default: '{/featured,/channels,/explore,/pages,/about-misskey}',
|
||||||
})
|
})
|
||||||
|
|
|
@ -28,6 +28,10 @@ export default async (job: Bull.Job<DeliverJobData>) => {
|
||||||
return 'skip (blocked)';
|
return 'skip (blocked)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (meta.privateMode && !meta.allowedHosts.includes(toPuny(host))) {
|
||||||
|
return 'skip (not allowed)';
|
||||||
|
}
|
||||||
|
|
||||||
// isSuspendedなら中断
|
// isSuspendedなら中断
|
||||||
let suspendedHosts = suspendedHostsCache.get(null);
|
let suspendedHosts = suspendedHostsCache.get(null);
|
||||||
if (suspendedHosts == null) {
|
if (suspendedHosts == null) {
|
||||||
|
|
|
@ -39,6 +39,11 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
|
||||||
return `Blocked request: ${host}`;
|
return `Blocked request: ${host}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 非公開モードなら許可なインスタンスのみ
|
||||||
|
if (meta.privateMode && !meta.allowedHosts.includes(host)) {
|
||||||
|
return `Blocked request: ${host}`;
|
||||||
|
}
|
||||||
|
|
||||||
const keyIdLower = signature.keyId.toLowerCase();
|
const keyIdLower = signature.keyId.toLowerCase();
|
||||||
if (keyIdLower.startsWith('acct:')) {
|
if (keyIdLower.startsWith('acct:')) {
|
||||||
return `Old keyId is no longer supported. ${keyIdLower}`;
|
return `Old keyId is no longer supported. ${keyIdLower}`;
|
||||||
|
|
73
packages/backend/src/remote/activitypub/check-fetch.ts
Normal file
73
packages/backend/src/remote/activitypub/check-fetch.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import config from '@/config/index.js';
|
||||||
|
import { IncomingMessage } from 'http';
|
||||||
|
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||||
|
import httpSignature from '@peertube/http-signature';
|
||||||
|
import { URL } from 'url';
|
||||||
|
import { toPuny } from '@/misc/convert-host.js';
|
||||||
|
import DbResolver from '@/remote/activitypub/db-resolver.js';
|
||||||
|
import { getApId } from '@/remote/activitypub/type.js';
|
||||||
|
|
||||||
|
|
||||||
|
export default async function checkFetch(req: IncomingMessage): Promise<number> {
|
||||||
|
const meta = await fetchMeta();
|
||||||
|
if (meta.secureMode || meta.privateMode) {
|
||||||
|
let signature;
|
||||||
|
|
||||||
|
try {
|
||||||
|
signature = httpSignature.parseRequest(req, { 'headers': [] });
|
||||||
|
} catch (e) {
|
||||||
|
return 401;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyId = new URL(signature.keyId);
|
||||||
|
const host = toPuny(keyId.hostname);
|
||||||
|
|
||||||
|
if (meta.blockedHosts.includes(host)) {
|
||||||
|
return 403;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (meta.privateMode && host !== config.host && !meta.allowedHosts.includes(host)) {
|
||||||
|
return 403;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyIdLower = signature.keyId.toLowerCase();
|
||||||
|
if (keyIdLower.startsWith('acct:')) {
|
||||||
|
// Old keyId is no longer supported.
|
||||||
|
return 401;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dbResolver = new DbResolver();
|
||||||
|
|
||||||
|
// HTTP-Signature keyIdを元にDBから取得
|
||||||
|
let authUser = await dbResolver.getAuthUserFromKeyId(signature.keyId);
|
||||||
|
|
||||||
|
// keyIdでわからなければ、resolveしてみる
|
||||||
|
if (authUser == null) {
|
||||||
|
try {
|
||||||
|
keyId.hash = '';
|
||||||
|
authUser = await dbResolver.getAuthUserFromApId(getApId(keyId.toString()));
|
||||||
|
} catch (e) {
|
||||||
|
// できなければ駄目
|
||||||
|
return 403;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// publicKey がなくても終了
|
||||||
|
if (authUser?.key == null) {
|
||||||
|
return 403;
|
||||||
|
}
|
||||||
|
|
||||||
|
// もう一回チェック
|
||||||
|
if (authUser.user.host !== host) {
|
||||||
|
return 403;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP-Signatureの検証
|
||||||
|
const httpSignatureValidated = httpSignature.verifySignature(signature, authUser.key.keyPem);
|
||||||
|
|
||||||
|
if (!httpSignatureValidated) {
|
||||||
|
return 403;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 200;
|
||||||
|
}
|
|
@ -72,6 +72,10 @@ export default class Resolver {
|
||||||
throw new Error('Instance is blocked');
|
throw new Error('Instance is blocked');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (meta.privateMode && config.host !== host && !meta.allowedHosts.includes(host)) {
|
||||||
|
throw new Error('Instance is not allowed');
|
||||||
|
}
|
||||||
|
|
||||||
if (config.signToActivityPubGet && !this.user) {
|
if (config.signToActivityPubGet && !this.user) {
|
||||||
this.user = await getInstanceActor();
|
this.user = await getInstanceActor();
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,12 +12,15 @@ import Followers from './activitypub/followers.js';
|
||||||
import Following from './activitypub/following.js';
|
import Following from './activitypub/following.js';
|
||||||
import Featured from './activitypub/featured.js';
|
import Featured from './activitypub/featured.js';
|
||||||
import { inbox as processInbox } from '@/queue/index.js';
|
import { inbox as processInbox } from '@/queue/index.js';
|
||||||
import { isSelfHost } from '@/misc/convert-host.js';
|
import { isSelfHost, toPuny } from '@/misc/convert-host.js';
|
||||||
import { Notes, Users, Emojis, NoteReactions } from '@/models/index.js';
|
import { Notes, Users, Emojis, NoteReactions } from '@/models/index.js';
|
||||||
import { ILocalUser, User } from '@/models/entities/user.js';
|
import { ILocalUser, User } from '@/models/entities/user.js';
|
||||||
import { In, IsNull, Not } from 'typeorm';
|
import { In, IsNull, Not } from 'typeorm';
|
||||||
import { renderLike } from '@/remote/activitypub/renderer/like.js';
|
import { renderLike } from '@/remote/activitypub/renderer/like.js';
|
||||||
import { getUserKeypair } from '@/misc/keypair-store.js';
|
import { getUserKeypair } from '@/misc/keypair-store.js';
|
||||||
|
import checkFetch from '@/remote/activitypub/check-fetch.js';
|
||||||
|
import { getInstanceActor } from '@/services/instance-actor.js';
|
||||||
|
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||||
import renderFollow from '@/remote/activitypub/renderer/follow.js';
|
import renderFollow from '@/remote/activitypub/renderer/follow.js';
|
||||||
|
|
||||||
// Init router
|
// Init router
|
||||||
|
@ -66,6 +69,12 @@ router.post('/users/:user/inbox', json(), inbox);
|
||||||
router.get('/notes/:note', async (ctx, next) => {
|
router.get('/notes/:note', async (ctx, next) => {
|
||||||
if (!isActivityPubReq(ctx)) return await next();
|
if (!isActivityPubReq(ctx)) return await next();
|
||||||
|
|
||||||
|
const verify = await checkFetch(ctx.req);
|
||||||
|
if (verify != 200) {
|
||||||
|
ctx.status = verify;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const note = await Notes.findOneBy({
|
const note = await Notes.findOneBy({
|
||||||
id: ctx.params.note,
|
id: ctx.params.note,
|
||||||
visibility: In(['public' as const, 'home' as const]),
|
visibility: In(['public' as const, 'home' as const]),
|
||||||
|
@ -88,12 +97,24 @@ router.get('/notes/:note', async (ctx, next) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.body = renderActivity(await renderNote(note, false));
|
ctx.body = renderActivity(await renderNote(note, false));
|
||||||
|
|
||||||
|
const meta = await fetchMeta();
|
||||||
|
if (meta.secureMode || meta.privateMode) {
|
||||||
|
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||||
|
} else {
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
|
}
|
||||||
setResponseType(ctx);
|
setResponseType(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
// note activity
|
// note activity
|
||||||
router.get('/notes/:note/activity', async ctx => {
|
router.get('/notes/:note/activity', async ctx => {
|
||||||
|
const verify = await checkFetch(ctx.req);
|
||||||
|
if (verify != 200) {
|
||||||
|
ctx.status = verify;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const note = await Notes.findOneBy({
|
const note = await Notes.findOneBy({
|
||||||
id: ctx.params.note,
|
id: ctx.params.note,
|
||||||
userHost: IsNull(),
|
userHost: IsNull(),
|
||||||
|
@ -107,7 +128,12 @@ router.get('/notes/:note/activity', async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.body = renderActivity(await packActivity(note));
|
ctx.body = renderActivity(await packActivity(note));
|
||||||
|
const meta = await fetchMeta();
|
||||||
|
if (meta.secureMode || meta.privateMode) {
|
||||||
|
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||||
|
} else {
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
|
}
|
||||||
setResponseType(ctx);
|
setResponseType(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -125,6 +151,20 @@ router.get('/users/:user/collections/featured', Featured);
|
||||||
|
|
||||||
// publickey
|
// publickey
|
||||||
router.get('/users/:user/publickey', async ctx => {
|
router.get('/users/:user/publickey', async ctx => {
|
||||||
|
const instanceActor = await getInstanceActor();
|
||||||
|
if (ctx.params.user === instanceActor.id) {
|
||||||
|
ctx.body = renderActivity(renderKey(instanceActor, await getUserKeypair(instanceActor.id)));
|
||||||
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
|
setResponseType(ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const verify = await checkFetch(ctx.req);
|
||||||
|
if (verify != 200) {
|
||||||
|
ctx.status = verify;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const userId = ctx.params.user;
|
const userId = ctx.params.user;
|
||||||
|
|
||||||
const user = await Users.findOneBy({
|
const user = await Users.findOneBy({
|
||||||
|
@ -141,7 +181,12 @@ router.get('/users/:user/publickey', async ctx => {
|
||||||
|
|
||||||
if (Users.isLocalUser(user)) {
|
if (Users.isLocalUser(user)) {
|
||||||
ctx.body = renderActivity(renderKey(user, keypair));
|
ctx.body = renderActivity(renderKey(user, keypair));
|
||||||
|
const meta = await fetchMeta();
|
||||||
|
if (meta.secureMode || meta.privateMode) {
|
||||||
|
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||||
|
} else {
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
|
}
|
||||||
setResponseType(ctx);
|
setResponseType(ctx);
|
||||||
} else {
|
} else {
|
||||||
ctx.status = 400;
|
ctx.status = 400;
|
||||||
|
@ -156,13 +201,30 @@ async function userInfo(ctx: Router.RouterContext, user: User | null) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.body = renderActivity(await renderPerson(user as ILocalUser));
|
ctx.body = renderActivity(await renderPerson(user as ILocalUser));
|
||||||
|
const meta = await fetchMeta();
|
||||||
|
if (meta.secureMode || meta.privateMode) {
|
||||||
|
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||||
|
} else {
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
|
}
|
||||||
setResponseType(ctx);
|
setResponseType(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
router.get('/users/:user', async (ctx, next) => {
|
router.get('/users/:user', async (ctx, next) => {
|
||||||
if (!isActivityPubReq(ctx)) return await next();
|
if (!isActivityPubReq(ctx)) return await next();
|
||||||
|
|
||||||
|
const instanceActor = await getInstanceActor();
|
||||||
|
if (ctx.params.user === instanceActor.id) {
|
||||||
|
await userInfo(ctx, instanceActor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const verify = await checkFetch(ctx.req);
|
||||||
|
if (verify != 200) {
|
||||||
|
ctx.status = verify;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const userId = ctx.params.user;
|
const userId = ctx.params.user;
|
||||||
|
|
||||||
const user = await Users.findOneBy({
|
const user = await Users.findOneBy({
|
||||||
|
@ -177,6 +239,18 @@ router.get('/users/:user', async (ctx, next) => {
|
||||||
router.get('/@:user', async (ctx, next) => {
|
router.get('/@:user', async (ctx, next) => {
|
||||||
if (!isActivityPubReq(ctx)) return await next();
|
if (!isActivityPubReq(ctx)) return await next();
|
||||||
|
|
||||||
|
if (ctx.params.user === 'instance.actor') {
|
||||||
|
const instanceActor = await getInstanceActor();
|
||||||
|
await userInfo(ctx, instanceActor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const verify = await checkFetch(ctx.req);
|
||||||
|
if (verify != 200) {
|
||||||
|
ctx.status = verify;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const user = await Users.findOneBy({
|
const user = await Users.findOneBy({
|
||||||
usernameLower: ctx.params.user.toLowerCase(),
|
usernameLower: ctx.params.user.toLowerCase(),
|
||||||
host: IsNull(),
|
host: IsNull(),
|
||||||
|
@ -185,10 +259,21 @@ router.get('/@:user', async (ctx, next) => {
|
||||||
|
|
||||||
await userInfo(ctx, user);
|
await userInfo(ctx, user);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get('/actor', async (ctx, next) => {
|
||||||
|
const instanceActor = await getInstanceActor();
|
||||||
|
await userInfo(ctx, instanceActor);
|
||||||
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
// emoji
|
// emoji
|
||||||
router.get('/emojis/:emoji', async ctx => {
|
router.get('/emojis/:emoji', async ctx => {
|
||||||
|
const verify = await checkFetch(ctx.req);
|
||||||
|
if (verify != 200) {
|
||||||
|
ctx.status = verify;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const emoji = await Emojis.findOneBy({
|
const emoji = await Emojis.findOneBy({
|
||||||
host: IsNull(),
|
host: IsNull(),
|
||||||
name: ctx.params.emoji,
|
name: ctx.params.emoji,
|
||||||
|
@ -200,12 +285,23 @@ router.get('/emojis/:emoji', async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.body = renderActivity(await renderEmoji(emoji));
|
ctx.body = renderActivity(await renderEmoji(emoji));
|
||||||
|
const meta = await fetchMeta();
|
||||||
|
if (meta.secureMode || meta.privateMode) {
|
||||||
|
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||||
|
} else {
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
|
}
|
||||||
setResponseType(ctx);
|
setResponseType(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
// like
|
// like
|
||||||
router.get('/likes/:like', async ctx => {
|
router.get('/likes/:like', async ctx => {
|
||||||
|
const verify = await checkFetch(ctx.req);
|
||||||
|
if (verify != 200) {
|
||||||
|
ctx.status = verify;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const reaction = await NoteReactions.findOneBy({ id: ctx.params.like });
|
const reaction = await NoteReactions.findOneBy({ id: ctx.params.like });
|
||||||
|
|
||||||
if (reaction == null) {
|
if (reaction == null) {
|
||||||
|
@ -221,12 +317,22 @@ router.get('/likes/:like', async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.body = renderActivity(await renderLike(reaction, note));
|
ctx.body = renderActivity(await renderLike(reaction, note));
|
||||||
|
const meta = await fetchMeta();
|
||||||
|
if (meta.secureMode || meta.privateMode) {
|
||||||
|
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||||
|
} else {
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
|
}
|
||||||
setResponseType(ctx);
|
setResponseType(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
// follow
|
// follow
|
||||||
router.get('/follows/:follower/:followee', async ctx => {
|
router.get('/follows/:follower/:followee', async ctx => {
|
||||||
|
const verify = await checkFetch(ctx.req);
|
||||||
|
if (verify != 200) {
|
||||||
|
ctx.status = verify;
|
||||||
|
return;
|
||||||
|
}
|
||||||
// This may be used before the follow is completed, so we do not
|
// This may be used before the follow is completed, so we do not
|
||||||
// check if the following exists.
|
// check if the following exists.
|
||||||
|
|
||||||
|
@ -247,7 +353,12 @@ router.get('/follows/:follower/:followee', async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.body = renderActivity(renderFollow(follower, followee));
|
ctx.body = renderActivity(renderFollow(follower, followee));
|
||||||
|
const meta = await fetchMeta();
|
||||||
|
if (meta.secureMode || meta.privateMode) {
|
||||||
|
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||||
|
} else {
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
|
}
|
||||||
setResponseType(ctx);
|
setResponseType(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,16 @@ import { setResponseType } from '../activitypub.js';
|
||||||
import renderNote from '@/remote/activitypub/renderer/note.js';
|
import renderNote from '@/remote/activitypub/renderer/note.js';
|
||||||
import { Users, Notes, UserNotePinings } from '@/models/index.js';
|
import { Users, Notes, UserNotePinings } from '@/models/index.js';
|
||||||
import { IsNull } from 'typeorm';
|
import { IsNull } from 'typeorm';
|
||||||
|
import checkFetch from '@/remote/activitypub/check-fetch.js';
|
||||||
|
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||||
|
|
||||||
export default async (ctx: Router.RouterContext) => {
|
export default async (ctx: Router.RouterContext) => {
|
||||||
|
const verify = await checkFetch(ctx.req);
|
||||||
|
if (verify != 200) {
|
||||||
|
ctx.status = verify;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const userId = ctx.params.user;
|
const userId = ctx.params.user;
|
||||||
|
|
||||||
const user = await Users.findOneBy({
|
const user = await Users.findOneBy({
|
||||||
|
@ -36,6 +44,12 @@ export default async (ctx: Router.RouterContext) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.body = renderActivity(rendered);
|
ctx.body = renderActivity(rendered);
|
||||||
|
|
||||||
|
const meta = await fetchMeta();
|
||||||
|
if (meta.secureMode || meta.privateMode) {
|
||||||
|
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||||
|
} else {
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
|
}
|
||||||
setResponseType(ctx);
|
setResponseType(ctx);
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,8 +9,16 @@ import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js';
|
||||||
import { Users, Followings, UserProfiles } from '@/models/index.js';
|
import { Users, Followings, UserProfiles } from '@/models/index.js';
|
||||||
import { Following } from '@/models/entities/following.js';
|
import { Following } from '@/models/entities/following.js';
|
||||||
import { setResponseType } from '../activitypub.js';
|
import { setResponseType } from '../activitypub.js';
|
||||||
|
import checkFetch from '@/remote/activitypub/check-fetch.js';
|
||||||
|
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||||
|
|
||||||
export default async (ctx: Router.RouterContext) => {
|
export default async (ctx: Router.RouterContext) => {
|
||||||
|
const verify = await checkFetch(ctx.req);
|
||||||
|
if (verify != 200) {
|
||||||
|
ctx.status = verify;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const userId = ctx.params.user;
|
const userId = ctx.params.user;
|
||||||
|
|
||||||
const cursor = ctx.request.query.cursor;
|
const cursor = ctx.request.query.cursor;
|
||||||
|
@ -89,7 +97,12 @@ export default async (ctx: Router.RouterContext) => {
|
||||||
// index page
|
// index page
|
||||||
const rendered = renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`);
|
const rendered = renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`);
|
||||||
ctx.body = renderActivity(rendered);
|
ctx.body = renderActivity(rendered);
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
|
||||||
setResponseType(ctx);
|
setResponseType(ctx);
|
||||||
}
|
}
|
||||||
|
const meta = await fetchMeta();
|
||||||
|
if (meta.secureMode || meta.privateMode) {
|
||||||
|
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||||
|
} else {
|
||||||
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,8 +9,16 @@ import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js';
|
||||||
import { Users, Followings, UserProfiles } from '@/models/index.js';
|
import { Users, Followings, UserProfiles } from '@/models/index.js';
|
||||||
import { Following } from '@/models/entities/following.js';
|
import { Following } from '@/models/entities/following.js';
|
||||||
import { setResponseType } from '../activitypub.js';
|
import { setResponseType } from '../activitypub.js';
|
||||||
|
import checkFetch from '@/remote/activitypub/check-fetch.js';
|
||||||
|
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||||
|
|
||||||
export default async (ctx: Router.RouterContext) => {
|
export default async (ctx: Router.RouterContext) => {
|
||||||
|
const verify = await checkFetch(ctx.req);
|
||||||
|
if (verify != 200) {
|
||||||
|
ctx.status = verify;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const userId = ctx.params.user;
|
const userId = ctx.params.user;
|
||||||
|
|
||||||
const cursor = ctx.request.query.cursor;
|
const cursor = ctx.request.query.cursor;
|
||||||
|
@ -89,7 +97,12 @@ export default async (ctx: Router.RouterContext) => {
|
||||||
// index page
|
// index page
|
||||||
const rendered = renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`);
|
const rendered = renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`);
|
||||||
ctx.body = renderActivity(rendered);
|
ctx.body = renderActivity(rendered);
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
|
||||||
setResponseType(ctx);
|
setResponseType(ctx);
|
||||||
}
|
}
|
||||||
|
const meta = await fetchMeta();
|
||||||
|
if (meta.secureMode || meta.privateMode) {
|
||||||
|
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||||
|
} else {
|
||||||
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,8 +13,16 @@ import { Users, Notes } from '@/models/index.js';
|
||||||
import { Note } from '@/models/entities/note.js';
|
import { Note } from '@/models/entities/note.js';
|
||||||
import { makePaginationQuery } from '../api/common/make-pagination-query.js';
|
import { makePaginationQuery } from '../api/common/make-pagination-query.js';
|
||||||
import { setResponseType } from '../activitypub.js';
|
import { setResponseType } from '../activitypub.js';
|
||||||
|
import checkFetch from '@/remote/activitypub/check-fetch.js';
|
||||||
|
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||||
|
|
||||||
export default async (ctx: Router.RouterContext) => {
|
export default async (ctx: Router.RouterContext) => {
|
||||||
|
const verify = await checkFetch(ctx.req);
|
||||||
|
if (verify != 200) {
|
||||||
|
ctx.status = verify;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const userId = ctx.params.user;
|
const userId = ctx.params.user;
|
||||||
|
|
||||||
const sinceId = ctx.request.query.since_id;
|
const sinceId = ctx.request.query.since_id;
|
||||||
|
@ -89,9 +97,15 @@ export default async (ctx: Router.RouterContext) => {
|
||||||
`${partOf}?page=true&since_id=000000000000000000000000`,
|
`${partOf}?page=true&since_id=000000000000000000000000`,
|
||||||
);
|
);
|
||||||
ctx.body = renderActivity(rendered);
|
ctx.body = renderActivity(rendered);
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
|
||||||
setResponseType(ctx);
|
setResponseType(ctx);
|
||||||
}
|
}
|
||||||
|
const meta = await fetchMeta();
|
||||||
|
if (meta.secureMode || meta.privateMode) {
|
||||||
|
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||||
|
} else {
|
||||||
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -291,6 +291,16 @@ export const meta = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
secureMode: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: true, nullable: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
privateMode: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: true, nullable: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -326,7 +336,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
expiresAt: MoreThan(new Date()),
|
expiresAt: MoreThan(new Date()),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
// TODO: add secure mode, etc
|
||||||
const response: any = {
|
const response: any = {
|
||||||
maintainerName: instance.maintainerName,
|
maintainerName: instance.maintainerName,
|
||||||
maintainerEmail: instance.maintainerEmail,
|
maintainerEmail: instance.maintainerEmail,
|
||||||
|
|
Loading…
Reference in a new issue