forked from FoundKeyGang/FoundKey
Compare commits
19 commits
43253171f4
...
3eeee88973
Author | SHA1 | Date | |
---|---|---|---|
3eeee88973 | |||
a74c1d9126 | |||
811d5cd0d7 | |||
d762143b89 | |||
21c1e5c06c | |||
91a4f38871 | |||
756ecbb1f7 | |||
b431471fd1 | |||
6edf5928e9 | |||
8aee4bb4d8 | |||
86355f948c | |||
a0525cb8ec | |||
fdbc72130f | |||
e465ea8103 | |||
3f438bcdab | |||
7cd11e7afd | |||
0b8fa2665c | |||
421b42d07d | |||
8920eeb86a |
19 changed files with 242 additions and 104 deletions
89
CHANGELOG.md
89
CHANGELOG.md
|
@ -11,37 +11,84 @@ Unreleased changes should not be listed in this file.
|
|||
Instead, run `git shortlog --format='%h %s' --group=trailer:changelog <last tag>..` to see unreleased changes; replace `<last tag>` with the tag you wish to compare from.
|
||||
If you are a contributor, please read [CONTRIBUTING.md, section "Changelog Trailer"](./CONTRIBUTING.md#changelog-trailer) on what to do instead.
|
||||
|
||||
## Unreleased
|
||||
## 13.0.0-preview2 - 2022-10-16
|
||||
### Security
|
||||
- server: Update `multer` dependency to resolve [CVE-2022-24434](https://nvd.nist.gov/vuln/detail/CVE-2022-24434)
|
||||
- server: Update `file-type`, `got`, and `sharp` dependencies to fix various security issues
|
||||
|
||||
### Added
|
||||
- Client: Show instance info in ticker
|
||||
- Client: Readded group pages
|
||||
- Client: add re-collapsing to quoted notes
|
||||
- allow to mute only renotes of a user
|
||||
- allow to export only selected custom emoji
|
||||
- client: improve emoji picker search
|
||||
- client: Extend Emoji list
|
||||
- client: show alt text in image viewer
|
||||
- client: Show instance info in ticker
|
||||
- client: Readded group pages
|
||||
- client: add re-collapsing to quoted notes
|
||||
- server: allow files storage path to be set explicitly
|
||||
- server: refactor expiring data and expire signins after 60 days
|
||||
- server: send delete activity to all known instances
|
||||
- server: add automatic dead instance detection
|
||||
|
||||
### Changed
|
||||
- Client: Use consistent date formatting based on language setting
|
||||
- Client: Add threshold to reduce occurances of "future" timestamps
|
||||
- Pages have been considerably simplified, several of the very complex features have been removed.
|
||||
- foundkey-js: Sync possible endpoints from backend
|
||||
- foundkey-js: update LiteInstanceMetadata fields
|
||||
- meta: use parallel and incremental builds
|
||||
- meta: update WORKDIR to foundkey
|
||||
- meta: update dependencies
|
||||
- client: consolidate about & notifications pages
|
||||
- client: include renote in visibility computation
|
||||
- client: make emoji amount slider more intuitive
|
||||
- client: sort emojis by query similarity in fuzzy picker
|
||||
- client: discard drafts that are just the default state
|
||||
- client: Use consistent date formatting based on language setting
|
||||
- client: Add threshold to reduce occurances of "future" timestamps
|
||||
- server: mute notifications in muted threads
|
||||
- server: allow for source lang to be overridden in note/translate
|
||||
- server: allow redis family to be specified as a string
|
||||
- server: increase image description limit to 2048 characters
|
||||
- server: Pages have been considerably simplified, several of the very complex features have been removed.
|
||||
Pages are now MFM only.
|
||||
**For admins:** There is a migration in place to convert page contents to text, but not everything can be migrated.
|
||||
You might want to check if you have any more complex pages on your instance and ask users to migrate them by hand.
|
||||
Or generally advise all users to simplify their pages to only text.
|
||||
|
||||
### Removed
|
||||
- Okteto config and Helm chart
|
||||
- Client: acrylic styling
|
||||
- Client: Twitter embeds, the standard URL preview is used instead.
|
||||
- Promotion entities and endpoints
|
||||
- Server: The configuration item `signToActivityPubGet` has been removed and will be ignored if set explicitly.
|
||||
Foundkey will now work as if it was set to `true`.
|
||||
|
||||
### Fixed
|
||||
- Client: Notifications for ended polls can now be turned off
|
||||
- Client: Emoji picker should load faster now
|
||||
- Server: Blocking remote accounts
|
||||
- client: alt text dialog properly handles non-images
|
||||
- client: Fix style scoping in MkMention
|
||||
- client: default instance ticker name to instance's domain name
|
||||
- client: improve error message for empty gallery posts
|
||||
- client: fix default-selected reply scopes
|
||||
- client: Make MFM cheatsheet interactive again
|
||||
- client: Fix reports not showing in control panel
|
||||
- client: make hard coded strings in emoji admin panel internationalized
|
||||
- client: Notifications for ended polls can now be turned off
|
||||
- client: improve emoji picker performance
|
||||
- server: Blocking remote accounts
|
||||
- server: fix table name used in toHtml
|
||||
- server: Fix appendChildren TypeError
|
||||
- server: ensure only own notifications can be marked as read
|
||||
- server: render HTML mentions correctly
|
||||
- server: increase requestId max size for GNU Social
|
||||
- server: fix HTTP GET parameters in OpenAPI docs
|
||||
- server: proper error messages for creating accounts
|
||||
- server: Fix thread muting queries
|
||||
- docker: add built foundkey-js files to container
|
||||
- service worker: Remove fetch handler from service worker
|
||||
|
||||
### Security
|
||||
- Server: Update `multer` dependency to resolve [CVE-2022-24434](https://nvd.nist.gov/vuln/detail/CVE-2022-24434)
|
||||
- Server: Update `file-type`, `got`, and `sharp` dependencies to fix various security issues
|
||||
### Removed
|
||||
- remove misskey-assets submodule
|
||||
- server: remove room data from user
|
||||
- client: remove ai mode
|
||||
- client: remove "Disable AiScript on Pages" setting
|
||||
- client: acrylic styling
|
||||
- client: Twitter embeds, the standard URL preview is used instead.
|
||||
- foundkey-js: remove room api endpoints
|
||||
- server: remove unusable setting to send error reports
|
||||
- server: ignore detail parameter on meta endpoint
|
||||
- server: Promotion entities and endpoints
|
||||
- server: The configuration item `signToActivityPubGet` has been removed and will be ignored if set explicitly.
|
||||
Foundkey will now work as if it was set to `true`.
|
||||
|
||||
## 13.0.0-preview1 - 2022-08-05
|
||||
### Added
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# Reporting Security Issues
|
||||
|
||||
If you discover a security issue in Misskey, please report it by sending an
|
||||
email to [syuilotan@yahoo.co.jp](mailto:syuilotan@yahoo.co.jp).
|
||||
If you discover a security issue in Foundkey, please report it by sending an
|
||||
email to [johann@qwertqwefsday.eu](mailto:johann@qwertqwefsday.eu).
|
||||
|
||||
This will allow us to assess the risk, and make a fix available before we add a
|
||||
bug report to the GitHub repository.
|
||||
bug report to the repository.
|
||||
|
||||
Thanks for helping make Misskey safe for everyone.
|
||||
Thanks for helping make Foundkey safe for everyone.
|
||||
|
|
|
@ -825,6 +825,13 @@ middle: "Medium"
|
|||
low: "Low"
|
||||
emailNotConfiguredWarning: "Email address not set."
|
||||
ratio: "Ratio"
|
||||
secureMode: "Secure Mode (Authorized Fetch)"
|
||||
instanceSecurity: "Instance Security"
|
||||
secureModeInfo: "Requests from other instances must be signed, otherwise notes won't be returned. signToActivityPubGet must be set to true in the other instance's configuration file."
|
||||
privateMode: "Private Mode"
|
||||
privateModeInfo: "When enabled, only authorized instances may fetch notes. Hides all notes from public."
|
||||
allowedInstances: "Allowed Instances"
|
||||
allowedInstancesDescription: "Set the hosts of the instances you want to allow, separated by line. Valid in private mode only."
|
||||
previewNoteText: "Show preview"
|
||||
customCss: "Custom CSS"
|
||||
customCssWarn: "This setting should only be used if you know what it does. Entering\
|
||||
|
|
|
@ -12,19 +12,22 @@ import { Cache } from '@/misc/cache.js';
|
|||
import { Instance } from '@/models/entities/instance.js';
|
||||
import { StatusError } from '@/misc/fetch.js';
|
||||
import { DeliverJobData } from '@/queue/types.js';
|
||||
import { LessThan } from 'typeorm';
|
||||
import { DAY } from '@/const.js';
|
||||
|
||||
const logger = new Logger('deliver');
|
||||
|
||||
let latest: string | null = null;
|
||||
|
||||
const suspendedHostsCache = new Cache<Instance[]>(1000 * 60 * 60);
|
||||
const deadThreshold = 30 * DAY;
|
||||
|
||||
export default async (job: Bull.Job<DeliverJobData>) => {
|
||||
const { host } = new URL(job.data.to);
|
||||
const puny = toPuny(host);
|
||||
|
||||
// ブロックしてたら中断
|
||||
const meta = await fetchMeta();
|
||||
if (meta.blockedHosts.includes(toPuny(host))) {
|
||||
if (meta.blockedHosts.includes(puny)) {
|
||||
return 'skip (blocked)';
|
||||
}
|
||||
|
||||
|
@ -32,18 +35,13 @@ export default async (job: Bull.Job<DeliverJobData>) => {
|
|||
return 'skip (not allowed)';
|
||||
}
|
||||
|
||||
// isSuspendedなら中断
|
||||
let suspendedHosts = suspendedHostsCache.get(null);
|
||||
if (suspendedHosts == null) {
|
||||
suspendedHosts = await Instances.find({
|
||||
where: {
|
||||
isSuspended: true,
|
||||
},
|
||||
});
|
||||
suspendedHostsCache.set(null, suspendedHosts);
|
||||
}
|
||||
if (suspendedHosts.map(x => x.host).includes(toPuny(host))) {
|
||||
return 'skip (suspended)';
|
||||
const deadTime = new Date(Date.now() - deadThreshold);
|
||||
const isSuspendedOrDead = await Instances.countBy([
|
||||
{ host: puny, isSuspended: true },
|
||||
{ host: puny, lastCommunicatedAt: LessThan(deadTime) },
|
||||
]);
|
||||
if (isSuspendedOrDead) {
|
||||
return 'skip (suspended or dead)';
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -39,7 +39,7 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
|
|||
return `Blocked request: ${host}`;
|
||||
}
|
||||
|
||||
// 非公開モードなら許可なインスタンスのみ
|
||||
// Only permitted instances if in private mode.
|
||||
if (meta.privateMode && !meta.allowedHosts.includes(host)) {
|
||||
return `Blocked request: ${host}`;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ 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) {
|
||||
|
@ -38,31 +37,28 @@ export default async function checkFetch(req: IncomingMessage): Promise<number>
|
|||
|
||||
const dbResolver = new DbResolver();
|
||||
|
||||
// HTTP-Signature keyIdを元にDBから取得
|
||||
// Get user from database based on HTTP-Signature keyId
|
||||
let authUser = await dbResolver.getAuthUserFromKeyId(signature.keyId);
|
||||
|
||||
// keyIdでわからなければ、resolveしてみる
|
||||
// If keyid is unknown, try resolving it
|
||||
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の検証
|
||||
// HTTP-Signature validation
|
||||
const httpSignatureValidated = httpSignature.verifySignature(signature, authUser.key.keyPem);
|
||||
|
||||
if (!httpSignatureValidated) {
|
||||
|
|
|
@ -8,6 +8,10 @@ interface IRecipe {
|
|||
type: string;
|
||||
}
|
||||
|
||||
interface IEveryoneRecipe extends IRecipe {
|
||||
type: 'Everyone';
|
||||
}
|
||||
|
||||
interface IFollowersRecipe extends IRecipe {
|
||||
type: 'Followers';
|
||||
}
|
||||
|
@ -17,6 +21,9 @@ interface IDirectRecipe extends IRecipe {
|
|||
to: IRemoteUser;
|
||||
}
|
||||
|
||||
const isEveryone = (recipe: any): recipe is IEveryoneRecipe =>
|
||||
recipe.type === 'Everyone';
|
||||
|
||||
const isFollowers = (recipe: any): recipe is IFollowersRecipe =>
|
||||
recipe.type === 'Followers';
|
||||
|
||||
|
@ -63,6 +70,13 @@ export default class DeliverManager {
|
|||
this.addRecipe(recipe);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add recipe to send this activity to all known sharedInboxes
|
||||
*/
|
||||
public addEveryone() {
|
||||
this.addRecipe({ type: 'Everyone' } as IEveryoneRecipe);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add recipe
|
||||
* @param recipe Recipe
|
||||
|
@ -82,9 +96,26 @@ export default class DeliverManager {
|
|||
/*
|
||||
build inbox list
|
||||
|
||||
Process follower recipes first to avoid duplication when processing
|
||||
direct recipes later.
|
||||
Processing order matters to avoid duplication.
|
||||
*/
|
||||
|
||||
if (this.recipes.some(r => isEveryone(r))) {
|
||||
// deliver to all of known network
|
||||
const sharedInboxes = await Users.createQueryBuilder('users')
|
||||
.select('users.sharedInbox', 'sharedInbox')
|
||||
// so we don't have to make our inboxes Set work as hard
|
||||
.distinct(true)
|
||||
// can't deliver to unknown shared inbox
|
||||
.where('users.sharedInbox IS NOT NULL')
|
||||
// don't deliver to ourselves
|
||||
.andWhere('users.host IS NOT NULL')
|
||||
.getRawMany();
|
||||
|
||||
for (const inbox of sharedInboxes) {
|
||||
inboxes.add(inbox.sharedInbox);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.recipes.some(r => isFollowers(r))) {
|
||||
// followers deliver
|
||||
// TODO: SELECT DISTINCT ON ("followerSharedInbox") "followerSharedInbox" みたいな問い合わせにすればよりパフォーマンス向上できそう
|
||||
|
|
|
@ -76,7 +76,7 @@ export default class Resolver {
|
|||
throw new Error('Instance is not allowed');
|
||||
}
|
||||
|
||||
if (!this.user) {
|
||||
if (config.signToActivityPubGet && !this.user) {
|
||||
this.user = await getInstanceActor();
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ router.get('/notes/:note', async (ctx, next) => {
|
|||
if (!isActivityPubReq(ctx)) return await next();
|
||||
|
||||
const verify = await checkFetch(ctx.req);
|
||||
if (verify != 200) {
|
||||
if (verify !== 200) {
|
||||
ctx.status = verify;
|
||||
return;
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ router.get('/notes/:note', async (ctx, next) => {
|
|||
}
|
||||
|
||||
// リモートだったらリダイレクト
|
||||
if (note.userHost != null) {
|
||||
if (note.userHost !== null) {
|
||||
if (note.uri == null || isSelfHost(note.userHost)) {
|
||||
ctx.status = 500;
|
||||
return;
|
||||
|
@ -100,7 +100,7 @@ router.get('/notes/:note', async (ctx, next) => {
|
|||
|
||||
const meta = await fetchMeta();
|
||||
if (meta.secureMode || meta.privateMode) {
|
||||
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||
ctx.set('Cache-Control', 'no-store');
|
||||
} else {
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ router.get('/notes/:note', async (ctx, next) => {
|
|||
// note activity
|
||||
router.get('/notes/:note/activity', async ctx => {
|
||||
const verify = await checkFetch(ctx.req);
|
||||
if (verify != 200) {
|
||||
if (verify !== 200) {
|
||||
ctx.status = verify;
|
||||
return;
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ router.get('/notes/:note/activity', async ctx => {
|
|||
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');
|
||||
ctx.set('Cache-Control', 'no-store');
|
||||
} else {
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ router.get('/users/:user/publickey', async ctx => {
|
|||
}
|
||||
|
||||
const verify = await checkFetch(ctx.req);
|
||||
if (verify != 200) {
|
||||
if (verify !== 200) {
|
||||
ctx.status = verify;
|
||||
return;
|
||||
}
|
||||
|
@ -183,7 +183,7 @@ router.get('/users/:user/publickey', async ctx => {
|
|||
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');
|
||||
ctx.set('Cache-Control', 'no-store');
|
||||
} else {
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
}
|
||||
|
@ -203,7 +203,7 @@ async function userInfo(ctx: Router.RouterContext, user: User | null) {
|
|||
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');
|
||||
ctx.set('Cache-Control', 'no-store');
|
||||
} else {
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
}
|
||||
|
@ -220,7 +220,7 @@ router.get('/users/:user', async (ctx, next) => {
|
|||
}
|
||||
|
||||
const verify = await checkFetch(ctx.req);
|
||||
if (verify != 200) {
|
||||
if (verify !== 200) {
|
||||
ctx.status = verify;
|
||||
return;
|
||||
}
|
||||
|
@ -246,7 +246,7 @@ router.get('/@:user', async (ctx, next) => {
|
|||
}
|
||||
|
||||
const verify = await checkFetch(ctx.req);
|
||||
if (verify != 200) {
|
||||
if (verify !== 200) {
|
||||
ctx.status = verify;
|
||||
return;
|
||||
}
|
||||
|
@ -287,7 +287,7 @@ router.get('/emojis/:emoji', async ctx => {
|
|||
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');
|
||||
ctx.set('Cache-Control', 'no-store');
|
||||
} else {
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
}
|
||||
|
@ -297,7 +297,7 @@ router.get('/emojis/:emoji', async ctx => {
|
|||
// like
|
||||
router.get('/likes/:like', async ctx => {
|
||||
const verify = await checkFetch(ctx.req);
|
||||
if (verify != 200) {
|
||||
if (verify !== 200) {
|
||||
ctx.status = verify;
|
||||
return;
|
||||
}
|
||||
|
@ -322,7 +322,7 @@ router.get('/likes/:like', async ctx => {
|
|||
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');
|
||||
ctx.set('Cache-Control', 'no-store');
|
||||
} else {
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
}
|
||||
|
@ -332,7 +332,7 @@ router.get('/likes/:like', async ctx => {
|
|||
// follow
|
||||
router.get('/follows/:follower/:followee', async ctx => {
|
||||
const verify = await checkFetch(ctx.req);
|
||||
if (verify != 200) {
|
||||
if (verify !== 200) {
|
||||
ctx.status = verify;
|
||||
return;
|
||||
}
|
||||
|
@ -358,7 +358,7 @@ router.get('/follows/:follower/:followee', async ctx => {
|
|||
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');
|
||||
ctx.set('Cache-Control', 'no-store');
|
||||
} else {
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
}
|
||||
|
|
|
@ -8,10 +8,13 @@ import { Users, Notes, UserNotePinings } from '@/models/index.js';
|
|||
import checkFetch from '@/remote/activitypub/check-fetch.js';
|
||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||
import { setResponseType } from '../activitypub.js';
|
||||
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) => {
|
||||
const verify = await checkFetch(ctx.req);
|
||||
if (verify != 200) {
|
||||
if (verify !== 200) {
|
||||
ctx.status = verify;
|
||||
return;
|
||||
}
|
||||
|
@ -47,7 +50,7 @@ export default async (ctx: Router.RouterContext) => {
|
|||
|
||||
const meta = await fetchMeta();
|
||||
if (meta.secureMode || meta.privateMode) {
|
||||
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||
ctx.set('Cache-Control', 'no-store');
|
||||
} else {
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import { fetchMeta } from '@/misc/fetch-meta.js';
|
|||
|
||||
export default async (ctx: Router.RouterContext) => {
|
||||
const verify = await checkFetch(ctx.req);
|
||||
if (verify != 200) {
|
||||
if (verify !== 200) {
|
||||
ctx.status = verify;
|
||||
return;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ export default async (ctx: Router.RouterContext) => {
|
|||
const userId = ctx.params.user;
|
||||
|
||||
const cursor = ctx.request.query.cursor;
|
||||
if (cursor != null && typeof cursor !== 'string') {
|
||||
if (cursor !== null && typeof cursor !== 'string') {
|
||||
ctx.status = 400;
|
||||
return;
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ export default async (ctx: Router.RouterContext) => {
|
|||
}
|
||||
const meta = await fetchMeta();
|
||||
if (meta.secureMode || meta.privateMode) {
|
||||
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||
ctx.set('Cache-Control', 'no-store');
|
||||
} else {
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import { fetchMeta } from '@/misc/fetch-meta.js';
|
|||
|
||||
export default async (ctx: Router.RouterContext) => {
|
||||
const verify = await checkFetch(ctx.req);
|
||||
if (verify != 200) {
|
||||
if (verify !== 200) {
|
||||
ctx.status = verify;
|
||||
return;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ export default async (ctx: Router.RouterContext) => {
|
|||
const userId = ctx.params.user;
|
||||
|
||||
const cursor = ctx.request.query.cursor;
|
||||
if (cursor != null && typeof cursor !== 'string') {
|
||||
if (cursor !== null && typeof cursor !== 'string') {
|
||||
ctx.status = 400;
|
||||
return;
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ export default async (ctx: Router.RouterContext) => {
|
|||
}
|
||||
const meta = await fetchMeta();
|
||||
if (meta.secureMode || meta.privateMode) {
|
||||
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||
ctx.set('Cache-Control', 'no-store');
|
||||
} else {
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import { fetchMeta } from '@/misc/fetch-meta.js';
|
|||
|
||||
export default async (ctx: Router.RouterContext) => {
|
||||
const verify = await checkFetch(ctx.req);
|
||||
if (verify != 200) {
|
||||
if (verify !== 200) {
|
||||
ctx.status = verify;
|
||||
return;
|
||||
}
|
||||
|
@ -27,20 +27,20 @@ export default async (ctx: Router.RouterContext) => {
|
|||
const userId = ctx.params.user;
|
||||
|
||||
const sinceId = ctx.request.query.since_id;
|
||||
if (sinceId != null && typeof sinceId !== 'string') {
|
||||
if (sinceId !== null && typeof sinceId !== 'string') {
|
||||
ctx.status = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
const untilId = ctx.request.query.until_id;
|
||||
if (untilId != null && typeof untilId !== 'string') {
|
||||
if (untilId !== null && typeof untilId !== 'string') {
|
||||
ctx.status = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
const page = ctx.request.query.page === 'true';
|
||||
|
||||
if (countIf(x => x != null, [sinceId, untilId]) > 1) {
|
||||
if (countIf(x => x !== null, [sinceId, untilId]) > 1) {
|
||||
ctx.status = 400;
|
||||
return;
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ export default async (ctx: Router.RouterContext) => {
|
|||
}
|
||||
const meta = await fetchMeta();
|
||||
if (meta.secureMode || meta.privateMode) {
|
||||
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||
ctx.set('Cache-Control', 'no-store');
|
||||
} else {
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
}
|
||||
|
|
|
@ -693,8 +693,8 @@ export interface IEndpointMeta {
|
|||
readonly secure?: boolean;
|
||||
|
||||
/**
|
||||
* プライベートモードでなら、このエンドポイントにリクエストするときにユーザー情報が必要か否か
|
||||
* 省略した場合は false として解釈されます
|
||||
* If in private mode, whether credentials are required when making a request to this endpoint.
|
||||
* If omitted, this is interpreted as false.
|
||||
*/
|
||||
readonly requireCredentialPrivateMode?: boolean;
|
||||
|
||||
|
|
|
@ -139,10 +139,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
set.secureMode = ps.secureMode;
|
||||
}
|
||||
|
||||
if (ps.mascotImageUrl !== undefined) {
|
||||
set.mascotImageUrl = ps.mascotImageUrl;
|
||||
}
|
||||
|
||||
if (ps.bannerUrl !== undefined) {
|
||||
set.bannerUrl = ps.bannerUrl;
|
||||
}
|
||||
|
|
|
@ -253,7 +253,7 @@ export const paramDef = {
|
|||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps, me) => {
|
||||
export default define(meta, paramDef, async (ps, me): Promise<Record<string, any>> => {
|
||||
const instance = await fetchMeta(true);
|
||||
|
||||
const emojis = await Emojis.find({
|
||||
|
@ -270,7 +270,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
},
|
||||
});
|
||||
|
||||
return {
|
||||
const response: Record<string, any> = {
|
||||
maintainerName: instance.maintainerName,
|
||||
maintainerEmail: instance.maintainerEmail,
|
||||
|
||||
|
@ -281,8 +281,10 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
description: instance.description,
|
||||
langs: instance.langs,
|
||||
tosUrl: instance.ToSUrl,
|
||||
|
||||
secureMode: instance.secureMode,
|
||||
privateMode: instance.privateMode,
|
||||
|
||||
disableRegistration: instance.disableRegistration,
|
||||
disableLocalTimeline: instance.disableLocalTimeline,
|
||||
disableGlobalTimeline: instance.disableGlobalTimeline,
|
||||
|
@ -309,17 +311,22 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
|
||||
translatorAvailable: instance.deeplAuthKey != null,
|
||||
|
||||
pinnedPages: instance.pinnedPages,
|
||||
pinnedClipId: instance.pinnedClipId,
|
||||
cacheRemoteFiles: instance.cacheRemoteFiles,
|
||||
requireSetup: (await Users.countBy({
|
||||
host: IsNull(),
|
||||
})) === 0,
|
||||
if (!instance.privateMode || me) {
|
||||
proxyAccountName: instance.proxyAccountId ? (await Users.pack(instance.proxyAccountId).catch(() => null))?.username : null,
|
||||
}
|
||||
...(ps.detail ? {
|
||||
pinnedPages: instance.privateMode && !me ? [] : instance.pinnedPages,
|
||||
pinnedClipId: instance.privateMode && !me ? [] : instance.pinnedClipId,
|
||||
cacheRemoteFiles: instance.cacheRemoteFiles,
|
||||
requireSetup: (await Users.countBy({
|
||||
host: IsNull(),
|
||||
})) === 0,
|
||||
} : {}),
|
||||
};
|
||||
|
||||
features: {
|
||||
if (ps.detail) {
|
||||
if (!instance.privateMode || me) {
|
||||
const proxyAccount = instance.proxyAccountId ? await Users.pack(instance.proxyAccountId).catch(() => null) : null;
|
||||
response.proxyAccountName = proxyAccount ? proxyAccount.username : null;
|
||||
}
|
||||
response.features = {
|
||||
registration: !instance.disableRegistration,
|
||||
localTimeLine: !instance.disableLocalTimeline,
|
||||
globalTimeLine: !instance.disableGlobalTimeline,
|
||||
|
@ -330,6 +337,8 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
objectStorage: instance.useObjectStorage,
|
||||
serviceWorker: instance.enableServiceWorker,
|
||||
miauth: true,
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
return response;
|
||||
});
|
||||
|
|
|
@ -24,6 +24,7 @@ import { getNoteSummary } from '@/misc/get-note-summary.js';
|
|||
import { queues } from '@/queue/queues.js';
|
||||
import { MINUTE, DAY } from '@/const.js';
|
||||
import { genOpenapiSpec } from '../api/openapi/gen-spec.js';
|
||||
import meta from '../api/endpoints/meta.js';
|
||||
import { urlPreviewHandler } from './url-preview.js';
|
||||
import { manifestHandler } from './manifest.js';
|
||||
import packFeed from './feed.js';
|
||||
|
@ -271,6 +272,12 @@ router.get('/@:user.json', async ctx => {
|
|||
//#region SSR (for crawlers)
|
||||
// User
|
||||
router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => {
|
||||
const meta = await fetchMeta();
|
||||
if (meta.privateMode) {
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
const { username, host } = Acct.parse(ctx.params.user);
|
||||
const user = await Users.findOneBy({
|
||||
usernameLower: username.toLowerCase(),
|
||||
|
@ -360,6 +367,12 @@ router.get('/notes/:note', async (ctx, next) => {
|
|||
|
||||
// Page
|
||||
router.get('/@:user/pages/:page', async (ctx, next) => {
|
||||
const meta = await fetchMeta();
|
||||
if (meta.privateMode) {
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
const { username, host } = Acct.parse(ctx.params.user);
|
||||
const user = await Users.findOneBy({
|
||||
usernameLower: username.toLowerCase(),
|
||||
|
@ -402,6 +415,12 @@ router.get('/@:user/pages/:page', async (ctx, next) => {
|
|||
// Clip
|
||||
// TODO: 非publicなclipのハンドリング
|
||||
router.get('/clips/:clip', async (ctx, next) => {
|
||||
const meta = await fetchMeta();
|
||||
if (meta.privateMode) {
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
const clip = await Clips.findOneBy({
|
||||
id: ctx.params.clip,
|
||||
});
|
||||
|
@ -430,6 +449,12 @@ router.get('/clips/:clip', async (ctx, next) => {
|
|||
|
||||
// Gallery post
|
||||
router.get('/gallery/:post', async (ctx, next) => {
|
||||
const meta = await fetchMeta();
|
||||
if (meta.privateMode) {
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
const post = await GalleryPosts.findOneBy({ id: ctx.params.post });
|
||||
|
||||
if (post) {
|
||||
|
@ -456,6 +481,12 @@ router.get('/gallery/:post', async (ctx, next) => {
|
|||
|
||||
// Channel
|
||||
router.get('/channels/:channel', async (ctx, next) => {
|
||||
const meta = await fetchMeta();
|
||||
if (meta.privateMode) {
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
const channel = await Channels.findOneBy({
|
||||
id: ctx.params.channel,
|
||||
});
|
||||
|
@ -526,6 +557,10 @@ router.get('/streaming', async ctx => {
|
|||
// Render base html for all requests
|
||||
router.get('(.*)', async ctx => {
|
||||
const meta = await fetchMeta();
|
||||
if (meta.privateMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.render('base', {
|
||||
img: meta.bannerUrl,
|
||||
title: meta.name || 'FoundKey',
|
||||
|
|
|
@ -10,7 +10,7 @@ import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js';
|
|||
import { Note } from '@/models/entities/note.js';
|
||||
import { Notes, Users, Instances } from '@/models/index.js';
|
||||
import { notesChart, perUserNotesChart, instanceChart } from '@/services/chart/index.js';
|
||||
import { deliverToFollowers, deliverToUser } from '@/remote/activitypub/deliver-manager.js';
|
||||
import DeliverManager from '@/remote/activitypub/deliver-manager.js';
|
||||
import { countSameRenotes } from '@/misc/count-same-renotes.js';
|
||||
import { isPureRenote } from '@/misc/renote.js';
|
||||
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js';
|
||||
|
@ -109,7 +109,7 @@ async function getMentionedRemoteUsers(note: Note): Promise<IRemoteUser[]> {
|
|||
const where = [] as any[];
|
||||
|
||||
// mention / reply / dm
|
||||
if (note.mentions > 0) {
|
||||
if (note.mentions.length > 0) {
|
||||
where.push({
|
||||
id: In(note.mentions),
|
||||
// only remote users, local users are on the server and do not need to be notified
|
||||
|
@ -132,10 +132,22 @@ async function getMentionedRemoteUsers(note: Note): Promise<IRemoteUser[]> {
|
|||
}
|
||||
|
||||
async function deliverToConcerned(user: { id: ILocalUser['id']; host: null; }, note: Note, content: any) {
|
||||
deliverToFollowers(user, content);
|
||||
deliverToRelays(user, content);
|
||||
const manager = new DeliverManager(user, content);
|
||||
|
||||
const remoteUsers = await getMentionedRemoteUsers(note);
|
||||
for (const remoteUser of remoteUsers) {
|
||||
deliverToUser(user, content, remoteUser);
|
||||
manager.addDirectRecipe(remoteUser);
|
||||
}
|
||||
|
||||
if (['public', 'home', 'followers'].includes(note.visibility)) {
|
||||
manager.addFollowersRecipe();
|
||||
}
|
||||
|
||||
if (['public', 'home'].includes(note.visibility)) {
|
||||
manager.addEveryone();
|
||||
}
|
||||
|
||||
await manager.execute();
|
||||
|
||||
deliverToRelays(user, content);
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
<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>
|
||||
<FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ i18n.ts.save }}</FormButton>
|
||||
</div>
|
||||
</FormFolder>
|
||||
</div>
|
||||
|
@ -77,6 +77,7 @@ async function init(): Promise<void> {
|
|||
summalyProxy = meta.summalyProxy;
|
||||
enableHcaptcha = meta.enableHcaptcha;
|
||||
enableRecaptcha = meta.enableRecaptcha;
|
||||
|
||||
secureMode = meta.secureMode;
|
||||
privateMode = meta.privateMode;
|
||||
allowedHosts = meta.allowedHosts.join('\n');
|
||||
|
@ -85,6 +86,9 @@ async function init(): Promise<void> {
|
|||
function save(): void {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
summalyProxy,
|
||||
secureMode,
|
||||
privateMode,
|
||||
allowedHosts: allowedHosts.split('\n'),
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue