From 458c72c15372a6ae94416656b155f7c727ab4597 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Mon, 11 Nov 2024 16:35:13 +0900 Subject: [PATCH 01/23] Update about-misskey.vue --- packages/frontend/src/pages/about-misskey.vue | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue index fbbfb6ea61..f2becfd8f5 100644 --- a/packages/frontend/src/pages/about-misskey.vue +++ b/packages/frontend/src/pages/about-misskey.vue @@ -272,6 +272,9 @@ const patronsWithIcon = [{ }, { name: 'Yatoigawa', icon: 'https://assets.misskey-hub.net/patrons/505e3568885a4a488431a8f22b4553d0.jpg', +}, { + name: '秋瀬カヲル', + icon: 'https://assets.misskey-hub.net/patrons/0f22aeb866484f4fa51db6721e3f9847.jpg', }]; const patrons = [ @@ -380,6 +383,7 @@ const patrons = [ 'ケモナーのケシン', 'こまつぶり', 'まゆつな空高', + 'asata', ]; const thereIsTreasure = ref($i && !claimedAchievements.includes('foundTreasure')); -- 2.45.3 From a11b77a4158e07c18bcc57f77660cb08c0d950ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A5=BA=E5=AD=90w=20=28Yumechi=29?= <35571479+eternal-flame-AD@users.noreply.github.com> Date: Mon, 11 Nov 2024 18:51:18 -0600 Subject: [PATCH 02/23] =?UTF-8?q?fix(backend):=20Webhook=20Test=E4=B8=80?= =?UTF-8?q?=E8=87=B4=E6=80=A7=20(#14863)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(backend): Webhook Test一致性 Signed-off-by: eternal-flame-AD * UserWebhookPayload<'followed'> 修正 Signed-off-by: eternal-flame-AD --------- Signed-off-by: eternal-flame-AD --- CHANGELOG.md | 1 + packages/backend/src/core/QueueService.ts | 9 +++--- .../backend/src/core/UserWebhookService.ts | 14 ++++++++- .../backend/src/core/WebhookTestService.ts | 31 ++++++++++++------- .../backend/test/unit/WebhookTestService.ts | 16 +++++----- .../src/pages/settings/webhook.edit.vue | 2 +- .../src/pages/settings/webhook.new.vue | 2 +- 7 files changed, 48 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5bbda36fa..c92b8c06a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/712) - Fix: FTT無効時にユーザーリストタイムラインが使用できない問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/709) +- Fix: User Webhookテスト機能のMock Payloadを修正 ### Misskey.js - Fix: Stream初期化時、別途WebSocketを指定する場合の型定義を修正 diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 37028026cc..50f08da241 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -7,7 +7,7 @@ import { randomUUID } from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; import type { IActivity } from '@/core/activitypub/type.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; -import type { MiWebhook, webhookEventTypes } from '@/models/Webhook.js'; +import type { MiWebhook, WebhookEventTypes, webhookEventTypes } from '@/models/Webhook.js'; import type { MiSystemWebhook, SystemWebhookEventType } from '@/models/SystemWebhook.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; @@ -35,6 +35,7 @@ import type { } from './QueueModule.js'; import type httpSignature from '@peertube/http-signature'; import type * as Bull from 'bullmq'; +import { type UserWebhookPayload } from './UserWebhookService.js'; @Injectable() export class QueueService { @@ -468,10 +469,10 @@ export class QueueService { * @see UserWebhookDeliverProcessorService */ @bindThis - public userWebhookDeliver( + public userWebhookDeliver( webhook: MiWebhook, - type: typeof webhookEventTypes[number], - content: unknown, + type: T, + content: UserWebhookPayload, opts?: { attempts?: number }, ) { const data: UserWebhookDeliverJobData = { diff --git a/packages/backend/src/core/UserWebhookService.ts b/packages/backend/src/core/UserWebhookService.ts index 8a40a53688..7117a3d7fa 100644 --- a/packages/backend/src/core/UserWebhookService.ts +++ b/packages/backend/src/core/UserWebhookService.ts @@ -6,11 +6,23 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; import { type WebhooksRepository } from '@/models/_.js'; -import { MiWebhook } from '@/models/Webhook.js'; +import { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { GlobalEvents } from '@/core/GlobalEventService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; +import type { Packed } from '@/misc/json-schema.js'; + +export type UserWebhookPayload = + T extends 'note' | 'reply' | 'renote' |'mention' ? { + note: Packed<'Note'>, + } : + T extends 'follow' | 'unfollow' ? { + user: Packed<'UserDetailedNotMe'>, + } : + T extends 'followed' ? { + user: Packed<'UserLite'>, + } : never; @Injectable() export class UserWebhookService implements OnApplicationShutdown { diff --git a/packages/backend/src/core/WebhookTestService.ts b/packages/backend/src/core/WebhookTestService.ts index c826a28963..b1ea7974fb 100644 --- a/packages/backend/src/core/WebhookTestService.ts +++ b/packages/backend/src/core/WebhookTestService.ts @@ -10,7 +10,7 @@ import { MiSystemWebhook, type SystemWebhookEventType } from '@/models/SystemWeb import { SystemWebhookService } from '@/core/SystemWebhookService.js'; import { Packed } from '@/misc/json-schema.js'; import { type WebhookEventTypes } from '@/models/Webhook.js'; -import { UserWebhookService } from '@/core/UserWebhookService.js'; +import { type UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js'; import { QueueService } from '@/core/QueueService.js'; import { ModeratorInactivityRemainingTime } from '@/queue/processors/CheckModeratorsActivityProcessorService.js'; @@ -306,10 +306,10 @@ export class WebhookTestService { * - 送信対象イベント(on)に関する設定 */ @bindThis - public async testUserWebhook( + public async testUserWebhook( params: { webhookId: MiWebhook['id'], - type: WebhookEventTypes, + type: T, override?: Partial>, }, sender: MiUser | null, @@ -321,7 +321,7 @@ export class WebhookTestService { } const webhook = webhooks[0]; - const send = (contents: unknown) => { + const send = (type: U, contents: UserWebhookPayload) => { const merged = { ...webhook, ...params.override, @@ -329,7 +329,7 @@ export class WebhookTestService { // テスト目的なのでUserWebhookServiceの機能を経由せず直接キューに追加する(チェック処理などをスキップする意図). // また、Jobの試行回数も1回だけ. - this.queueService.userWebhookDeliver(merged, params.type, contents, { attempts: 1 }); + this.queueService.userWebhookDeliver(merged, type, contents, { attempts: 1 }); }; const dummyNote1 = generateDummyNote({ @@ -361,33 +361,40 @@ export class WebhookTestService { switch (params.type) { case 'note': { - send(toPackedNote(dummyNote1)); + send('note', { note: toPackedNote(dummyNote1) }); break; } case 'reply': { - send(toPackedNote(dummyReply1)); + send('reply', { note: toPackedNote(dummyReply1) }); break; } case 'renote': { - send(toPackedNote(dummyRenote1)); + send('renote', { note: toPackedNote(dummyRenote1) }); break; } case 'mention': { - send(toPackedNote(dummyMention1)); + send('mention', { note: toPackedNote(dummyMention1) }); break; } case 'follow': { - send(toPackedUserDetailedNotMe(dummyUser1)); + send('follow', { user: toPackedUserDetailedNotMe(dummyUser1) }); break; } case 'followed': { - send(toPackedUserLite(dummyUser2)); + send('followed', { user: toPackedUserLite(dummyUser2) }); break; } case 'unfollow': { - send(toPackedUserDetailedNotMe(dummyUser3)); + send('unfollow', { user: toPackedUserDetailedNotMe(dummyUser3) }); break; } + // まだ実装されていない (#9485) + case 'reaction': return; + default: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const _exhaustiveAssertion: never = params.type; + return; + } } } diff --git a/packages/backend/test/unit/WebhookTestService.ts b/packages/backend/test/unit/WebhookTestService.ts index 5e63b86f8f..be84ae9b84 100644 --- a/packages/backend/test/unit/WebhookTestService.ts +++ b/packages/backend/test/unit/WebhookTestService.ts @@ -7,7 +7,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { beforeAll, describe, jest } from '@jest/globals'; import { WebhookTestService } from '@/core/WebhookTestService.js'; -import { UserWebhookService } from '@/core/UserWebhookService.js'; +import { UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js'; import { SystemWebhookService } from '@/core/SystemWebhookService.js'; import { GlobalModule } from '@/GlobalModule.js'; import { MiSystemWebhook, MiUser, MiWebhook, UserProfilesRepository, UsersRepository } from '@/models/_.js'; @@ -122,7 +122,7 @@ describe('WebhookTestService', () => { const calls = queueService.userWebhookDeliver.mock.calls[0]; expect((calls[0] as any).id).toBe('dummy-webhook'); expect(calls[1]).toBe('note'); - expect((calls[2] as any).id).toBe('dummy-note-1'); + expect((calls[2] as UserWebhookPayload<'note'>).note.id).toBe('dummy-note-1'); }); test('reply', async () => { @@ -131,7 +131,7 @@ describe('WebhookTestService', () => { const calls = queueService.userWebhookDeliver.mock.calls[0]; expect((calls[0] as any).id).toBe('dummy-webhook'); expect(calls[1]).toBe('reply'); - expect((calls[2] as any).id).toBe('dummy-reply-1'); + expect((calls[2] as UserWebhookPayload<'reply'>).note.id).toBe('dummy-reply-1'); }); test('renote', async () => { @@ -140,7 +140,7 @@ describe('WebhookTestService', () => { const calls = queueService.userWebhookDeliver.mock.calls[0]; expect((calls[0] as any).id).toBe('dummy-webhook'); expect(calls[1]).toBe('renote'); - expect((calls[2] as any).id).toBe('dummy-renote-1'); + expect((calls[2] as UserWebhookPayload<'renote'>).note.id).toBe('dummy-renote-1'); }); test('mention', async () => { @@ -149,7 +149,7 @@ describe('WebhookTestService', () => { const calls = queueService.userWebhookDeliver.mock.calls[0]; expect((calls[0] as any).id).toBe('dummy-webhook'); expect(calls[1]).toBe('mention'); - expect((calls[2] as any).id).toBe('dummy-mention-1'); + expect((calls[2] as UserWebhookPayload<'mention'>).note.id).toBe('dummy-mention-1'); }); test('follow', async () => { @@ -158,7 +158,7 @@ describe('WebhookTestService', () => { const calls = queueService.userWebhookDeliver.mock.calls[0]; expect((calls[0] as any).id).toBe('dummy-webhook'); expect(calls[1]).toBe('follow'); - expect((calls[2] as any).id).toBe('dummy-user-1'); + expect((calls[2] as UserWebhookPayload<'follow'>).user.id).toBe('dummy-user-1'); }); test('followed', async () => { @@ -167,7 +167,7 @@ describe('WebhookTestService', () => { const calls = queueService.userWebhookDeliver.mock.calls[0]; expect((calls[0] as any).id).toBe('dummy-webhook'); expect(calls[1]).toBe('followed'); - expect((calls[2] as any).id).toBe('dummy-user-2'); + expect((calls[2] as UserWebhookPayload<'followed'>).user.id).toBe('dummy-user-2'); }); test('unfollow', async () => { @@ -176,7 +176,7 @@ describe('WebhookTestService', () => { const calls = queueService.userWebhookDeliver.mock.calls[0]; expect((calls[0] as any).id).toBe('dummy-webhook'); expect(calls[1]).toBe('unfollow'); - expect((calls[2] as any).id).toBe('dummy-user-3'); + expect((calls[2] as UserWebhookPayload<'unfollow'>).user.id).toBe('dummy-user-3'); }); describe('NoSuchWebhookError', () => { diff --git a/packages/frontend/src/pages/settings/webhook.edit.vue b/packages/frontend/src/pages/settings/webhook.edit.vue index 40d23e36c5..22b008fb61 100644 --- a/packages/frontend/src/pages/settings/webhook.edit.vue +++ b/packages/frontend/src/pages/settings/webhook.edit.vue @@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- {{ i18n.ts._webhookSettings._events.reaction }} + {{ i18n.ts._webhookSettings._events.reaction }}
diff --git a/packages/frontend/src/pages/settings/webhook.new.vue b/packages/frontend/src/pages/settings/webhook.new.vue index d62357caaf..727c4df2d6 100644 --- a/packages/frontend/src/pages/settings/webhook.new.vue +++ b/packages/frontend/src/pages/settings/webhook.new.vue @@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts._webhookSettings._events.note }} {{ i18n.ts._webhookSettings._events.reply }} {{ i18n.ts._webhookSettings._events.renote }} - {{ i18n.ts._webhookSettings._events.reaction }} + {{ i18n.ts._webhookSettings._events.reaction }} {{ i18n.ts._webhookSettings._events.mention }}
-- 2.45.3 From 174c3ef096b27004edc5696311fa5c3ec7b0ab4b Mon Sep 17 00:00:00 2001 From: eternal-flame-AD Date: Tue, 12 Nov 2024 02:18:25 -0600 Subject: [PATCH 03/23] Web security configuration Signed-off-by: eternal-flame-AD --- .config/cypress-devcontainer.yml | 22 +++++++++- .config/docker_example.yml | 22 +++++++++- .config/example.yml | 22 +++++++++- packages/backend/src/config.ts | 19 ++++++++- packages/backend/src/core/DriveService.ts | 40 +++++++++---------- packages/backend/src/server/ServerService.ts | 18 ++++++++- packages/backend/src/server/csp.ts | 28 +++++++++++-- .../src/server/web/ClientServerService.ts | 16 +++++++- 8 files changed, 154 insertions(+), 33 deletions(-) diff --git a/.config/cypress-devcontainer.yml b/.config/cypress-devcontainer.yml index e5bc1afa51..179ce77dc9 100644 --- a/.config/cypress-devcontainer.yml +++ b/.config/cypress-devcontainer.yml @@ -168,8 +168,8 @@ id: 'aidx' # options: # dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' -# ┌─────────────────────┐ -#───┘ Other configuration └───────────────────────────────────── +# ┌──────────────┐ +#──┘ Web Security └────────────────────────────────────── # Whether disable HSTS #disableHsts: true @@ -180,6 +180,24 @@ id: 'aidx' # - https://hstspreload.org/ #hstsPreload: false +# Enable additional security headers that reduce the risk of XSS attacks or privacy leaks. +# browserSandboxing: +# # Do not send the Referrer header to other domains. The default when browserSandboxing is missing is true. +# strictOriginReferrer: true +# csp: +# # Do not send a CSP header. The default is a strict CSP header that prevents any form of external fetching or execution. +# disable: false +# # Merge additional directives into the CSP header. The default is an empty object. +# # You may want to list your CDN or other trusted domains here. +# # Media proxies are automatically added to the CSP header. This is an exception, things like Sentry will not be automatically added. +# appendDirectives: +# 'script-src': +# - "'unsafe-eval'" # do not use this ... just an example +# - 'https://example.com' + +# ┌─────────────────────┐ +#───┘ Other configuration └───────────────────────────────────── + # Number of worker processes #clusterLimit: 1 diff --git a/.config/docker_example.yml b/.config/docker_example.yml index 7d51ab0443..35dfa5ded3 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -162,8 +162,8 @@ id: 'aidx' # options: # dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' -# ┌─────────────────────┐ -#───┘ Other configuration └───────────────────────────────────── +# ┌──────────────┐ +#──┘ Web Security └────────────────────────────────────── # Whether disable HSTS #disableHsts: true @@ -174,6 +174,24 @@ id: 'aidx' # - https://hstspreload.org/ #hstsPreload: false +# Enable additional security headers that reduce the risk of XSS attacks or privacy leaks. +# browserSandboxing: +# # Do not send the Referrer header to other domains. The default when browserSandboxing is missing is true. +# strictOriginReferrer: true +# csp: +# # Do not send a CSP header. The default is a strict CSP header that prevents any form of external fetching or execution. +# disable: false +# # Merge additional directives into the CSP header. The default is an empty object. +# # You may want to list your CDN or other trusted domains here. +# # Media proxies are automatically added to the CSP header. This is an exception, things like Sentry will not be automatically added. +# appendDirectives: +# 'script-src': +# - "'unsafe-eval'" # do not use this ... just an example +# - 'https://example.com' + +# ┌─────────────────────┐ +#───┘ Other configuration └───────────────────────────────────── + # Number of worker processes #clusterLimit: 1 diff --git a/.config/example.yml b/.config/example.yml index ce336d9f75..ef0e30d4e1 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -244,8 +244,8 @@ id: 'aidx' # options: # dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' -# ┌─────────────────────┐ -#───┘ Other configuration └───────────────────────────────────── +# ┌──────────────┐ +#──┘ Web Security └────────────────────────────────────── # Whether disable HSTS #disableHsts: true @@ -256,6 +256,24 @@ id: 'aidx' # - https://hstspreload.org/ #hstsPreload: false +# Enable additional security headers that reduce the risk of XSS attacks or privacy leaks. +# browserSandboxing: +# # Do not send the Referrer header to other domains. The default when browserSandboxing is missing is true. +# strictOriginReferrer: true +# csp: +# # Do not send a CSP header. The default is a strict CSP header that prevents any form of external fetching or execution. +# disable: false +# # Merge additional directives into the CSP header. The default is an empty object. +# # You may want to list your CDN or other trusted domains here. +# # Media proxies are automatically added to the CSP header. This is an exception, things like Sentry will not be automatically added. +# appendDirectives: +# 'script-src': +# - "'unsafe-eval'" # do not use this ... just an example +# - 'https://example.com' + +# ┌─────────────────────┐ +#───┘ Other configuration └───────────────────────────────────── + # Number of worker processes #clusterLimit: 1 diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 0ac357be77..035c9c6331 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -20,6 +20,18 @@ type RedisOptionsSource = Partial & { prefix?: string; }; +type BrowserSandboxing = { + // send Referrer-Policy: strict-origin + strictOriginReferrer?: boolean; + csp?: { + disable?: boolean; + + appendDirectives?: { + [directive: string]: string | string[]; + } + }; +}; + /** * 設定ファイルの型 */ @@ -65,6 +77,8 @@ type Source = { publishTarballInsteadOfProvideRepositoryUrl?: boolean; + browserSandboxing?: BrowserSandboxing; + setupPassword?: string; proxy?: string; @@ -155,7 +169,9 @@ export type Config = { proxyRemoteFiles: boolean | undefined; signToActivityPubGet: boolean | undefined; - cspPrerenderedContent: Map, + browserSandboxing: BrowserSandboxing; + + cspPrerenderedContent: Map; version: string; gitDescribe: string; @@ -252,6 +268,7 @@ export function loadConfig(): Config { version, gitCommit, gitDescribe, + browserSandboxing: config.browserSandboxing ?? { strictOriginReferrer: true }, publishTarballInsteadOfProvideRepositoryUrl: !!config.publishTarballInsteadOfProvideRepositoryUrl, setupPassword: config.setupPassword, url: url.origin, diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index c332e5a0a8..495d67a93b 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -87,9 +87,9 @@ type UploadFromUrlArgs = { @Injectable() export class DriveService { - public static NoSuchFolderError = class extends Error {}; - public static InvalidFileNameError = class extends Error {}; - public static CannotUnmarkSensitiveError = class extends Error {}; + public static NoSuchFolderError = class extends Error { }; + public static InvalidFileNameError = class extends Error { }; + public static CannotUnmarkSensitiveError = class extends Error { }; private registerLogger: Logger; private downloaderLogger: Logger; private deleteLogger: Logger; @@ -147,11 +147,11 @@ export class DriveService { */ @bindThis private async save(file: MiDriveFile, path: string, name: string, type: string, hash: string, size: number): Promise { - // thunbnail, webpublic を必要なら生成 + // thunbnail, webpublic を必要なら生成 const alts = await this.generateAlts(path, type, !file.uri); if (this.meta.useObjectStorage) { - //#region ObjectStorage params + //#region ObjectStorage params let [ext] = (name.match(/\.([a-zA-Z0-9_-]+)$/) ?? ['']); if (ext === '') { @@ -170,11 +170,11 @@ export class DriveService { } const baseUrl = this.meta.objectStorageBaseUrl - ?? `${ this.meta.objectStorageUseSSL ? 'https' : 'http' }://${ this.meta.objectStorageEndpoint }${ this.meta.objectStoragePort ? `:${this.meta.objectStoragePort}` : '' }/${ this.meta.objectStorageBucket }`; + ?? `${this.meta.objectStorageUseSSL ? 'https' : 'http'}://${this.meta.objectStorageEndpoint}${this.meta.objectStoragePort ? `:${this.meta.objectStoragePort}` : ''}/${this.meta.objectStorageBucket}`; // for original const key = `${this.meta.objectStoragePrefix}/${randomUUID()}${ext}`; - const url = `${ baseUrl }/${ key }`; + const url = `${baseUrl}/${key}`; // for alts let webpublicKey: string | null = null; @@ -191,7 +191,7 @@ export class DriveService { if (alts.webpublic) { webpublicKey = `${this.meta.objectStoragePrefix}/webpublic-${randomUUID()}.${alts.webpublic.ext}`; - webpublicUrl = `${ baseUrl }/${ webpublicKey }`; + webpublicUrl = `${baseUrl}/${webpublicKey}`; this.registerLogger.info(`uploading webpublic: ${webpublicKey}`); uploads.push(this.upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, alts.webpublic.ext, name)); @@ -199,7 +199,7 @@ export class DriveService { if (alts.thumbnail) { thumbnailKey = `${this.meta.objectStoragePrefix}/thumbnail-${randomUUID()}.${alts.thumbnail.ext}`; - thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`; + thumbnailUrl = `${baseUrl}/${thumbnailKey}`; this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`); uploads.push(this.upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type, alts.thumbnail.ext, `${name}.thumbnail`)); @@ -311,9 +311,9 @@ export class DriveService { satisfyWebpublic = !!( type !== 'image/svg+xml' && // security reason type !== 'image/avif' && // not supported by Mastodon and MS Edge - !(metadata.exif ?? metadata.iptc ?? metadata.xmp ?? metadata.tifftagPhotoshop) && - metadata.width && metadata.width <= 2048 && - metadata.height && metadata.height <= 2048 + !(metadata.exif ?? metadata.iptc ?? metadata.xmp ?? metadata.tifftagPhotoshop) && + metadata.width && metadata.width <= 2048 && + metadata.height && metadata.height <= 2048 ); } catch (err) { this.registerLogger.warn(`sharp failed: ${err}`); @@ -470,11 +470,11 @@ export class DriveService { const info = await this.fileInfoService.getFileInfo(path, { skipSensitiveDetection: skipNsfwCheck, sensitiveThreshold: // 感度が高いほどしきい値は低くすることになる - this.meta.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 0.1 : - this.meta.sensitiveMediaDetectionSensitivity === 'high' ? 0.3 : - this.meta.sensitiveMediaDetectionSensitivity === 'low' ? 0.7 : - this.meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0.9 : - 0.5, + this.meta.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 0.1 : + this.meta.sensitiveMediaDetectionSensitivity === 'high' ? 0.3 : + this.meta.sensitiveMediaDetectionSensitivity === 'low' ? 0.7 : + this.meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0.9 : + 0.5, sensitiveThresholdForPorn: 0.75, enableSensitiveMediaDetectionForVideos: this.meta.enableSensitiveMediaDetectionForVideos, }); @@ -494,7 +494,7 @@ export class DriveService { ); if (user && !force) { - // Check if there is a file with the same hash + // Check if there is a file with the same hash const matched = await this.driveFilesRepository.findOneBy({ md5: info.md5, userId: user.id, @@ -582,7 +582,7 @@ export class DriveService { file.maybePorn = info.porn; file.isSensitive = user ? this.userEntityService.isLocalUser(user) && profile!.alwaysMarkNsfw ? true : - sensitive ?? false + sensitive ?? false : false; if (user && this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, user.host)) file.isSensitive = true; @@ -616,7 +616,7 @@ export class DriveService { file = await this.driveFilesRepository.insertOne(file); } catch (err) { - // duplicate key error (when already registered) + // duplicate key error (when already registered) if (isDuplicateKeyValueError(err)) { this.registerLogger.info(`already registered ${file.uri}`); diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index df67a728ef..be02274600 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -89,14 +89,28 @@ export class ServerService implements OnApplicationShutdown { fastify.addHook('onRequest', makeHstsHook(host, preload)); } + // Other Security/Privacy Headers + fastify.addHook('onRequest', (_, reply, done) => { + reply.header('x-content-type-options', 'nosniff'); + reply.header('permissions-policy', 'interest-cohort=()'); // Disable FLoC + if (this.config.browserSandboxing.strictOriginReferrer) { + reply.header('referrer-policy', 'strict-origin'); + } + done(); + }); + // CSP - if (process.env.NODE_ENV === 'production') { + if (process.env.NODE_ENV === 'production' && !this.config.browserSandboxing.csp?.disable) { console.debug('cspPrerenderedContent', this.config.cspPrerenderedContent); const generatedCSP = generateCSP(this.config.cspPrerenderedContent, { mediaProxy: this.config.mediaProxy ? `https://${new URL(this.config.mediaProxy).host}` : undefined, + script_src: [ + `https://${new URL(this.config.url).host}/embed_vite/`, + `https://${new URL(this.config.url).host}/vite/`, + ], }); fastify.addHook('onRequest', (_, reply, done) => { - reply.header('Content-Security-Policy', generatedCSP); + reply.header('content-security-policy', generatedCSP); done(); }); } diff --git a/packages/backend/src/server/csp.ts b/packages/backend/src/server/csp.ts index 45b37fbd06..aeee4eab3a 100644 --- a/packages/backend/src/server/csp.ts +++ b/packages/backend/src/server/csp.ts @@ -13,6 +13,8 @@ export type CSPHashed = { export function generateCSP(hashedMap: Map, options: { mediaProxy?: string, + script_src?: string[], + append?: { [key: string]: string | string[] }, }) { const keys = Array.from(hashedMap.keys()); const scripts = keys @@ -22,7 +24,7 @@ export function generateCSP(hashedMap: Map, options: { .filter(name => name.endsWith('.css')) .map(name => `'${hashedMap.get(name)!.integrity}'`); - return ([ + const cpolicy = [ ['default-src', ['\'self\'']], ['img-src', [ @@ -42,7 +44,11 @@ export function generateCSP(hashedMap: Map, options: { // // ref: https://github.com/shikijs/shiki/issues/671 ['style-src-attr', ['\'self\'', '\'unsafe-inline\'']], - ['script-src', ['\'self\'', '\'wasm-unsafe-eval\'', ...scripts]], + ['script-src', [ + ...(options.script_src ? options.script_src : ['\'self\'']), + '\'wasm-unsafe-eval\'', + ...scripts + ]], ['object-src', ['\'none\'']], ['base-uri', ['\'self\'']], ['form-action', ['\'self\'']], @@ -52,7 +58,23 @@ export function generateCSP(hashedMap: Map, options: { [ ['upgrade-insecure-requests', []], ] : []), - ] as [string, string[]][]) + ] as [string, string[]][]; + + if (options.append) { + for (const [name, values] of Object.entries(options.append)) { + if (!values) { + continue; + } + const found = cpolicy.find(([n]) => n === name); + if (found) { + found[1].push(...(Array.isArray(values) ? values : [values])); + } else { + cpolicy.push([name, Array.isArray(values) ? values : [values]]); + } + } + } + + return cpolicy .map(([name, values]) => { return `${name} ${values.join(' ')}`; }).join('; '); diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index e2f227323f..b107fdc7f9 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -248,14 +248,28 @@ export class ClientServerService { fastify.addHook('onRequest', makeHstsHook(host, preload)); } + // Other Security/Privacy Headers + fastify.addHook('onRequest', (_, reply, done) => { + reply.header('x-content-type-options', 'nosniff'); + reply.header('permissions-policy', 'interest-cohort=()'); // Disable FLoC + if (this.config.browserSandboxing.strictOriginReferrer ?? true) { + reply.header('referrer-policy', 'strict-origin'); + } + done(); + }); + // CSP if (process.env.NODE_ENV === 'production') { console.debug('cspPrerenderedContent', this.config.cspPrerenderedContent); const generatedCSP = generateCSP(this.config.cspPrerenderedContent, { mediaProxy: this.config.mediaProxy ? `https://${new URL(this.config.mediaProxy).host}` : undefined, + script_src: [ + `https://${new URL(this.config.url).host}/embed_vite/`, + `https://${new URL(this.config.url).host}/vite/`, + ], }); fastify.addHook('onRequest', (_, reply, done) => { - reply.header('Content-Security-Policy', generatedCSP); + reply.header('content-security-policy', generatedCSP); done(); }); } -- 2.45.3 From 2305788ed9cdc0c61dfeef9249f93f760a9d5f77 Mon Sep 17 00:00:00 2001 From: shimmar <78616491+shimmar@users.noreply.github.com> Date: Wed, 13 Nov 2024 11:19:54 +0900 Subject: [PATCH 04/23] =?UTF-8?q?Enhance(frontend):=20=E3=83=8E=E3=83=BC?= =?UTF-8?q?=E3=83=88=E8=A9=B3=E7=B4=B0=E7=94=BB=E9=9D=A2=E3=81=AB=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E3=83=AB=E3=81=AE=E3=83=90=E3=83=83=E3=82=B8=E3=82=92?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=20(#14946)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance(frontend): ノートの詳細画面にロールのバッジを表示(#14058) * Update CHANGELOG.md --- CHANGELOG.md | 1 + .../src/components/MkNoteDetailed.vue | 27 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c92b8c06a1..7c07d86580 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - Enhance: Self-XSS防止用の警告を追加 - Enhance: カタルーニャ語 (ca-ES) に対応 - Enhance: 個別お知らせページではMetaタグを出力するように +- Enhance: ノート詳細画面にロールのバッジを表示 - Fix: 通知の範囲指定の設定項目が必要ない通知設定でも範囲指定の設定がでている問題を修正 - Fix: Turnstileが失敗・期限切れした際にも成功扱いとなってしまう問題を修正 (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/768) diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index e0473dce5e..4a350388c2 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -62,7 +62,14 @@ SPDX-License-Identifier: AGPL-3.0-only -
+
+
+ +
+
+ +
+
@@ -679,12 +686,30 @@ function loadConversation() { float: right; } +.noteHeaderUsernameAndBadgeRoles { + display: flex; +} + .noteHeaderUsername { margin-bottom: 2px; + margin-right: 0.5em; line-height: 1.3; word-wrap: anywhere; } +.noteHeaderBadgeRoles { + margin: 0 .5em 0 0; +} + +.noteHeaderBadgeRole { + height: 1.3em; + vertical-align: -20%; + + & + .noteHeaderBadgeRole { + margin-left: 0.2em; + } +} + .noteContent { container-type: inline-size; overflow-wrap: break-word; -- 2.45.3 From 4d541015109b0b046595634f81e4e54754d4a288 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Wed, 13 Nov 2024 19:43:36 +0900 Subject: [PATCH 05/23] update node to 22.11.0 (#14869) * wip * Update CHANGELOG.md * Update CHANGELOG.md --- .devcontainer/devcontainer.json | 2 +- .github/workflows/get-api-diff.yml | 2 +- .github/workflows/on-release-created.yml | 2 +- .github/workflows/test-backend.yml | 4 ++-- .github/workflows/test-federation.yml | 2 +- .github/workflows/test-frontend.yml | 4 ++-- .github/workflows/test-misskey-js.yml | 2 +- .github/workflows/test-production.yml | 2 +- .github/workflows/validate-api-json.yml | 2 +- .node-version | 2 +- CHANGELOG.md | 4 ++++ Dockerfile | 2 +- 12 files changed, 17 insertions(+), 13 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index fbf959d449..713c2e5fdd 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,7 +5,7 @@ "workspaceFolder": "/workspace", "features": { "ghcr.io/devcontainers/features/node:1": { - "version": "20.16.0" + "version": "22.11.0" }, "ghcr.io/devcontainers-contrib/features/corepack:1": {} }, diff --git a/.github/workflows/get-api-diff.yml b/.github/workflows/get-api-diff.yml index 1bcaa0d9c4..972619ec60 100644 --- a/.github/workflows/get-api-diff.yml +++ b/.github/workflows/get-api-diff.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: - node-version: [20.16.0] + node-version: [22.11.0] api-json-name: [api-base.json, api-head.json] include: - api-json-name: api-base.json diff --git a/.github/workflows/on-release-created.yml b/.github/workflows/on-release-created.yml index ffaf7bc038..6258fa693a 100644 --- a/.github/workflows/on-release-created.yml +++ b/.github/workflows/on-release-created.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: - node-version: [20.16.0] + node-version: [22.11.0] steps: - uses: actions/checkout@v4.1.1 diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index d95d6676f9..cedcf16ecd 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: - node-version: [20.16.0] + node-version: [22.11.0] services: postgres: @@ -71,7 +71,7 @@ jobs: strategy: matrix: - node-version: [20.16.0] + node-version: [22.11.0] services: postgres: diff --git a/.github/workflows/test-federation.yml b/.github/workflows/test-federation.yml index 183ddb6f34..e89cdcb091 100644 --- a/.github/workflows/test-federation.yml +++ b/.github/workflows/test-federation.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [20.16.0] + node-version: [22.11.0] steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml index c68e1a8ef1..eca596c7c7 100644 --- a/.github/workflows/test-frontend.yml +++ b/.github/workflows/test-frontend.yml @@ -26,7 +26,7 @@ jobs: strategy: matrix: - node-version: [20.16.0] + node-version: [22.11.0] steps: - uses: actions/checkout@v4.1.1 @@ -61,7 +61,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20.16.0] + node-version: [22.11.0] browser: [chrome] services: diff --git a/.github/workflows/test-misskey-js.yml b/.github/workflows/test-misskey-js.yml index 63e81f8c92..054c10bf61 100644 --- a/.github/workflows/test-misskey-js.yml +++ b/.github/workflows/test-misskey-js.yml @@ -21,7 +21,7 @@ jobs: strategy: matrix: - node-version: [20.16.0] + node-version: [22.11.0] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: diff --git a/.github/workflows/test-production.yml b/.github/workflows/test-production.yml index 0abc09c5a6..11a95ca82f 100644 --- a/.github/workflows/test-production.yml +++ b/.github/workflows/test-production.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: - node-version: [20.16.0] + node-version: [22.11.0] steps: - uses: actions/checkout@v4.1.1 diff --git a/.github/workflows/validate-api-json.yml b/.github/workflows/validate-api-json.yml index f809af1063..835b2a9a24 100644 --- a/.github/workflows/validate-api-json.yml +++ b/.github/workflows/validate-api-json.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: - node-version: [20.16.0] + node-version: [22.11.0] steps: - uses: actions/checkout@v4.1.1 diff --git a/.node-version b/.node-version index 8ce7030825..7af24b7ddb 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -20.16.0 +22.11.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c07d86580..e07cdc00d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## 2024.11.0 +### Note +- DockerのNode.jsが22.11.0に更新されました + ### General - Feat: コンテンツの表示にログインを必須にできるように - Feat: 過去のノートを非公開化/フォロワーのみ表示可能にできるように @@ -31,6 +34,7 @@ - Fix: メールアドレス登録有効化時の「完了」ダイアログボックスの表示条件を修正 ### Server +- Enhance: DockerのNode.jsを22.11.0に更新 - Enhance: 起動前の疎通チェックで、DBとメイン以外のRedisの疎通確認も行うように (Based on https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/588) (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/715) diff --git a/Dockerfile b/Dockerfile index e21b2a31fc..ee765abe7c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax = docker/dockerfile:1.4 -ARG NODE_VERSION=20.16.0-bullseye +ARG NODE_VERSION=22.11.0-bullseye # build assets & compile TypeScript -- 2.45.3 From afac97997767de61935d2960e928efbb75b3f12a Mon Sep 17 00:00:00 2001 From: eternal-flame-AD Date: Tue, 12 Nov 2024 02:18:25 -0600 Subject: [PATCH 06/23] Web security configuration Signed-off-by: eternal-flame-AD --- .config/cypress-devcontainer.yml | 22 +++++++++- .config/docker_example.yml | 22 +++++++++- .config/example.yml | 22 +++++++++- locales/en-US.yml | 1 + locales/ja-JP.yml | 1 + packages/backend/src/config.ts | 19 ++++++++- packages/backend/src/core/DriveService.ts | 40 +++++++++---------- packages/backend/src/server/ServerService.ts | 18 ++++++++- .../api/endpoints/admin/accounts/create.ts | 2 + packages/backend/src/server/csp.ts | 28 +++++++++++-- .../src/server/web/ClientServerService.ts | 16 +++++++- packages/misskey-js/etc/misskey-js.api.md | 2 +- .../misskey-js/src/autogen/apiClientJSDoc.ts | 2 +- packages/misskey-js/src/autogen/types.ts | 4 +- packages/misskey-js/src/consts.ts | 1 + 15 files changed, 163 insertions(+), 37 deletions(-) diff --git a/.config/cypress-devcontainer.yml b/.config/cypress-devcontainer.yml index e5bc1afa51..179ce77dc9 100644 --- a/.config/cypress-devcontainer.yml +++ b/.config/cypress-devcontainer.yml @@ -168,8 +168,8 @@ id: 'aidx' # options: # dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' -# ┌─────────────────────┐ -#───┘ Other configuration └───────────────────────────────────── +# ┌──────────────┐ +#──┘ Web Security └────────────────────────────────────── # Whether disable HSTS #disableHsts: true @@ -180,6 +180,24 @@ id: 'aidx' # - https://hstspreload.org/ #hstsPreload: false +# Enable additional security headers that reduce the risk of XSS attacks or privacy leaks. +# browserSandboxing: +# # Do not send the Referrer header to other domains. The default when browserSandboxing is missing is true. +# strictOriginReferrer: true +# csp: +# # Do not send a CSP header. The default is a strict CSP header that prevents any form of external fetching or execution. +# disable: false +# # Merge additional directives into the CSP header. The default is an empty object. +# # You may want to list your CDN or other trusted domains here. +# # Media proxies are automatically added to the CSP header. This is an exception, things like Sentry will not be automatically added. +# appendDirectives: +# 'script-src': +# - "'unsafe-eval'" # do not use this ... just an example +# - 'https://example.com' + +# ┌─────────────────────┐ +#───┘ Other configuration └───────────────────────────────────── + # Number of worker processes #clusterLimit: 1 diff --git a/.config/docker_example.yml b/.config/docker_example.yml index 7d51ab0443..35dfa5ded3 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -162,8 +162,8 @@ id: 'aidx' # options: # dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' -# ┌─────────────────────┐ -#───┘ Other configuration └───────────────────────────────────── +# ┌──────────────┐ +#──┘ Web Security └────────────────────────────────────── # Whether disable HSTS #disableHsts: true @@ -174,6 +174,24 @@ id: 'aidx' # - https://hstspreload.org/ #hstsPreload: false +# Enable additional security headers that reduce the risk of XSS attacks or privacy leaks. +# browserSandboxing: +# # Do not send the Referrer header to other domains. The default when browserSandboxing is missing is true. +# strictOriginReferrer: true +# csp: +# # Do not send a CSP header. The default is a strict CSP header that prevents any form of external fetching or execution. +# disable: false +# # Merge additional directives into the CSP header. The default is an empty object. +# # You may want to list your CDN or other trusted domains here. +# # Media proxies are automatically added to the CSP header. This is an exception, things like Sentry will not be automatically added. +# appendDirectives: +# 'script-src': +# - "'unsafe-eval'" # do not use this ... just an example +# - 'https://example.com' + +# ┌─────────────────────┐ +#───┘ Other configuration └───────────────────────────────────── + # Number of worker processes #clusterLimit: 1 diff --git a/.config/example.yml b/.config/example.yml index ce336d9f75..ef0e30d4e1 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -244,8 +244,8 @@ id: 'aidx' # options: # dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' -# ┌─────────────────────┐ -#───┘ Other configuration └───────────────────────────────────── +# ┌──────────────┐ +#──┘ Web Security └────────────────────────────────────── # Whether disable HSTS #disableHsts: true @@ -256,6 +256,24 @@ id: 'aidx' # - https://hstspreload.org/ #hstsPreload: false +# Enable additional security headers that reduce the risk of XSS attacks or privacy leaks. +# browserSandboxing: +# # Do not send the Referrer header to other domains. The default when browserSandboxing is missing is true. +# strictOriginReferrer: true +# csp: +# # Do not send a CSP header. The default is a strict CSP header that prevents any form of external fetching or execution. +# disable: false +# # Merge additional directives into the CSP header. The default is an empty object. +# # You may want to list your CDN or other trusted domains here. +# # Media proxies are automatically added to the CSP header. This is an exception, things like Sentry will not be automatically added. +# appendDirectives: +# 'script-src': +# - "'unsafe-eval'" # do not use this ... just an example +# - 'https://example.com' + +# ┌─────────────────────┐ +#───┘ Other configuration └───────────────────────────────────── + # Number of worker processes #clusterLimit: 1 diff --git a/locales/en-US.yml b/locales/en-US.yml index 8570addfa2..313126d669 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -2119,6 +2119,7 @@ _permissions: "read:flash-likes": "View list of liked Plays" "write:flash-likes": "Edit list of liked Plays" "read:admin:abuse-user-reports": "View user reports" + "write:admin:create-account": "Create user account" "write:admin:delete-account": "Delete user account" "write:admin:delete-all-files-of-a-user": "Delete all files of a user" "read:admin:index-stats": "View database index stats" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 5d8e1a5e72..fdc2ce045b 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2163,6 +2163,7 @@ _permissions: "read:flash-likes": "Playのいいねを見る" "write:flash-likes": "Playのいいねを操作する" "read:admin:abuse-user-reports": "ユーザーからの通報を見る" + "write:admin:create-account": "ユーザーアカウントを作成する" "write:admin:delete-account": "ユーザーアカウントを削除する" "write:admin:delete-all-files-of-a-user": "ユーザーのすべてのファイルを削除する" "read:admin:index-stats": "データベースインデックスに関する情報を見る" diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 0ac357be77..035c9c6331 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -20,6 +20,18 @@ type RedisOptionsSource = Partial & { prefix?: string; }; +type BrowserSandboxing = { + // send Referrer-Policy: strict-origin + strictOriginReferrer?: boolean; + csp?: { + disable?: boolean; + + appendDirectives?: { + [directive: string]: string | string[]; + } + }; +}; + /** * 設定ファイルの型 */ @@ -65,6 +77,8 @@ type Source = { publishTarballInsteadOfProvideRepositoryUrl?: boolean; + browserSandboxing?: BrowserSandboxing; + setupPassword?: string; proxy?: string; @@ -155,7 +169,9 @@ export type Config = { proxyRemoteFiles: boolean | undefined; signToActivityPubGet: boolean | undefined; - cspPrerenderedContent: Map, + browserSandboxing: BrowserSandboxing; + + cspPrerenderedContent: Map; version: string; gitDescribe: string; @@ -252,6 +268,7 @@ export function loadConfig(): Config { version, gitCommit, gitDescribe, + browserSandboxing: config.browserSandboxing ?? { strictOriginReferrer: true }, publishTarballInsteadOfProvideRepositoryUrl: !!config.publishTarballInsteadOfProvideRepositoryUrl, setupPassword: config.setupPassword, url: url.origin, diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index c332e5a0a8..495d67a93b 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -87,9 +87,9 @@ type UploadFromUrlArgs = { @Injectable() export class DriveService { - public static NoSuchFolderError = class extends Error {}; - public static InvalidFileNameError = class extends Error {}; - public static CannotUnmarkSensitiveError = class extends Error {}; + public static NoSuchFolderError = class extends Error { }; + public static InvalidFileNameError = class extends Error { }; + public static CannotUnmarkSensitiveError = class extends Error { }; private registerLogger: Logger; private downloaderLogger: Logger; private deleteLogger: Logger; @@ -147,11 +147,11 @@ export class DriveService { */ @bindThis private async save(file: MiDriveFile, path: string, name: string, type: string, hash: string, size: number): Promise { - // thunbnail, webpublic を必要なら生成 + // thunbnail, webpublic を必要なら生成 const alts = await this.generateAlts(path, type, !file.uri); if (this.meta.useObjectStorage) { - //#region ObjectStorage params + //#region ObjectStorage params let [ext] = (name.match(/\.([a-zA-Z0-9_-]+)$/) ?? ['']); if (ext === '') { @@ -170,11 +170,11 @@ export class DriveService { } const baseUrl = this.meta.objectStorageBaseUrl - ?? `${ this.meta.objectStorageUseSSL ? 'https' : 'http' }://${ this.meta.objectStorageEndpoint }${ this.meta.objectStoragePort ? `:${this.meta.objectStoragePort}` : '' }/${ this.meta.objectStorageBucket }`; + ?? `${this.meta.objectStorageUseSSL ? 'https' : 'http'}://${this.meta.objectStorageEndpoint}${this.meta.objectStoragePort ? `:${this.meta.objectStoragePort}` : ''}/${this.meta.objectStorageBucket}`; // for original const key = `${this.meta.objectStoragePrefix}/${randomUUID()}${ext}`; - const url = `${ baseUrl }/${ key }`; + const url = `${baseUrl}/${key}`; // for alts let webpublicKey: string | null = null; @@ -191,7 +191,7 @@ export class DriveService { if (alts.webpublic) { webpublicKey = `${this.meta.objectStoragePrefix}/webpublic-${randomUUID()}.${alts.webpublic.ext}`; - webpublicUrl = `${ baseUrl }/${ webpublicKey }`; + webpublicUrl = `${baseUrl}/${webpublicKey}`; this.registerLogger.info(`uploading webpublic: ${webpublicKey}`); uploads.push(this.upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, alts.webpublic.ext, name)); @@ -199,7 +199,7 @@ export class DriveService { if (alts.thumbnail) { thumbnailKey = `${this.meta.objectStoragePrefix}/thumbnail-${randomUUID()}.${alts.thumbnail.ext}`; - thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`; + thumbnailUrl = `${baseUrl}/${thumbnailKey}`; this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`); uploads.push(this.upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type, alts.thumbnail.ext, `${name}.thumbnail`)); @@ -311,9 +311,9 @@ export class DriveService { satisfyWebpublic = !!( type !== 'image/svg+xml' && // security reason type !== 'image/avif' && // not supported by Mastodon and MS Edge - !(metadata.exif ?? metadata.iptc ?? metadata.xmp ?? metadata.tifftagPhotoshop) && - metadata.width && metadata.width <= 2048 && - metadata.height && metadata.height <= 2048 + !(metadata.exif ?? metadata.iptc ?? metadata.xmp ?? metadata.tifftagPhotoshop) && + metadata.width && metadata.width <= 2048 && + metadata.height && metadata.height <= 2048 ); } catch (err) { this.registerLogger.warn(`sharp failed: ${err}`); @@ -470,11 +470,11 @@ export class DriveService { const info = await this.fileInfoService.getFileInfo(path, { skipSensitiveDetection: skipNsfwCheck, sensitiveThreshold: // 感度が高いほどしきい値は低くすることになる - this.meta.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 0.1 : - this.meta.sensitiveMediaDetectionSensitivity === 'high' ? 0.3 : - this.meta.sensitiveMediaDetectionSensitivity === 'low' ? 0.7 : - this.meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0.9 : - 0.5, + this.meta.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 0.1 : + this.meta.sensitiveMediaDetectionSensitivity === 'high' ? 0.3 : + this.meta.sensitiveMediaDetectionSensitivity === 'low' ? 0.7 : + this.meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0.9 : + 0.5, sensitiveThresholdForPorn: 0.75, enableSensitiveMediaDetectionForVideos: this.meta.enableSensitiveMediaDetectionForVideos, }); @@ -494,7 +494,7 @@ export class DriveService { ); if (user && !force) { - // Check if there is a file with the same hash + // Check if there is a file with the same hash const matched = await this.driveFilesRepository.findOneBy({ md5: info.md5, userId: user.id, @@ -582,7 +582,7 @@ export class DriveService { file.maybePorn = info.porn; file.isSensitive = user ? this.userEntityService.isLocalUser(user) && profile!.alwaysMarkNsfw ? true : - sensitive ?? false + sensitive ?? false : false; if (user && this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, user.host)) file.isSensitive = true; @@ -616,7 +616,7 @@ export class DriveService { file = await this.driveFilesRepository.insertOne(file); } catch (err) { - // duplicate key error (when already registered) + // duplicate key error (when already registered) if (isDuplicateKeyValueError(err)) { this.registerLogger.info(`already registered ${file.uri}`); diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index df67a728ef..be02274600 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -89,14 +89,28 @@ export class ServerService implements OnApplicationShutdown { fastify.addHook('onRequest', makeHstsHook(host, preload)); } + // Other Security/Privacy Headers + fastify.addHook('onRequest', (_, reply, done) => { + reply.header('x-content-type-options', 'nosniff'); + reply.header('permissions-policy', 'interest-cohort=()'); // Disable FLoC + if (this.config.browserSandboxing.strictOriginReferrer) { + reply.header('referrer-policy', 'strict-origin'); + } + done(); + }); + // CSP - if (process.env.NODE_ENV === 'production') { + if (process.env.NODE_ENV === 'production' && !this.config.browserSandboxing.csp?.disable) { console.debug('cspPrerenderedContent', this.config.cspPrerenderedContent); const generatedCSP = generateCSP(this.config.cspPrerenderedContent, { mediaProxy: this.config.mediaProxy ? `https://${new URL(this.config.mediaProxy).host}` : undefined, + script_src: [ + `https://${new URL(this.config.url).host}/embed_vite/`, + `https://${new URL(this.config.url).host}/vite/`, + ], }); fastify.addHook('onRequest', (_, reply, done) => { - reply.header('Content-Security-Policy', generatedCSP); + reply.header('content-security-policy', generatedCSP); done(); }); } diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index d30131a62f..816dc4e04d 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -19,6 +19,8 @@ import { Packed } from '@/misc/json-schema.js'; export const meta = { tags: ['admin'], + kind: 'write:admin:create-account', + errors: { accessDenied: { message: 'Access denied.', diff --git a/packages/backend/src/server/csp.ts b/packages/backend/src/server/csp.ts index 45b37fbd06..aeee4eab3a 100644 --- a/packages/backend/src/server/csp.ts +++ b/packages/backend/src/server/csp.ts @@ -13,6 +13,8 @@ export type CSPHashed = { export function generateCSP(hashedMap: Map, options: { mediaProxy?: string, + script_src?: string[], + append?: { [key: string]: string | string[] }, }) { const keys = Array.from(hashedMap.keys()); const scripts = keys @@ -22,7 +24,7 @@ export function generateCSP(hashedMap: Map, options: { .filter(name => name.endsWith('.css')) .map(name => `'${hashedMap.get(name)!.integrity}'`); - return ([ + const cpolicy = [ ['default-src', ['\'self\'']], ['img-src', [ @@ -42,7 +44,11 @@ export function generateCSP(hashedMap: Map, options: { // // ref: https://github.com/shikijs/shiki/issues/671 ['style-src-attr', ['\'self\'', '\'unsafe-inline\'']], - ['script-src', ['\'self\'', '\'wasm-unsafe-eval\'', ...scripts]], + ['script-src', [ + ...(options.script_src ? options.script_src : ['\'self\'']), + '\'wasm-unsafe-eval\'', + ...scripts + ]], ['object-src', ['\'none\'']], ['base-uri', ['\'self\'']], ['form-action', ['\'self\'']], @@ -52,7 +58,23 @@ export function generateCSP(hashedMap: Map, options: { [ ['upgrade-insecure-requests', []], ] : []), - ] as [string, string[]][]) + ] as [string, string[]][]; + + if (options.append) { + for (const [name, values] of Object.entries(options.append)) { + if (!values) { + continue; + } + const found = cpolicy.find(([n]) => n === name); + if (found) { + found[1].push(...(Array.isArray(values) ? values : [values])); + } else { + cpolicy.push([name, Array.isArray(values) ? values : [values]]); + } + } + } + + return cpolicy .map(([name, values]) => { return `${name} ${values.join(' ')}`; }).join('; '); diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index e2f227323f..b107fdc7f9 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -248,14 +248,28 @@ export class ClientServerService { fastify.addHook('onRequest', makeHstsHook(host, preload)); } + // Other Security/Privacy Headers + fastify.addHook('onRequest', (_, reply, done) => { + reply.header('x-content-type-options', 'nosniff'); + reply.header('permissions-policy', 'interest-cohort=()'); // Disable FLoC + if (this.config.browserSandboxing.strictOriginReferrer ?? true) { + reply.header('referrer-policy', 'strict-origin'); + } + done(); + }); + // CSP if (process.env.NODE_ENV === 'production') { console.debug('cspPrerenderedContent', this.config.cspPrerenderedContent); const generatedCSP = generateCSP(this.config.cspPrerenderedContent, { mediaProxy: this.config.mediaProxy ? `https://${new URL(this.config.mediaProxy).host}` : undefined, + script_src: [ + `https://${new URL(this.config.url).host}/embed_vite/`, + `https://${new URL(this.config.url).host}/vite/`, + ], }); fastify.addHook('onRequest', (_, reply, done) => { - reply.header('Content-Security-Policy', generatedCSP); + reply.header('content-security-policy', generatedCSP); done(); }); } diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 8ac48678ed..2457783311 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -2876,7 +2876,7 @@ type PartialRolePolicyOverride = Partial<{ }>; // @public (undocumented) -export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "write:admin:suspend-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"]; +export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:create-account", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "write:admin:suspend-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"]; // @public (undocumented) type PingResponse = operations['ping']['responses']['200']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index e2c7cbba52..236eb87131 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -88,7 +88,7 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *No* + * **Credential required**: *No* / **Permission**: *write:admin:create-account* */ request( endpoint: E, diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 5f9b4316f3..227a9c5377 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -85,7 +85,7 @@ export type paths = { * admin/accounts/create * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *No* / **Permission**: *write:admin:create-account* */ post: operations['admin___accounts___create']; }; @@ -5659,7 +5659,7 @@ export type operations = { * admin/accounts/create * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *No* / **Permission**: *write:admin:create-account* */ admin___accounts___create: { requestBody: { diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts index c5911a70eb..40cc44763f 100644 --- a/packages/misskey-js/src/consts.ts +++ b/packages/misskey-js/src/consts.ts @@ -64,6 +64,7 @@ export const permissions = [ 'read:flash-likes', 'write:flash-likes', 'read:admin:abuse-user-reports', + 'write:admin:create-account', 'write:admin:delete-account', 'write:admin:delete-all-files-of-a-user', 'read:admin:index-stats', -- 2.45.3 From fd271ea268aa63c8f05f379869f51f1944a25b7a Mon Sep 17 00:00:00 2001 From: eternal-flame-AD Date: Thu, 14 Nov 2024 02:50:44 -0600 Subject: [PATCH 07/23] Relax admin automated account registration Signed-off-by: eternal-flame-AD --- .../api/endpoints/admin/accounts/create.ts | 8 +- .../backend/test/e2e/admin-create-account.ts | 88 +++++++++++++++++++ 2 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 packages/backend/test/e2e/admin-create-account.ts diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index d30131a62f..29e1ddd5a0 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -15,18 +15,21 @@ import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { ApiError } from '@/server/api/error.js'; import { Packed } from '@/misc/json-schema.js'; +import { RoleService } from '@/core/RoleService.js'; export const meta = { tags: ['admin'], errors: { accessDenied: { + httpStatusCode: 403, message: 'Access denied.', code: 'ACCESS_DENIED', id: '1fb7cb09-d46a-4fff-b8df-057708cce513', }, wrongInitialPassword: { + httpStatusCode: 401, message: 'Initial password is incorrect.', code: 'INCORRECT_INITIAL_PASSWORD', id: '97147c55-1ae1-4f6f-91d6-e1c3e0e76d62', @@ -65,6 +68,7 @@ export default class extends Endpoint { // eslint- @Inject(DI.usersRepository) private usersRepository: UsersRepository, + private roleService: RoleService, private userEntityService: UserEntityService, private signupService: SignupService, private instanceActorService: InstanceActorService, @@ -85,8 +89,8 @@ export default class extends Endpoint { // eslint- // 初期パスワードが設定されていないのに初期パスワードが入力された場合 throw new ApiError(meta.errors.wrongInitialPassword); } - } else if ((realUsers && !me?.isRoot) || token !== null) { - // 初回セットアップではなく、管理者でない場合 or 外部トークンを使用している場合 + } else if (!(me?.isRoot) && !await this.roleService.isAdministrator(me)) { + // 管理者でない場合 throw new ApiError(meta.errors.accessDenied); } diff --git a/packages/backend/test/e2e/admin-create-account.ts b/packages/backend/test/e2e/admin-create-account.ts new file mode 100644 index 0000000000..357d624d3d --- /dev/null +++ b/packages/backend/test/e2e/admin-create-account.ts @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +process.env.NODE_ENV = 'test'; + +import * as assert from 'assert'; + +import type * as misskey from 'misskey-js'; +import { api, role, signup } from '../utils.js'; + +describe('Admin Create User', () => { + let admin: misskey.entities.SignupResponse; + let user: misskey.entities.SignupResponse; + let formerAdmin: misskey.entities.SignupResponse; + let adminRole : misskey.entities.Role; + let formerAdminRole : misskey.entities.Role; + + beforeAll(async () => { + admin = await signup({ username: 'admin' }); + formerAdmin = await signup({ username: 'former_admin' }); + user = await signup({ username: 'user' }); + adminRole = await role(admin, { + name: 'admin', + isAdministrator: true + }); + formerAdminRole = await role(admin, { + name: 'former_admin', + isAdministrator: true + }); + const addAdminRole = await api('admin/roles/assign', { + userId: admin.id, + roleId: adminRole.id + }, admin); + assert.strictEqual(addAdminRole.status, 204); + + const addFormerAdminRole = await api('admin/roles/assign', { + userId: formerAdmin.id, + roleId: formerAdminRole.id + }, admin); + assert.strictEqual(addFormerAdminRole.status, 204); + }, 1000 * 60 * 2); + + test('Create User', async () => { + const newUser1 = await api('admin/accounts/create', { + username: 'new_user1', + password: 'password', + }, admin); + assert.strictEqual(newUser1.status, 200); + + const newUser2 = await api('admin/accounts/create', { + username: 'new_user2', + password: 'password', + }, formerAdmin); + assert.strictEqual(newUser2.status, 200); + + const newUser3 = await api('admin/accounts/create', { + username: 'new_user3', + password: 'password', + }, user); + assert.strictEqual(newUser3.status, 403); + }); + + test('Revoking Admin Role', async () => { + const res = await api('admin/roles/delete', {roleId: formerAdminRole.id}, admin); + assert.strictEqual(res.status, 200); + + const res2 = await api('admin/roles/delete', {roleId: adminRole.id}, formerAdmin); + assert.strictEqual(res2.status, 403); + }); + + test('Revoked User Should Not Create User', async () => { + const newUser4 = await api('admin/accounts/create', { + username: 'new_user4', + password: 'password', + }, formerAdmin); + + assert.strictEqual(newUser4.status, 403); + + const newUser5 = await api('admin/accounts/create', { + username: 'new_user5', + password: 'password', + }, admin); + + assert.strictEqual(newUser5.status, 200); + }); +}) \ No newline at end of file -- 2.45.3 From 60eb7e1dc94d2abcf0f3767272d324b4e71e1c43 Mon Sep 17 00:00:00 2001 From: eternal-flame-AD Date: Thu, 14 Nov 2024 11:50:42 -0600 Subject: [PATCH 08/23] admin registration script Signed-off-by: eternal-flame-AD --- yume-mods/admin-scripts/.gitignore | 1 + yume-mods/admin-scripts/batch_register.fish | 79 +++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 yume-mods/admin-scripts/.gitignore create mode 100755 yume-mods/admin-scripts/batch_register.fish diff --git a/yume-mods/admin-scripts/.gitignore b/yume-mods/admin-scripts/.gitignore new file mode 100644 index 0000000000..956d4725fc --- /dev/null +++ b/yume-mods/admin-scripts/.gitignore @@ -0,0 +1 @@ +/secrets diff --git a/yume-mods/admin-scripts/batch_register.fish b/yume-mods/admin-scripts/batch_register.fish new file mode 100755 index 0000000000..1112fa9d7a --- /dev/null +++ b/yume-mods/admin-scripts/batch_register.fish @@ -0,0 +1,79 @@ +#!/usr/bin/env fish + +argparse --name batch_register 'n/number=' 'h/host=' 'm/mount=' -- $argv + +set -l dir_name (dirname (realpath (status -f))) + +echo "Running in $dir_name" + +if [ -f $dir_name/.env ] + source $dir_name/.env +end + +if [ -f $dir_name/secrets/.env ] + source $dir_name/secrets/.env +end + +if ! set -q MISSKEY_TOKEN + echo "Please set MISSKEY_TOKEN in .env file." + exit 1 +end + +if ! set -q VAULT_MOUNT + echo "Please set VAULT_MOUNT in .env file." + exit 1 +end + +if set -q _flag_host + set HOST $_flag_host +else + set HOST "https://test0.mi.yumechi.jp" +end + +if set -q _flag_number + set NUMBER $_flag_number +else + set NUMBER 1 +end + +if ! set -q ACCOUNT_PREFIX + set ACCOUNT_PREFIX test_automated_ +end + +for i in (seq 1 $NUMBER) + set -l USERNAME "$ACCOUNT_PREFIX"(openssl rand 8 | base64 | tr -d /+=) + set -l PASSWORD (openssl rand 16 | base64) + + echo "[$i/$NUMBER]" \ + "Registering $USERNAME" + + echo "$PASSWORD" | vault kv put -mount=$VAULT_MOUNT $USERNAME username=$USERNAME password=- + or begin + echo "Failed to save password for $USERNAME" + exit 3 + end + + set -l start (date +%s%N) + jq -nr --arg username $USERNAME --arg password $PASSWORD '{ + username: $username, + password: $password + }' | curl -f --tlsv1.2 -d@- \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $MISSKEY_TOKEN" \ + "$HOST/api/admin/accounts/create" | \ + jq --arg password $PASSWORD \ + '{"id": .id, "username": .username, "password": $password, "success": true, "token": .token}' | \ + tee /dev/stderr | \ + vault kv put -mount=$VAULT_MOUNT $USERNAME @/dev/stdin + or begin + echo "Failed to register $USERNAME" + vault kv destroy -mount=$VAULT_MOUNT -versions=1 $USERNAME + exit 2 + end + + echo "[$i/$NUMBER]" \ + "Registered $USERNAME" (math \((date +%s%N) - $start\) / 1000000) ms +end + +echo "Done." -- 2.45.3 From 7f8c8f62b1fab2b7f7ededeb7cb2dbc387895d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:33:09 +0900 Subject: [PATCH 09/23] =?UTF-8?q?fix(frontend):=20=E3=82=B9=E3=83=9E?= =?UTF-8?q?=E3=83=9B=E3=81=A7=E8=A1=A8=E7=A4=BA=E3=81=97=E3=81=9F=E6=99=82?= =?UTF-8?q?=E3=81=ABipv6=E3=81=A0=E3=81=A8=E3=81=AF=E3=81=BF=E5=87=BA?= =?UTF-8?q?=E3=81=A6=E3=81=97=E3=81=BE=E3=81=86=E3=81=AE=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=20(#14960)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): スマホで表示した時にipv6だとはみ出てしまうのを修正 (MisskeyIO#815) (cherry picked from commit aec01dd4adda8e975da523c5bea329120e689569) * Update Changelog --------- Co-authored-by: sleep-moe --- CHANGELOG.md | 2 ++ packages/frontend/src/pages/admin-user.vue | 1 + 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e07cdc00d7..f51fcb9d0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,8 @@ = Fix: ノート投稿ボタンにホバー時のスタイルが適用されていないのを修正 (Cherry-picked from https://github.com/taiyme/misskey/pull/305) - Fix: メールアドレス登録有効化時の「完了」ダイアログボックスの表示条件を修正 +- Fix: 画面幅が狭い環境でデザインが崩れる問題を修正 + (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/815) ### Server - Enhance: DockerのNode.jsを22.11.0に更新 diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue index 948e7a3cce..30d7e38638 100644 --- a/packages/frontend/src/pages/admin-user.vue +++ b/packages/frontend/src/pages/admin-user.vue @@ -627,6 +627,7 @@ definePageMetadata(() => ({