Merge remote-tracking branch 'nullobsi/secure-fetch' into develop
This commit is contained in:
commit
fcb91702d9
91 changed files with 619 additions and 120 deletions
|
@ -778,6 +778,13 @@ middle: "中"
|
||||||
low: "低"
|
low: "低"
|
||||||
emailNotConfiguredWarning: "メールアドレスの設定がされていません。"
|
emailNotConfiguredWarning: "メールアドレスの設定がされていません。"
|
||||||
ratio: "比率"
|
ratio: "比率"
|
||||||
|
secureMode: "セキュアモード (Authorized Fetch)"
|
||||||
|
instanceSecurity: "インスタンスのセキュリティー"
|
||||||
|
secureModeInfo: "他のインスタンスからリクエストするときに、証明を付けなければ返送しません。他のインスタンスの設定ファイルでsignToActivityPubGetはtrueにしてください。"
|
||||||
|
privateMode: "非公開モード"
|
||||||
|
privateModeInfo: "有効にして、許可されているインスタンスのみがリクエストできます。すべてのノートが公開に非表示にします。"
|
||||||
|
allowedInstances: "許可されたインスタンス"
|
||||||
|
allowedInstancesDescription: "許可したいインスタンスのホストを改行で区切って設定します。非公開モードだけで有効です。"
|
||||||
previewNoteText: "本文をプレビュー"
|
previewNoteText: "本文をプレビュー"
|
||||||
customCss: "カスタムCSS"
|
customCss: "カスタムCSS"
|
||||||
customCssWarn: "この設定は必ず知識のある方が行ってください。不適切な設定を行うとクライアントが正常に使用できなくなる恐れがあります。"
|
customCssWarn: "この設定は必ず知識のある方が行ってください。不適切な設定を行うとクライアントが正常に使用できなくなる恐れがあります。"
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
|
||||||
|
|
||||||
|
export class allowlistSecureMode1626733991004 {
|
||||||
|
name = 'allowlistSecureMode1626733991004';
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "allowedHosts" character varying(256) [] default '{}'`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "secureMode" bool default false`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "privateMode" bool default false`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "allowedHosts"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "secureMode"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "privateMode"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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));
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
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));
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
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');
|
||||||
|
}
|
||||||
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));
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
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');
|
||||||
|
}
|
||||||
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));
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
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');
|
||||||
|
}
|
||||||
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));
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
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');
|
||||||
|
}
|
||||||
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));
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
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');
|
||||||
|
}
|
||||||
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));
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
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');
|
||||||
|
}
|
||||||
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);
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
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');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,6 +7,8 @@ import { limiter } from './limiter.js';
|
||||||
import endpoints, { IEndpointMeta } from './endpoints.js';
|
import endpoints, { IEndpointMeta } from './endpoints.js';
|
||||||
import { ApiError } from './error.js';
|
import { ApiError } from './error.js';
|
||||||
import { apiLogger } from './logger.js';
|
import { apiLogger } from './logger.js';
|
||||||
|
import { AccessToken } from '@/models/entities/access-token.js';
|
||||||
|
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||||
|
|
||||||
const accessDenied = {
|
const accessDenied = {
|
||||||
message: 'Access denied.',
|
message: 'Access denied.',
|
||||||
|
@ -93,6 +95,17 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// private mode
|
||||||
|
const meta = await fetchMeta();
|
||||||
|
if (meta.privateMode && ep.meta.requireCredentialPrivateMode && user == null) {
|
||||||
|
throw new ApiError({
|
||||||
|
message: 'Credential required.',
|
||||||
|
code: 'CREDENTIAL_REQUIRED',
|
||||||
|
id: '1384574d-a912-4b81-8601-c7b1c4085df1',
|
||||||
|
httpStatusCode: 401
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Cast non JSON input
|
// Cast non JSON input
|
||||||
if ((ep.meta.requireFile || ctx?.method === 'GET') && ep.params.properties) {
|
if ((ep.meta.requireFile || ctx?.method === 'GET') && ep.params.properties) {
|
||||||
for (const k of Object.keys(ep.params.properties)) {
|
for (const k of Object.keys(ep.params.properties)) {
|
||||||
|
|
|
@ -706,6 +706,12 @@ export interface IEndpointMeta {
|
||||||
*/
|
*/
|
||||||
readonly secure?: boolean;
|
readonly secure?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* プライベートモードでなら、このエンドポイントにリクエストするときにユーザー情報が必要か否か
|
||||||
|
* 省略した場合は false として解釈されます
|
||||||
|
*/
|
||||||
|
readonly requireCredentialPrivateMode?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* エンドポイントの種類
|
* エンドポイントの種類
|
||||||
* パーミッションの実現に利用されます。
|
* パーミッションの実現に利用されます。
|
||||||
|
|
|
@ -187,6 +187,22 @@ export const meta = {
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
allowedHosts: {
|
||||||
|
type: 'array',
|
||||||
|
optional: true, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
privateMode: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
secureMode: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
hcaptchaSecretKey: {
|
hcaptchaSecretKey: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: true, nullable: true,
|
optional: true, nullable: true,
|
||||||
|
@ -388,6 +404,9 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
pinnedUsers: instance.pinnedUsers,
|
pinnedUsers: instance.pinnedUsers,
|
||||||
hiddenTags: instance.hiddenTags,
|
hiddenTags: instance.hiddenTags,
|
||||||
blockedHosts: instance.blockedHosts,
|
blockedHosts: instance.blockedHosts,
|
||||||
|
allowedHosts: instance.allowedHosts,
|
||||||
|
privateMode: instance.privateMode,
|
||||||
|
secureMode: instance.secureMode,
|
||||||
hcaptchaSecretKey: instance.hcaptchaSecretKey,
|
hcaptchaSecretKey: instance.hcaptchaSecretKey,
|
||||||
recaptchaSecretKey: instance.recaptchaSecretKey,
|
recaptchaSecretKey: instance.recaptchaSecretKey,
|
||||||
sensitiveMediaDetection: instance.sensitiveMediaDetection,
|
sensitiveMediaDetection: instance.sensitiveMediaDetection,
|
||||||
|
|
|
@ -27,6 +27,11 @@ export const paramDef = {
|
||||||
blockedHosts: { type: 'array', nullable: true, items: {
|
blockedHosts: { type: 'array', nullable: true, items: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
} },
|
} },
|
||||||
|
allowedHosts: { type: 'array', nullable: true, items: {
|
||||||
|
type: 'string',
|
||||||
|
} },
|
||||||
|
secureMode: { type: 'boolean', nullable: true },
|
||||||
|
privateMode: { type: 'boolean', nullable: true },
|
||||||
themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' },
|
themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' },
|
||||||
mascotImageUrl: { type: 'string', nullable: true },
|
mascotImageUrl: { type: 'string', nullable: true },
|
||||||
bannerUrl: { type: 'string', nullable: true },
|
bannerUrl: { type: 'string', nullable: true },
|
||||||
|
@ -142,6 +147,18 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
set.themeColor = ps.themeColor;
|
set.themeColor = ps.themeColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(ps.allowedHosts)) {
|
||||||
|
set.allowedHosts = ps.allowedHosts.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof ps.privateMode === 'boolean') {
|
||||||
|
set.privateMode = ps.privateMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof ps.secureMode === 'boolean') {
|
||||||
|
set.secureMode = ps.secureMode;
|
||||||
|
}
|
||||||
|
|
||||||
if (ps.mascotImageUrl !== undefined) {
|
if (ps.mascotImageUrl !== undefined) {
|
||||||
set.mascotImageUrl = ps.mascotImageUrl;
|
set.mascotImageUrl = ps.mascotImageUrl;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ export const meta = {
|
||||||
tags: ['meta'],
|
tags: ['meta'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -5,6 +5,7 @@ export const meta = {
|
||||||
tags: ['channels'],
|
tags: ['channels'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -6,6 +6,7 @@ export const meta = {
|
||||||
tags: ['channels'],
|
tags: ['channels'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
|
|
@ -8,6 +8,7 @@ export const meta = {
|
||||||
tags: ['notes', 'channels'],
|
tags: ['notes', 'channels'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -4,6 +4,7 @@ import define from '../../define.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['charts', 'users'],
|
tags: ['charts', 'users'],
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: getJsonSchema(activeUsersChart.schema),
|
res: getJsonSchema(activeUsersChart.schema),
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import define from '../../define.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['charts'],
|
tags: ['charts'],
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: getJsonSchema(apRequestChart.schema),
|
res: getJsonSchema(apRequestChart.schema),
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import define from '../../define.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['charts', 'drive'],
|
tags: ['charts', 'drive'],
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: getJsonSchema(driveChart.schema),
|
res: getJsonSchema(driveChart.schema),
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import define from '../../define.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['charts'],
|
tags: ['charts'],
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: getJsonSchema(federationChart.schema),
|
res: getJsonSchema(federationChart.schema),
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import define from '../../define.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['charts', 'hashtags'],
|
tags: ['charts', 'hashtags'],
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: getJsonSchema(hashtagChart.schema),
|
res: getJsonSchema(hashtagChart.schema),
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import define from '../../define.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['charts'],
|
tags: ['charts'],
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: getJsonSchema(instanceChart.schema),
|
res: getJsonSchema(instanceChart.schema),
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import define from '../../define.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['charts', 'notes'],
|
tags: ['charts', 'notes'],
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: getJsonSchema(notesChart.schema),
|
res: getJsonSchema(notesChart.schema),
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import define from '../../../define.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['charts', 'drive', 'users'],
|
tags: ['charts', 'drive', 'users'],
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: getJsonSchema(perUserDriveChart.schema),
|
res: getJsonSchema(perUserDriveChart.schema),
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { perUserFollowingChart } from '@/services/chart/index.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['charts', 'users', 'following'],
|
tags: ['charts', 'users', 'following'],
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: getJsonSchema(perUserFollowingChart.schema),
|
res: getJsonSchema(perUserFollowingChart.schema),
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import define from '../../../define.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['charts', 'users', 'notes'],
|
tags: ['charts', 'users', 'notes'],
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: getJsonSchema(perUserNotesChart.schema),
|
res: getJsonSchema(perUserNotesChart.schema),
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import define from '../../../define.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['charts', 'users', 'reactions'],
|
tags: ['charts', 'users', 'reactions'],
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: getJsonSchema(perUserReactionsChart.schema),
|
res: getJsonSchema(perUserReactionsChart.schema),
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import define from '../../define.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['charts', 'users'],
|
tags: ['charts', 'users'],
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: getJsonSchema(usersChart.schema),
|
res: getJsonSchema(usersChart.schema),
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ export const meta = {
|
||||||
tags: ['account', 'notes', 'clips'],
|
tags: ['account', 'notes', 'clips'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
kind: 'read:account',
|
kind: 'read:account',
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ export const meta = {
|
||||||
tags: ['clips', 'account'],
|
tags: ['clips', 'account'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
kind: 'read:account',
|
kind: 'read:account',
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ export const meta = {
|
||||||
tags: ['federation'],
|
tags: ['federation'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -6,6 +6,7 @@ export const meta = {
|
||||||
tags: ['federation'],
|
tags: ['federation'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -7,6 +7,7 @@ export const meta = {
|
||||||
tags: ['federation'],
|
tags: ['federation'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -6,6 +6,7 @@ export const meta = {
|
||||||
tags: ['federation'],
|
tags: ['federation'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
oneOf: [{
|
oneOf: [{
|
||||||
|
|
|
@ -6,6 +6,7 @@ export const meta = {
|
||||||
tags: ['federation'],
|
tags: ['federation'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -5,6 +5,7 @@ export const meta = {
|
||||||
tags: ['gallery'],
|
tags: ['gallery'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -5,6 +5,7 @@ export const meta = {
|
||||||
tags: ['gallery'],
|
tags: ['gallery'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { GalleryPosts } from '@/models/index.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['gallery'],
|
tags: ['gallery'],
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -6,6 +6,7 @@ export const meta = {
|
||||||
tags: ['gallery'],
|
tags: ['gallery'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
errors: {
|
errors: {
|
||||||
noSuchPost: {
|
noSuchPost: {
|
||||||
|
|
|
@ -7,6 +7,7 @@ export const meta = {
|
||||||
tags: ['meta'],
|
tags: ['meta'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
|
|
|
@ -5,6 +5,7 @@ export const meta = {
|
||||||
tags: ['hashtags'],
|
tags: ['hashtags'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -5,6 +5,7 @@ export const meta = {
|
||||||
tags: ['hashtags'],
|
tags: ['hashtags'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -7,6 +7,7 @@ export const meta = {
|
||||||
tags: ['hashtags'],
|
tags: ['hashtags'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
|
|
@ -24,6 +24,7 @@ export const meta = {
|
||||||
tags: ['hashtags'],
|
tags: ['hashtags'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
tags: ['hashtags', 'users'],
|
tags: ['hashtags', 'users'],
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -340,6 +350,10 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
tosUrl: instance.ToSUrl,
|
tosUrl: instance.ToSUrl,
|
||||||
repositoryUrl: instance.repositoryUrl,
|
repositoryUrl: instance.repositoryUrl,
|
||||||
feedbackUrl: instance.feedbackUrl,
|
feedbackUrl: instance.feedbackUrl,
|
||||||
|
|
||||||
|
secureMode: instance.secureMode,
|
||||||
|
privateMode: instance.privateMode,
|
||||||
|
|
||||||
disableRegistration: instance.disableRegistration,
|
disableRegistration: instance.disableRegistration,
|
||||||
disableLocalTimeline: instance.disableLocalTimeline,
|
disableLocalTimeline: instance.disableLocalTimeline,
|
||||||
disableGlobalTimeline: instance.disableGlobalTimeline,
|
disableGlobalTimeline: instance.disableGlobalTimeline,
|
||||||
|
@ -359,10 +373,10 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
backgroundImageUrl: instance.backgroundImageUrl,
|
backgroundImageUrl: instance.backgroundImageUrl,
|
||||||
logoImageUrl: instance.logoImageUrl,
|
logoImageUrl: instance.logoImageUrl,
|
||||||
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため
|
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため
|
||||||
emojis: await Emojis.packMany(emojis),
|
emojis: instance.privateMode && !me ? [] : await Emojis.packMany(emojis),
|
||||||
defaultLightTheme: instance.defaultLightTheme,
|
defaultLightTheme: instance.defaultLightTheme,
|
||||||
defaultDarkTheme: instance.defaultDarkTheme,
|
defaultDarkTheme: instance.defaultDarkTheme,
|
||||||
ads: ads.map(ad => ({
|
ads: instance.privateMode && !me ? [] : ads.map(ad => ({
|
||||||
id: ad.id,
|
id: ad.id,
|
||||||
url: ad.url,
|
url: ad.url,
|
||||||
place: ad.place,
|
place: ad.place,
|
||||||
|
@ -380,8 +394,8 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
translatorAvailable: instance.deeplAuthKey != null,
|
translatorAvailable: instance.deeplAuthKey != null,
|
||||||
|
|
||||||
...(ps.detail ? {
|
...(ps.detail ? {
|
||||||
pinnedPages: instance.pinnedPages,
|
pinnedPages: instance.privateMode && !me ? [] : instance.pinnedPages,
|
||||||
pinnedClipId: instance.pinnedClipId,
|
pinnedClipId: instance.privateMode && !me ? [] : instance.pinnedClipId,
|
||||||
cacheRemoteFiles: instance.cacheRemoteFiles,
|
cacheRemoteFiles: instance.cacheRemoteFiles,
|
||||||
requireSetup: (await Users.countBy({
|
requireSetup: (await Users.countBy({
|
||||||
host: IsNull(),
|
host: IsNull(),
|
||||||
|
@ -390,9 +404,11 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (ps.detail) {
|
if (ps.detail) {
|
||||||
const proxyAccount = instance.proxyAccountId ? await Users.pack(instance.proxyAccountId).catch(() => null) : null;
|
if (!instance.privateMode || me) {
|
||||||
|
const proxyAccount = instance.proxyAccountId ? await Users.pack(instance.proxyAccountId).catch(() => null) : null;
|
||||||
|
response.proxyAccountName = proxyAccount ? proxyAccount.username : null;
|
||||||
|
}
|
||||||
|
|
||||||
response.proxyAccountName = proxyAccount ? proxyAccount.username : null;
|
|
||||||
response.features = {
|
response.features = {
|
||||||
registration: !instance.disableRegistration,
|
registration: !instance.disableRegistration,
|
||||||
localTimeLine: !instance.disableLocalTimeline,
|
localTimeLine: !instance.disableLocalTimeline,
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { makePaginationQuery } from '../common/make-pagination-query.js';
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
|
|
@ -10,6 +10,7 @@ export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
@ -20,7 +21,7 @@ export const meta = {
|
||||||
ref: 'Note',
|
ref: 'Note',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
};
|
||||||
|
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
|
|
@ -8,6 +8,7 @@ export const meta = {
|
||||||
tags: ['clips', 'notes'],
|
tags: ['clips', 'notes'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -8,6 +8,7 @@ export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -7,6 +7,7 @@ export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -8,6 +8,7 @@ export const meta = {
|
||||||
tags: ['notes', 'reactions'],
|
tags: ['notes', 'reactions'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
allowGet: true,
|
allowGet: true,
|
||||||
cacheSec: 60,
|
cacheSec: 60,
|
||||||
|
|
|
@ -11,6 +11,7 @@ export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -9,6 +9,7 @@ export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['notes', 'hashtags'],
|
tags: ['notes', 'hashtags'],
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -12,6 +12,7 @@ export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -7,6 +7,7 @@ export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
|
|
@ -12,6 +12,7 @@ export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
|
|
@ -5,6 +5,7 @@ export const meta = {
|
||||||
tags: ['pages'],
|
tags: ['pages'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -8,6 +8,7 @@ export const meta = {
|
||||||
tags: ['pages'],
|
tags: ['pages'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
|
|
@ -9,6 +9,7 @@ export const meta = {
|
||||||
tags: ['users'],
|
tags: ['users'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -4,6 +4,7 @@ import define from '../define.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
tags: ['meta'],
|
tags: ['meta'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { IsNull } from 'typeorm';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
tags: ['meta'],
|
tags: ['meta'],
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ export const meta = {
|
||||||
tags: ['users'],
|
tags: ['users'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['users', 'clips'],
|
tags: ['users', 'clips'],
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
description: 'Show all clips this user owns.',
|
description: 'Show all clips this user owns.',
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ export const meta = {
|
||||||
tags: ['users'],
|
tags: ['users'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
description: 'Show everyone that follows this user.',
|
description: 'Show everyone that follows this user.',
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ export const meta = {
|
||||||
tags: ['users'],
|
tags: ['users'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
description: 'Show everyone that this user is following.',
|
description: 'Show everyone that this user is following.',
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { makePaginationQuery } from '../../../common/make-pagination-query.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['users', 'gallery'],
|
tags: ['users', 'gallery'],
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
description: 'Show all gallery posts by the given user.',
|
description: 'Show all gallery posts by the given user.',
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ export const meta = {
|
||||||
tags: ['users'],
|
tags: ['users'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
description: 'Get a list of other users that the specified user frequently replies to.',
|
description: 'Get a list of other users that the specified user frequently replies to.',
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['users', 'notes'],
|
tags: ['users', 'notes'],
|
||||||
|
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
description: 'Show all notes that this user created.',
|
description: 'Show all notes that this user created.',
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['users', 'pages'],
|
tags: ['users', 'pages'],
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
description: 'Show all pages this user created.',
|
description: 'Show all pages this user created.',
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ export const meta = {
|
||||||
tags: ['users', 'reactions'],
|
tags: ['users', 'reactions'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
description: 'Show all reactions this user made.',
|
description: 'Show all reactions this user made.',
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ export const meta = {
|
||||||
tags: ['users'],
|
tags: ['users'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
description: 'Search for a user by username and/or host.',
|
description: 'Search for a user by username and/or host.',
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ export const meta = {
|
||||||
tags: ['users'],
|
tags: ['users'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
description: 'Search for users.',
|
description: 'Search for users.',
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ export const meta = {
|
||||||
tags: ['users'],
|
tags: ['users'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
description: 'Show the properties of a user.',
|
description: 'Show the properties of a user.',
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ export const meta = {
|
||||||
tags: ['users'],
|
tags: ['users'],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: false,
|
||||||
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
description: 'Show statistics about a user.',
|
description: 'Show statistics about a user.',
|
||||||
|
|
||||||
|
|
|
@ -218,6 +218,10 @@ router.get('/api.json', async ctx => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const getFeed = async (acct: string) => {
|
const getFeed = async (acct: string) => {
|
||||||
|
const meta = await fetchMeta();
|
||||||
|
if (meta.privateMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const { username, host } = Acct.parse(acct);
|
const { username, host } = Acct.parse(acct);
|
||||||
const user = await Users.findOneBy({
|
const user = await Users.findOneBy({
|
||||||
usernameLower: username.toLowerCase(),
|
usernameLower: username.toLowerCase(),
|
||||||
|
@ -290,6 +294,7 @@ router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => {
|
||||||
instanceName: meta.name || 'Misskey',
|
instanceName: meta.name || 'Misskey',
|
||||||
icon: meta.iconUrl,
|
icon: meta.iconUrl,
|
||||||
themeColor: meta.themeColor,
|
themeColor: meta.themeColor,
|
||||||
|
privateMode: meta.privateMode,
|
||||||
});
|
});
|
||||||
ctx.set('Cache-Control', 'public, max-age=15');
|
ctx.set('Cache-Control', 'public, max-age=15');
|
||||||
} else {
|
} else {
|
||||||
|
@ -333,6 +338,7 @@ router.get('/notes/:note', async (ctx, next) => {
|
||||||
summary: getNoteSummary(_note),
|
summary: getNoteSummary(_note),
|
||||||
instanceName: meta.name || 'Misskey',
|
instanceName: meta.name || 'Misskey',
|
||||||
icon: meta.iconUrl,
|
icon: meta.iconUrl,
|
||||||
|
privateMode: meta.privateMode,
|
||||||
themeColor: meta.themeColor,
|
themeColor: meta.themeColor,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -370,6 +376,7 @@ router.get('/@:user/pages/:page', async (ctx, next) => {
|
||||||
instanceName: meta.name || 'Misskey',
|
instanceName: meta.name || 'Misskey',
|
||||||
icon: meta.iconUrl,
|
icon: meta.iconUrl,
|
||||||
themeColor: meta.themeColor,
|
themeColor: meta.themeColor,
|
||||||
|
privateMode: meta.privateMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (['public'].includes(page.visibility)) {
|
if (['public'].includes(page.visibility)) {
|
||||||
|
@ -400,6 +407,7 @@ router.get('/clips/:clip', async (ctx, next) => {
|
||||||
profile,
|
profile,
|
||||||
avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: clip.userId })),
|
avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: clip.userId })),
|
||||||
instanceName: meta.name || 'Misskey',
|
instanceName: meta.name || 'Misskey',
|
||||||
|
privateMode: meta.privateMode,
|
||||||
icon: meta.iconUrl,
|
icon: meta.iconUrl,
|
||||||
themeColor: meta.themeColor,
|
themeColor: meta.themeColor,
|
||||||
});
|
});
|
||||||
|
@ -427,6 +435,7 @@ router.get('/gallery/:post', async (ctx, next) => {
|
||||||
instanceName: meta.name || 'Misskey',
|
instanceName: meta.name || 'Misskey',
|
||||||
icon: meta.iconUrl,
|
icon: meta.iconUrl,
|
||||||
themeColor: meta.themeColor,
|
themeColor: meta.themeColor,
|
||||||
|
privateMode: meta.privateMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.set('Cache-Control', 'public, max-age=15');
|
ctx.set('Cache-Control', 'public, max-age=15');
|
||||||
|
@ -451,6 +460,7 @@ router.get('/channels/:channel', async (ctx, next) => {
|
||||||
instanceName: meta.name || 'Misskey',
|
instanceName: meta.name || 'Misskey',
|
||||||
icon: meta.iconUrl,
|
icon: meta.iconUrl,
|
||||||
themeColor: meta.themeColor,
|
themeColor: meta.themeColor,
|
||||||
|
privateMode: meta.privateMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.set('Cache-Control', 'public, max-age=15');
|
ctx.set('Cache-Control', 'public, max-age=15');
|
||||||
|
@ -464,6 +474,10 @@ router.get('/channels/:channel', async (ctx, next) => {
|
||||||
|
|
||||||
router.get('/_info_card_', async ctx => {
|
router.get('/_info_card_', async ctx => {
|
||||||
const meta = await fetchMeta(true);
|
const meta = await fetchMeta(true);
|
||||||
|
if (meta.privateMode) {
|
||||||
|
ctx.status = 403;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ctx.remove('X-Frame-Options');
|
ctx.remove('X-Frame-Options');
|
||||||
|
|
||||||
|
@ -511,6 +525,7 @@ router.get('(.*)', async ctx => {
|
||||||
desc: meta.description,
|
desc: meta.description,
|
||||||
icon: meta.iconUrl,
|
icon: meta.iconUrl,
|
||||||
themeColor: meta.themeColor,
|
themeColor: meta.themeColor,
|
||||||
|
privateMode: meta.privateMode,
|
||||||
});
|
});
|
||||||
ctx.set('Cache-Control', 'public, max-age=15');
|
ctx.set('Cache-Control', 'public, max-age=15');
|
||||||
});
|
});
|
||||||
|
|
|
@ -51,6 +51,8 @@ html
|
||||||
meta(name='description' content= desc || '✨🌎✨ A interplanetary communication platform ✨🚀✨')
|
meta(name='description' content= desc || '✨🌎✨ A interplanetary communication platform ✨🚀✨')
|
||||||
|
|
||||||
block meta
|
block meta
|
||||||
|
if privateMode
|
||||||
|
meta(name='robots' content='noindex')
|
||||||
|
|
||||||
block og
|
block og
|
||||||
meta(property='og:title' content= title || 'Calckey')
|
meta(property='og:title' content= title || 'Calckey')
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
extends ./base
|
extends ./base
|
||||||
|
|
||||||
block vars
|
block vars
|
||||||
- const title = channel.name;
|
- const title = privateMode ? '非公開インスタンス' : channel.name;
|
||||||
- const url = `${config.url}/channels/${channel.id}`;
|
- const url = `${config.url}/channels/${channel.id}`;
|
||||||
|
|
||||||
block title
|
block title
|
||||||
= `${title} | ${instanceName}`
|
= `${title} | ${instanceName}`
|
||||||
|
|
||||||
block desc
|
block desc
|
||||||
meta(name='description' content= channel.description)
|
unless privateMode
|
||||||
|
meta(name='description' content=channel.description)
|
||||||
|
|
||||||
block og
|
block og
|
||||||
meta(property='og:type' content='article')
|
unless privateMode
|
||||||
meta(property='og:title' content= title)
|
meta(property='og:type' content='article')
|
||||||
meta(property='og:description' content= channel.description)
|
meta(property='og:title' content= title)
|
||||||
meta(property='og:url' content= url)
|
meta(property='og:description' content= channel.description)
|
||||||
meta(property='og:image' content= channel.bannerUrl)
|
meta(property='og:url' content= url)
|
||||||
|
meta(property='og:image' content= channel.bannerUrl)
|
||||||
|
|
|
@ -2,30 +2,33 @@ extends ./base
|
||||||
|
|
||||||
block vars
|
block vars
|
||||||
- const user = clip.user;
|
- const user = clip.user;
|
||||||
- const title = clip.name;
|
- const title = privateMode ? '非公開インスタンス' : clip.name;
|
||||||
- const url = `${config.url}/clips/${clip.id}`;
|
- const url = `${config.url}/clips/${clip.id}`;
|
||||||
|
|
||||||
block title
|
block title
|
||||||
= `${title} | ${instanceName}`
|
= `${title} | ${instanceName}`
|
||||||
|
|
||||||
block desc
|
block desc
|
||||||
meta(name='description' content= clip.description)
|
unless privateMode
|
||||||
|
meta(name='description' content= clip.description)
|
||||||
|
|
||||||
block og
|
block og
|
||||||
meta(property='og:type' content='article')
|
unless privateMode
|
||||||
meta(property='og:title' content= title)
|
meta(property='og:type' content='article')
|
||||||
meta(property='og:description' content= clip.description)
|
meta(property='og:title' content= title)
|
||||||
meta(property='og:url' content= url)
|
meta(property='og:description' content= clip.description)
|
||||||
meta(property='og:image' content= avatarUrl)
|
meta(property='og:url' content= url)
|
||||||
|
meta(property='og:image' content= avatarUrl)
|
||||||
|
|
||||||
block meta
|
block meta
|
||||||
if profile.noCrawle
|
unless privateMode
|
||||||
meta(name='robots' content='noindex')
|
if profile.noCrawle
|
||||||
|
meta(name='robots' content='noindex')
|
||||||
|
|
||||||
meta(name='misskey:user-username' content=user.username)
|
meta(name='misskey:user-username' content=user.username)
|
||||||
meta(name='misskey:user-id' content=user.id)
|
meta(name='misskey:user-id' content=user.id)
|
||||||
meta(name='misskey:clip-id' content=clip.id)
|
meta(name='misskey:clip-id' content=clip.id)
|
||||||
|
|
||||||
// todo
|
// todo
|
||||||
if user.twitter
|
if user.twitter
|
||||||
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)
|
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)
|
||||||
|
|
|
@ -2,32 +2,35 @@ extends ./base
|
||||||
|
|
||||||
block vars
|
block vars
|
||||||
- const user = post.user;
|
- const user = post.user;
|
||||||
- const title = post.title;
|
- const title = privateMode ? '非公開インスタンス' : post.title;
|
||||||
- const url = `${config.url}/gallery/${post.id}`;
|
- const url = `${config.url}/gallery/${post.id}`;
|
||||||
|
|
||||||
block title
|
block title
|
||||||
= `${title} | ${instanceName}`
|
= `${title} | ${instanceName}`
|
||||||
|
|
||||||
block desc
|
block desc
|
||||||
meta(name='description' content= post.description)
|
unless privateMode
|
||||||
|
meta(name='description' content= post.description)
|
||||||
|
|
||||||
block og
|
block og
|
||||||
meta(property='og:type' content='article')
|
unless privateMode
|
||||||
meta(property='og:title' content= title)
|
meta(property='og:type' content='article')
|
||||||
meta(property='og:description' content= post.description)
|
meta(property='og:title' content= title)
|
||||||
meta(property='og:url' content= url)
|
meta(property='og:description' content= post.description)
|
||||||
meta(property='og:image' content= post.files[0].thumbnailUrl)
|
meta(property='og:url' content= url)
|
||||||
|
meta(property='og:image' content= post.files[0].thumbnailUrl)
|
||||||
|
|
||||||
block meta
|
block meta
|
||||||
if user.host || profile.noCrawle
|
unless privateMode
|
||||||
meta(name='robots' content='noindex')
|
if user.host || profile.noCrawle
|
||||||
|
meta(name='robots' content='noindex')
|
||||||
|
|
||||||
meta(name='misskey:user-username' content=user.username)
|
meta(name='misskey:user-username' content=user.username)
|
||||||
meta(name='misskey:user-id' content=user.id)
|
meta(name='misskey:user-id' content=user.id)
|
||||||
|
|
||||||
// todo
|
// todo
|
||||||
if user.twitter
|
if user.twitter
|
||||||
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)
|
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)
|
||||||
|
|
||||||
if !user.host
|
if !user.host
|
||||||
link(rel='alternate' href=url type='application/activity+json')
|
link(rel='alternate' href=url type='application/activity+json')
|
||||||
|
|
|
@ -2,7 +2,7 @@ extends ./base
|
||||||
|
|
||||||
block vars
|
block vars
|
||||||
- const user = note.user;
|
- const user = note.user;
|
||||||
- const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`;
|
- const title = privateMode ? '非公開インスタンス' : (user.name ? `${user.name} (@${user.username})` : `@${user.username}`);
|
||||||
- const url = `${config.url}/notes/${note.id}`;
|
- const url = `${config.url}/notes/${note.id}`;
|
||||||
- const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null;
|
- const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null;
|
||||||
|
|
||||||
|
@ -10,33 +10,36 @@ block title
|
||||||
= `${title} | ${instanceName}`
|
= `${title} | ${instanceName}`
|
||||||
|
|
||||||
block desc
|
block desc
|
||||||
meta(name='description' content= summary)
|
unless privateMode
|
||||||
|
meta(name='description' content= summary)
|
||||||
|
|
||||||
block og
|
block og
|
||||||
meta(property='og:type' content='article')
|
unless privateMode
|
||||||
meta(property='og:title' content= title)
|
meta(property='og:type' content='article')
|
||||||
meta(property='og:description' content= summary)
|
meta(property='og:title' content= title)
|
||||||
meta(property='og:url' content= url)
|
meta(property='og:description' content= summary)
|
||||||
meta(property='og:image' content= avatarUrl)
|
meta(property='og:url' content= url)
|
||||||
|
meta(property='og:image' content= avatarUrl)
|
||||||
|
|
||||||
block meta
|
block meta
|
||||||
if user.host || isRenote || profile.noCrawle
|
unless privateMode
|
||||||
meta(name='robots' content='noindex')
|
if user.host || isRenote || profile.noCrawle
|
||||||
|
meta(name='robots' content='noindex')
|
||||||
|
|
||||||
meta(name='misskey:user-username' content=user.username)
|
meta(name='misskey:user-username' content=user.username)
|
||||||
meta(name='misskey:user-id' content=user.id)
|
meta(name='misskey:user-id' content=user.id)
|
||||||
meta(name='misskey:note-id' content=note.id)
|
meta(name='misskey:note-id' content=note.id)
|
||||||
|
|
||||||
// todo
|
// todo
|
||||||
if user.twitter
|
if user.twitter
|
||||||
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)
|
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)
|
||||||
|
|
||||||
if note.prev
|
if note.prev
|
||||||
link(rel='prev' href=`${config.url}/notes/${note.prev}`)
|
link(rel='prev' href=`${config.url}/notes/${note.prev}`)
|
||||||
if note.next
|
if note.next
|
||||||
link(rel='next' href=`${config.url}/notes/${note.next}`)
|
link(rel='next' href=`${config.url}/notes/${note.next}`)
|
||||||
|
|
||||||
if !user.host
|
if !user.host
|
||||||
link(rel='alternate' href=url type='application/activity+json')
|
link(rel='alternate' href=url type='application/activity+json')
|
||||||
if note.uri
|
if note.uri
|
||||||
link(rel='alternate' href=note.uri type='application/activity+json')
|
link(rel='alternate' href=note.uri type='application/activity+json')
|
||||||
|
|
|
@ -2,30 +2,33 @@ extends ./base
|
||||||
|
|
||||||
block vars
|
block vars
|
||||||
- const user = page.user;
|
- const user = page.user;
|
||||||
- const title = page.title;
|
- const title = privateMode ? '非公開インスタンス' : page.title;
|
||||||
- const url = `${config.url}/@${user.username}/${page.name}`;
|
- const url = `${config.url}/@${user.username}/${page.name}`;
|
||||||
|
|
||||||
block title
|
block title
|
||||||
= `${title} | ${instanceName}`
|
= `${title} | ${instanceName}`
|
||||||
|
|
||||||
block desc
|
block desc
|
||||||
meta(name='description' content= page.summary)
|
unless privateMode
|
||||||
|
meta(name='description' content= page.summary)
|
||||||
|
|
||||||
block og
|
block og
|
||||||
meta(property='og:type' content='article')
|
unless privateMode
|
||||||
meta(property='og:title' content= title)
|
meta(property='og:type' content='article')
|
||||||
meta(property='og:description' content= page.summary)
|
meta(property='og:title' content= title)
|
||||||
meta(property='og:url' content= url)
|
meta(property='og:description' content= page.summary)
|
||||||
meta(property='og:image' content= page.eyeCatchingImage ? page.eyeCatchingImage.thumbnailUrl : avatarUrl)
|
meta(property='og:url' content= url)
|
||||||
|
meta(property='og:image' content= page.eyeCatchingImage ? page.eyeCatchingImage.thumbnailUrl : avatarUrl)
|
||||||
|
|
||||||
block meta
|
block meta
|
||||||
if profile.noCrawle
|
unless privateMode
|
||||||
meta(name='robots' content='noindex')
|
if profile.noCrawle
|
||||||
|
meta(name='robots' content='noindex')
|
||||||
|
|
||||||
meta(name='misskey:user-username' content=user.username)
|
meta(name='misskey:user-username' content=user.username)
|
||||||
meta(name='misskey:user-id' content=user.id)
|
meta(name='misskey:user-id' content=user.id)
|
||||||
meta(name='misskey:page-id' content=page.id)
|
meta(name='misskey:page-id' content=page.id)
|
||||||
|
|
||||||
// todo
|
// todo
|
||||||
if user.twitter
|
if user.twitter
|
||||||
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)
|
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)
|
||||||
|
|
|
@ -1,39 +1,42 @@
|
||||||
extends ./base
|
extends ./base
|
||||||
|
|
||||||
block vars
|
block vars
|
||||||
- const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`;
|
- const title = privateMode ? '非公開インスタンス' : (user.name ? `${user.name} (@${user.username})` : `@${user.username}`);
|
||||||
- const url = `${config.url}/@${(user.host ? `${user.username}@${user.host}` : user.username)}`;
|
- const url = `${config.url}/@${(user.host ? `${user.username}@${user.host}` : user.username)}`;
|
||||||
|
|
||||||
block title
|
block title
|
||||||
= `${title} | ${instanceName}`
|
= `${title} | ${instanceName}`
|
||||||
|
|
||||||
block desc
|
block desc
|
||||||
meta(name='description' content= profile.description)
|
unless privateMode
|
||||||
|
meta(name='description' content= profile.description)
|
||||||
|
|
||||||
block og
|
block og
|
||||||
meta(property='og:type' content='blog')
|
unless privateMode
|
||||||
meta(property='og:title' content= title)
|
meta(property='og:type' content='blog')
|
||||||
meta(property='og:description' content= profile.description)
|
meta(property='og:title' content= title)
|
||||||
meta(property='og:url' content= url)
|
meta(property='og:description' content= profile.description)
|
||||||
meta(property='og:image' content= avatarUrl)
|
meta(property='og:url' content= url)
|
||||||
|
meta(property='og:image' content= avatarUrl)
|
||||||
|
|
||||||
block meta
|
block meta
|
||||||
if user.host || profile.noCrawle
|
unless privateMode
|
||||||
meta(name='robots' content='noindex')
|
if user.host || profile.noCrawle
|
||||||
|
meta(name='robots' content='noindex')
|
||||||
|
|
||||||
meta(name='misskey:user-username' content=user.username)
|
meta(name='misskey:user-username' content=user.username)
|
||||||
meta(name='misskey:user-id' content=user.id)
|
meta(name='misskey:user-id' content=user.id)
|
||||||
|
|
||||||
if profile.twitter
|
if profile.twitter
|
||||||
meta(name='twitter:creator' content=`@${profile.twitter.screenName}`)
|
meta(name='twitter:creator' content=`@${profile.twitter.screenName}`)
|
||||||
|
|
||||||
if !sub
|
if !sub
|
||||||
if !user.host
|
if !user.host
|
||||||
link(rel='alternate' href=`${config.url}/users/${user.id}` type='application/activity+json')
|
link(rel='alternate' href=`${config.url}/users/${user.id}` type='application/activity+json')
|
||||||
if user.uri
|
if user.uri
|
||||||
link(rel='alternate' href=user.uri type='application/activity+json')
|
link(rel='alternate' href=user.uri type='application/activity+json')
|
||||||
if profile.url
|
if profile.url
|
||||||
link(rel='alternate' href=profile.url type='text/html')
|
link(rel='alternate' href=profile.url type='text/html')
|
||||||
|
|
||||||
each m in me
|
each m in me
|
||||||
link(rel='me' href=`${m}`)
|
link(rel='me' href=`${m}`)
|
||||||
|
|
|
@ -94,6 +94,26 @@
|
||||||
<FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ i18n.ts.save }}</FormButton>
|
<FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ i18n.ts.save }}</FormButton>
|
||||||
</div>
|
</div>
|
||||||
</FormFolder>
|
</FormFolder>
|
||||||
|
|
||||||
|
<FormFolder class="_formBlock">
|
||||||
|
<template #label>{{ i18n.ts.instanceSecurity }}</template>
|
||||||
|
|
||||||
|
<div class="_formRoot">
|
||||||
|
<FormSwitch v-if="!privateMode" v-model="secureMode">
|
||||||
|
<template #label>{{ i18n.ts.secureMode }}</template>
|
||||||
|
<template #caption>{{ i18n.ts.secureModeInfo }}</template>
|
||||||
|
</FormSwitch>
|
||||||
|
<FormSwitch v-model="privateMode">
|
||||||
|
<template #label>{{ i18n.ts.privateMode }}</template>
|
||||||
|
<template #caption>{{ i18n.ts.privateModeInfo }}</template>
|
||||||
|
</FormSwitch>
|
||||||
|
<FormTextarea v-if="privateMode" v-model="allowedHosts">
|
||||||
|
<template #label>{{ i18n.ts.allowedInstances }}</template>
|
||||||
|
<template #caption>{{ i18n.ts.allowedInstancesDescription }}</template>
|
||||||
|
</FormTextarea>
|
||||||
|
<FormButton primary class="_formBlock" @click="saveInstance"><i class="fas fa-save"></i> {{ i18n.ts.save }}</FormButton>
|
||||||
|
</div>
|
||||||
|
</FormFolder>
|
||||||
</div>
|
</div>
|
||||||
</FormSuspense>
|
</FormSuspense>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
|
@ -112,6 +132,7 @@ import FormSuspense from '@/components/form/suspense.vue';
|
||||||
import FormRange from '@/components/form/range.vue';
|
import FormRange from '@/components/form/range.vue';
|
||||||
import FormInput from '@/components/form/input.vue';
|
import FormInput from '@/components/form/input.vue';
|
||||||
import FormButton from '@/components/ui/button.vue';
|
import FormButton from '@/components/ui/button.vue';
|
||||||
|
import FormTextarea from '@/components/form/textarea.vue';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
import { fetchInstance } from '@/instance';
|
import { fetchInstance } from '@/instance';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
|
@ -127,6 +148,10 @@ let enableSensitiveMediaDetectionForVideos: boolean = $ref(false);
|
||||||
let enableIpLogging: boolean = $ref(false);
|
let enableIpLogging: boolean = $ref(false);
|
||||||
let enableActiveEmailValidation: boolean = $ref(false);
|
let enableActiveEmailValidation: boolean = $ref(false);
|
||||||
|
|
||||||
|
let secureMode: boolean = $ref(false);
|
||||||
|
let privateMode: boolean = $ref(false);
|
||||||
|
let allowedHosts: string = $ref('');
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
const meta = await os.api('admin/meta');
|
const meta = await os.api('admin/meta');
|
||||||
summalyProxy = meta.summalyProxy;
|
summalyProxy = meta.summalyProxy;
|
||||||
|
@ -143,6 +168,10 @@ async function init() {
|
||||||
enableSensitiveMediaDetectionForVideos = meta.enableSensitiveMediaDetectionForVideos;
|
enableSensitiveMediaDetectionForVideos = meta.enableSensitiveMediaDetectionForVideos;
|
||||||
enableIpLogging = meta.enableIpLogging;
|
enableIpLogging = meta.enableIpLogging;
|
||||||
enableActiveEmailValidation = meta.enableActiveEmailValidation;
|
enableActiveEmailValidation = meta.enableActiveEmailValidation;
|
||||||
|
|
||||||
|
secureMode = meta.secureMode;
|
||||||
|
privateMode = meta.privateMode;
|
||||||
|
allowedHosts = meta.allowedHosts.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
|
@ -165,6 +194,16 @@ function save() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function saveInstance() {
|
||||||
|
os.apiWithDialog('admin/update-meta', {
|
||||||
|
secureMode,
|
||||||
|
privateMode,
|
||||||
|
allowedHosts: allowedHosts.split('\n'),
|
||||||
|
}).then(() => {
|
||||||
|
fetchInstance();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const headerActions = $computed(() => []);
|
const headerActions = $computed(() => []);
|
||||||
|
|
||||||
const headerTabs = $computed(() => []);
|
const headerTabs = $computed(() => []);
|
||||||
|
|
Loading…
Reference in a new issue