From d41d77fcd7c7c40b8409a22baecd0c0101db6e1e Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 25 Mar 2025 17:25:30 -0400 Subject: [PATCH] revert 7bfada9792bc4d29d47d3895643543cbe15191cd: enhance: remove bull-board support --- packages/backend/package.json | 3 + .../src/server/api/ApiServerService.ts | 3 + .../src/server/web/ClientServerService.ts | 62 ++++++++++++++++ packages/backend/test/e2e/fetch-resource.ts | 32 +++++++-- packages/backend/test/utils.ts | 6 +- packages/frontend/src/pages/admin/queue.vue | 13 +++- pnpm-lock.yaml | 72 +++++++++++++++++++ 7 files changed, 180 insertions(+), 11 deletions(-) diff --git a/packages/backend/package.json b/packages/backend/package.json index ff4f2d7cf3..3f77d0f08a 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -67,6 +67,9 @@ "dependencies": { "@aws-sdk/client-s3": "3.772.0", "@aws-sdk/lib-storage": "3.772.0", + "@bull-board/api": "6.7.7", + "@bull-board/fastify": "6.7.7", + "@bull-board/ui": "6.7.7", "@discordapp/twemoji": "15.1.0", "@fastify/accepts": "5.0.2", "@fastify/cookie": "11.0.2", diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index 12459d5698..0d77309537 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -6,6 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import cors from '@fastify/cors'; import multipart from '@fastify/multipart'; +import fastifyCookie from '@fastify/cookie'; import { ModuleRef } from '@nestjs/core'; import { AuthenticationResponseJSON } from '@simplewebauthn/types'; import type { Config } from '@/config.js'; @@ -56,6 +57,8 @@ export class ApiServerService { }, }); + fastify.register(fastifyCookie, {}); + // Prevent cache fastify.addHook('onRequest', (request, reply, done) => { reply.header('Cache-Control', 'private, max-age=0, must-revalidate'); diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 165e4f3f73..79deb107b0 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -7,12 +7,16 @@ import { randomUUID } from 'node:crypto'; import { dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; +import { createBullBoard } from '@bull-board/api'; +import { BullMQAdapter } from '@bull-board/api/bullMQAdapter.js'; +import { FastifyAdapter as BullBoardFastifyAdapter } from '@bull-board/fastify'; import ms from 'ms'; import sharp from 'sharp'; import pug from 'pug'; import { In, IsNull } from 'typeorm'; import fastifyStatic from '@fastify/static'; import fastifyView from '@fastify/view'; +import fastifyCookie from '@fastify/cookie'; import fastifyProxy from '@fastify/http-proxy'; import vary from 'vary'; import htmlSafeJsonStringify from 'htmlescape'; @@ -222,6 +226,64 @@ export class ClientServerService { @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { + fastify.register(fastifyCookie, {}); + + //#region Bull Dashboard + const bullBoardPath = '/queue'; + + // Authenticate + fastify.addHook('onRequest', async (request, reply) => { + if (request.routeOptions.url == null) { + reply.code(404).send('Not found'); + return; + } + + // %71ueueとかでリクエストされたら困るため + const url = decodeURI(request.routeOptions.url); + if (url === bullBoardPath || url.startsWith(bullBoardPath + '/')) { + if (!url.startsWith(bullBoardPath + '/static/')) { + reply.header('Cache-Control', 'private, max-age=0, must-revalidate'); + } + + const token = request.cookies.token; + if (token == null) { + reply.code(401).send('Login required'); + return; + } + const user = await this.usersRepository.findOneBy({ token }); + if (user == null) { + reply.code(403).send('No such user'); + return; + } + const isAdministrator = await this.roleService.isAdministrator(user); + if (!isAdministrator) { + reply.code(403).send('Access denied'); + return; + } + } + }); + + const bullBoardServerAdapter = new BullBoardFastifyAdapter(); + + createBullBoard({ + queues: [ + this.systemQueue, + this.endedPollNotificationQueue, + this.deliverQueue, + this.inboxQueue, + this.dbQueue, + this.relationshipQueue, + this.objectStorageQueue, + this.userWebhookDeliverQueue, + this.systemWebhookDeliverQueue, + ].map(q => new BullMQAdapter(q)), + serverAdapter: bullBoardServerAdapter, + }); + + bullBoardServerAdapter.setBasePath(bullBoardPath); + (fastify.register as any)(bullBoardServerAdapter.registerPlugin(), { prefix: bullBoardPath }); + //#endregion + fastify.register(fastifyView, { root: _dirname + '/views', engine: { diff --git a/packages/backend/test/e2e/fetch-resource.ts b/packages/backend/test/e2e/fetch-resource.ts index 740295bda8..b85cebf724 100644 --- a/packages/backend/test/e2e/fetch-resource.ts +++ b/packages/backend/test/e2e/fetch-resource.ts @@ -6,7 +6,7 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { channel, clip, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js'; +import { channel, clip, cookie, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js'; import type { SimpleGetResponse } from '../utils.js'; import type * as misskey from 'misskey-js'; @@ -156,20 +156,20 @@ describe('Webリソース', () => { describe(' has entry such ', () => { beforeEach(() => { - post(alice, { text: '**a**' }); + post(alice, { text: "**a**" }) }); test('MFMを含まない。', async () => { - const content = await simpleGet(path(alice.username), '*/*', undefined, res => res.text()); + const content = await simpleGet(path(alice.username), "*/*", undefined, res => res.text()); const _body: unknown = content.body; // JSONフィードのときは改めて文字列化する - const body: string = typeof (_body) === 'object' ? JSON.stringify(_body) : _body as string; + const body: string = typeof (_body) === "object" ? JSON.stringify(_body) : _body as string; - if (body.includes('**a**')) { - throw new Error('MFM shouldn\'t be included'); + if (body.includes("**a**")) { + throw new Error("MFM shouldn't be included"); } }); - }); + }) }); describe.each([{ path: '/api/foo' }])('$path', ({ path }) => { @@ -180,6 +180,24 @@ describe('Webリソース', () => { })); }); + describe.each([{ path: '/queue' }])('$path', ({ path }) => { + test('はログインしないとGETできない。', async () => await notOk({ + path, + status: 401, + })); + + test('はadminでなければGETできない。', async () => await notOk({ + path, + cookie: cookie(bob), + status: 403, + })); + + test('はadminならGETできる。', async () => await ok({ + path, + cookie: cookie(alice), + })); + }); + describe.each([{ path: '/streaming' }])('$path', ({ path }) => { test('はGETできない。', async () => await notOk({ path, diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index 8306208477..cf97473d14 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -35,7 +35,7 @@ export type SystemWebhookPayload = { createdAt: string; type: string; body: any; -}; +} const config = loadConfig(); export const port = config.port; @@ -45,6 +45,10 @@ export const host = new URL(config.url).host; export const WEBHOOK_HOST = 'http://localhost:15080'; export const WEBHOOK_PORT = 15080; +export const cookie = (me: UserToken): string => { + return `token=${me.token};`; +}; + export type ApiRequest = { endpoint: E, parameters: P, diff --git a/packages/frontend/src/pages/admin/queue.vue b/packages/frontend/src/pages/admin/queue.vue index b5aee1e51e..65d728e776 100644 --- a/packages/frontend/src/pages/admin/queue.vue +++ b/packages/frontend/src/pages/admin/queue.vue @@ -17,11 +17,11 @@ SPDX-License-Identifier: AGPL-3.0-only