diff --git a/CHANGELOG.md b/CHANGELOG.md index 12f3c7593..3ccfd7389 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ ## Unreleased +### Note +- コントロールパネル内にあるサマリープロキシの設定個所がセキュリティから全般へ変更となります。 + ### General +- Enhance: URLプレビューの有効化・無効化を設定できるように #13569 +- Enhance: アンテナでBotによるノートを除外できるように + (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/545) - Fix: Play作成時に設定した公開範囲が機能していない問題を修正 ### Client @@ -23,6 +29,7 @@ ### Server - Enhance: エンドポイント`antennas/update`の必須項目を`antennaId`のみに +- Enhance: misskey-dev/summaly@5.1.0の取り込み(プレビュー生成処理の効率化) - Fix: フォローリクエストを作成する際に既存のものは削除するように (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/440) diff --git a/locales/en-US.yml b/locales/en-US.yml index 1fb581d8d..527beb471 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1212,6 +1212,10 @@ useGroupedNotifications: "Display grouped notifications" signupPendingError: "There was a problem verifying the email address. The link may have expired." cwNotationRequired: "If \"Hide content\" is enabled, a description must be provided." doReaction: "Add reaction" +wellKnownWebsites: "Well-known websites" +wellKnownWebsitesDescription: "Separate with spaces for AND, new lines for OR. Surround with slashes for regular expressions. Matching will allow redirection to external sites without a warning." +warningRedirectingExternalWebsiteTitle: "You are leaving our site!" +warningRedirectingExternalWebsiteDescription: "You are about to jump to another site.\nPlease make sure this link is reliable before proceeding.\n\n{url}" code: "Code" reloadRequiredToApplySettings: "Reloading is required to apply the settings." remainingN: "Remaining: {n}" diff --git a/locales/index.d.ts b/locales/index.d.ts index 44b24d83c..3e779465c 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -4860,6 +4860,25 @@ export interface Locale extends ILocale { * リアクションする */ "doReaction": string; + /** + * よく知られたウェブサイト + */ + "wellKnownWebsites": string; + /** + * スペースで区切るとAND指定になり、改行で区切るとOR指定になります。スラッシュで囲むと正規表現になります。一致した場合、外部サイトへのリダイレクトの警告を省略させることができます。 + */ + "wellKnownWebsitesDescription": string; + /** + * 外部サイトへ移動します + */ + "warningRedirectingExternalWebsiteTitle": string; + /** + * 別のサイトにジャンプしようとしています。 + * リンク先の安全性を十分に確認した上で進んでください。 + * + * {url} + */ + "warningRedirectingExternalWebsiteDescription": ParameterizedString<"url">; /** * サムネイルの表示を制限するURL */ @@ -4972,6 +4991,10 @@ export interface Locale extends ILocale { * リトライ */ "gameRetry": string; + /** + * 使用しない場合は空欄にしてください + */ + "notUsePleaseLeaveBlank": string; /** * 通報の種類 */ @@ -10024,6 +10047,60 @@ export interface Locale extends ILocale { */ "header": string; }; + "_urlPreviewSetting": { + /** + * URLプレビューの設定 + */ + "title": string; + /** + * URLプレビューを有効にする + */ + "enable": string; + /** + * プレビュー取得時のタイムアウト(ms) + */ + "timeout": string; + /** + * プレビュー取得の所要時間がこの値を超えた場合、プレビューは生成されません。 + */ + "timeoutDescription": string; + /** + * Content-Lengthの最大値(byte) + */ + "maximumContentLength": string; + /** + * Content-Lengthがこの値を超えた場合、プレビューは生成されません。 + */ + "maximumContentLengthDescription": string; + /** + * Content-Lengthが取得できた場合のみプレビューを生成 + */ + "requireContentLength": string; + /** + * 相手サーバがContent-Lengthを返さない場合、プレビューは生成されません。 + */ + "requireContentLengthDescription": string; + /** + * User-Agent + */ + "userAgent": string; + /** + * プレビュー取得時に使用されるUser-Agentを設定します。空欄の場合、デフォルトのUser-Agentが使用されます。 + */ + "userAgentDescription": string; + /** + * プレビューを生成するプロキシのエンドポイント + */ + "summaryProxy": string; + /** + * Misskey本体ではなく、サマリープロキシを使用してプレビューを生成します。 + */ + "summaryProxyDescription": string; + /** + * プロキシには下記パラメータがクエリ文字列として連携されます。プロキシ側がこれらをサポートしない場合、設定値は無視されます。 + */ + "summaryProxyDescription2": string; + }; } declare const locales: { [lang: string]: Locale; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index fd84e0318..525fdaeac 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1211,6 +1211,10 @@ useGroupedNotifications: "通知をグルーピングして表示する" signupPendingError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。" cwNotationRequired: "「内容を隠す」がオンの場合は注釈の記述が必要です。" doReaction: "リアクションする" +wellKnownWebsites: "よく知られたウェブサイト" +wellKnownWebsitesDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。スラッシュで囲むと正規表現になります。一致した場合、外部サイトへのリダイレクトの警告を省略させることができます。" +warningRedirectingExternalWebsiteTitle: "外部サイトへ移動します" +warningRedirectingExternalWebsiteDescription: "別のサイトにジャンプしようとしています。\nリンク先の安全性を十分に確認した上で進んでください。\n\n{url}" urlPreviewDenyList: "サムネイルの表示を制限するURL" urlPreviewDenyListDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。スラッシュで囲むと正規表現になります。一致した場合、サムネイルがぼかされて表示されます。" code: "コード" @@ -1239,6 +1243,7 @@ enableHorizontalSwipe: "スワイプしてタブを切り替える" loading: "読み込み中" surrender: "やめる" gameRetry: "リトライ" +notUsePleaseLeaveBlank: "使用しない場合は空欄にしてください" abuseReportCategory: "通報の種類" selectCategory: "カテゴリを選択" reportComplete: "通報完了" @@ -2671,3 +2676,17 @@ _offlineScreen: title: "オフライン - サーバーに接続できません" header: "サーバーに接続できません" +_urlPreviewSetting: + title: "URLプレビューの設定" + enable: "URLプレビューを有効にする" + timeout: "プレビュー取得時のタイムアウト(ms)" + timeoutDescription: "プレビュー取得の所要時間がこの値を超えた場合、プレビューは生成されません。" + maximumContentLength: "Content-Lengthの最大値(byte)" + maximumContentLengthDescription: "Content-Lengthがこの値を超えた場合、プレビューは生成されません。" + requireContentLength: "Content-Lengthが取得できた場合のみプレビューを生成" + requireContentLengthDescription: "相手サーバがContent-Lengthを返さない場合、プレビューは生成されません。" + userAgent: "User-Agent" + userAgentDescription: "プレビュー取得時に使用されるUser-Agentを設定します。空欄の場合、デフォルトのUser-Agentが使用されます。" + summaryProxy: "プレビューを生成するプロキシのエンドポイント" + summaryProxyDescription: "Misskey本体ではなく、サマリープロキシを使用してプレビューを生成します。" + summaryProxyDescription2: "プロキシには下記パラメータがクエリ文字列として連携されます。プロキシ側がこれらをサポートしない場合、設定値は無視されます。" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 5b3d5538a..e105a0df1 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1209,6 +1209,10 @@ useGroupedNotifications: "알림을 그룹화하고 표시" signupPendingError: "메일 주소 확인중에 문제가 발생했습니다. 링크의 유효기간이 지났을 가능성이 있습니다." cwNotationRequired: "'내용을 숨기기'를 체크한 경우 주석을 써야 합니다." doReaction: "리액션 추가" +wellKnownWebsites: "잘 알려진 웹사이트" +wellKnownWebsitesDescription: "공백으로 구분하면 AND 지정이 되며, 개행으로 구분하면 OR 지정이 됩니다. 슬래시로 둘러싸면 정규 표현식이 됩니다. 일치하는 경우, 외부 사이트로의 리다이렉트 경고를 생략할 수 있습니다." +warningRedirectingExternalWebsiteTitle: "외부 사이트로 이동합니다" +warningRedirectingExternalWebsiteDescription: "다른 사이트로 이동하려고 합니다.\n링크가 안전한지 충분히 확인한 후 이동해주세요.\n\n{url}" code: "문자열" reloadRequiredToApplySettings: "설정을 적용하려면 새로고침을 해야 합니다." remainingN: "나머지: {n}" diff --git a/package.json b/package.json index a548e0cde..cfa2cef82 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2024.3.1-host.2b", + "version": "2024.3.1-host.2c", "codename": "nasubi", "repository": { "type": "git", @@ -52,19 +52,19 @@ "sharp": "0.33.2" }, "dependencies": { - "cssnano": "6.1.0", + "cssnano": "6.1.1", "execa": "8.0.1", "js-yaml": "4.1.0", - "postcss": "8.4.37", + "postcss": "8.4.38", "terser": "5.29.2", - "typescript": "5.4.2" + "typescript": "5.4.3" }, "devDependencies": { "@types/node": "20.11.30", "@typescript-eslint/eslint-plugin": "7.3.1", "@typescript-eslint/parser": "7.3.1", "cross-env": "7.0.3", - "cypress": "13.7.0", + "cypress": "13.7.1", "eslint": "8.57.0", "ncp": "2.0.0", "start-server-and-test": "2.0.3" diff --git a/packages/backend/migration/1710512074000-url-preview-meta.js b/packages/backend/migration/1710512074000-url-preview-meta.js new file mode 100644 index 000000000..8af521bbf --- /dev/null +++ b/packages/backend/migration/1710512074000-url-preview-meta.js @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class UrlPreviewMeta1710512074000 { + name = 'UrlPreviewMeta1710512074000' + + async up(queryRunner) { + await queryRunner.query(` + alter table meta + rename column "summalyProxy" to "urlPreviewSummaryProxyUrl"; + alter table meta + add "urlPreviewEnabled" boolean default true not null; + alter table meta + add "urlPreviewTimeout" integer default 10000 not null; + alter table meta + add "urlPreviewMaximumContentLength" bigint default 10485760 not null; + alter table meta + add "urlPreviewRequireContentLength" boolean default false not null; + alter table meta + add "urlPreviewUserAgent" varchar(1024) default null; + `); + } + + async down(queryRunner) { + await queryRunner.query(` + alter table meta + rename column "urlPreviewSummaryProxyUrl" to "summalyProxy"; + alter table meta + drop column "urlPreviewEnabled"; + alter table meta + drop column "urlPreviewTimeout"; + alter table meta + drop column "urlPreviewMaximumContentLength"; + alter table meta + drop column "urlPreviewRequireContentLength"; + alter table meta + drop column "urlPreviewUserAgent"; + `); + } +} diff --git a/packages/backend/migration/1711008460816-external-website-warn.js b/packages/backend/migration/1711008460816-external-website-warn.js new file mode 100644 index 000000000..6e6ae75ef --- /dev/null +++ b/packages/backend/migration/1711008460816-external-website-warn.js @@ -0,0 +1,11 @@ +export class ExternalWebsiteWarn1711008460816 { + name = 'ExternalWebsiteWarn1711008460816' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "wellKnownWebsites" character varying(3072) array NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "wellKnownWebsites"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index 9db75f49a..9884d044b 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -78,7 +78,7 @@ "@fastify/express": "2.3.0", "@fastify/formbody": "7.4.0", "@fastify/http-proxy": "9.5.0", - "@fastify/multipart": "8.1.0", + "@fastify/multipart": "8.2.0", "@fastify/static": "7.0.1", "@fastify/view": "9.0.0", "@misskey-dev/sharp-read-bmp": "1.2.0", @@ -100,7 +100,7 @@ "bcryptjs": "2.4.3", "blurhash": "2.0.5", "body-parser": "1.20.2", - "bullmq": "5.4.3", + "bullmq": "5.4.4", "cacheable-lookup": "7.0.0", "cbor": "9.0.2", "chalk": "5.3.0", @@ -144,7 +144,7 @@ "nested-property": "4.0.0", "node-fetch": "3.3.2", "node-forge": "1.3.1", - "nodemailer": "6.9.12", + "nodemailer": "6.9.13", "nsfwjs": "2.4.2", "oauth": "0.10.0", "oauth2orize": "1.12.0", @@ -154,7 +154,7 @@ "parse5": "7.1.2", "pg": "8.11.3", "pino": "8.19.0", - "pino-pretty": "10.3.1", + "pino-pretty": "11.0.0", "pkce-challenge": "4.1.0", "probe-image-size": "7.2.3", "promise-limit": "2.7.0", @@ -171,7 +171,7 @@ "rss-parser": "3.13.0", "rxjs": "7.8.1", "samlify": "2.8.11", - "sanitize-html": "2.12.1", + "sanitize-html": "2.13.0", "secure-json-parse": "2.7.0", "sharp": "0.33.2", "slacc": "0.0.10", @@ -183,7 +183,7 @@ "tsc-alias": "1.8.8", "tsconfig-paths": "4.2.0", "typeorm": "0.3.20", - "typescript": "5.4.2", + "typescript": "5.4.3", "ulid": "2.3.0", "vary": "1.1.2", "web-push": "3.6.7", @@ -219,7 +219,7 @@ "@types/oauth": "0.9.4", "@types/oauth2orize": "1.11.4", "@types/oauth2orize-pkce": "0.1.2", - "@types/pg": "8.11.3", + "@types/pg": "8.11.4", "@types/pug": "2.0.10", "@types/punycode": "2.1.4", "@types/qrcode": "1.5.5", diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index a7e32e116..daabc41c9 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -113,6 +113,8 @@ type Source = { proxyRemoteFiles?: boolean; videoThumbnailGenerator?: string; + bypassRateLimit?: { header: string; value: string }[]; + signToActivityPubGet?: boolean; perChannelMaxNoteCacheCount?: number; @@ -205,6 +207,7 @@ export type Config = { mediaProxy: string; externalMediaProxyEnabled: boolean; videoThumbnailGenerator: string | null; + bypassRateLimit: { header: string; value: string }[] | undefined; redis: RedisOptions & RedisOptionsSource; redisForPubsub: RedisOptions & RedisOptionsSource; redisForSystemQueue: RedisOptions & RedisOptionsSource; @@ -319,6 +322,7 @@ export function loadConfig(): Config { videoThumbnailGenerator: config.videoThumbnailGenerator ? config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator : null, + bypassRateLimit: config.bypassRateLimit, userAgent: `Misskey/${version} (${config.url})`, clientEntry: clientManifest['src/_boot_.ts'], clientManifestExists: clientManifestExists, diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts index f615c8747..30f004a33 100644 --- a/packages/backend/src/core/entities/MetaEntityService.ts +++ b/packages/backend/src/core/entities/MetaEntityService.ts @@ -99,6 +99,7 @@ export class MetaEntityService { imageUrl: ad.imageUrl, dayOfWeek: ad.dayOfWeek, })), + wellKnownWebsites: instance.wellKnownWebsites, notesPerOneAd: instance.notesPerOneAd, enableEmail: instance.enableEmail, enableServiceWorker: instance.enableServiceWorker, @@ -110,6 +111,7 @@ export class MetaEntityService { policies: { ...DEFAULT_POLICIES, ...instance.policies }, mediaProxy: this.config.mediaProxy, + enableUrlPreview: instance.urlPreviewEnabled, }; } diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index a012c47c1..63c29834e 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -272,12 +272,6 @@ export class MiMeta { }) public enableSensitiveMediaDetectionForVideos: boolean; - @Column('varchar', { - length: 1024, - nullable: true, - }) - public summalyProxy: string | null; - @Column('boolean', { default: false, }) @@ -512,6 +506,11 @@ export class MiMeta { }) public notesPerOneAd: number; + @Column('varchar', { + length: 3072, array: true, default: '{}', + }) + public wellKnownWebsites: string[]; + @Column('varchar', { length: 3072, array: true, default: '{}', }) @@ -521,4 +520,36 @@ export class MiMeta { length: 1024, array: true, default: '{}', }) public featuredGameChannels: string[]; + + @Column('boolean', { + default: true, + }) + public urlPreviewEnabled: boolean; + + @Column('integer', { + default: 10000, + }) + public urlPreviewTimeout: number; + + @Column('bigint', { + default: 1024 * 1024 * 10, + }) + public urlPreviewMaximumContentLength: number; + + @Column('boolean', { + default: true, + }) + public urlPreviewRequireContentLength: boolean; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public urlPreviewSummaryProxyUrl: string | null; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public urlPreviewUserAgent: string | null; } diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts index 150b94a18..633cc1cc0 100644 --- a/packages/backend/src/models/json-schema/meta.ts +++ b/packages/backend/src/models/json-schema/meta.ts @@ -183,6 +183,14 @@ export const packedMetaLiteSchema = { }, }, }, + wellKnownWebsites: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, notesPerOneAd: { type: 'number', optional: false, nullable: false, @@ -204,6 +212,10 @@ export const packedMetaLiteSchema = { type: 'string', optional: false, nullable: false, }, + enableUrlPreview: { + type: 'boolean', + optional: false, nullable: false, + }, backgroundImageUrl: { type: 'string', optional: false, nullable: true, diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index d8e792051..cd15721b0 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -18,6 +18,7 @@ import { createTemp } from '@/misc/create-temp.js'; import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; +import type { Config } from '@/config.js'; import { ApiError } from './error.js'; import { RateLimiterService } from './RateLimiterService.js'; import { ApiLoggerService } from './ApiLoggerService.js'; @@ -39,6 +40,8 @@ export class ApiCallService implements OnApplicationShutdown { private userIpHistoriesClearIntervalId: NodeJS.Timeout; constructor( + @Inject(DI.config) + private config: Config, @Inject(DI.userIpsRepository) private userIpsRepository: UserIpsRepository, @@ -243,7 +246,8 @@ export class ApiCallService implements OnApplicationShutdown { throw new ApiError(accessDenied); } - if (ep.meta.limit) { + const bypassRateLimit = this.config.bypassRateLimit?.some(({ header, value }) => request.headers[header] === value) ?? false; + if (ep.meta.limit && !bypassRateLimit) { // koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app. let limitActor: string; if (user) { diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index ba5c3dd21..f9ee6a016 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -325,9 +325,17 @@ export const meta = { type: 'number', optional: false, nullable: false, }, + wellKnownWebsites: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, urlPreviewDenyList: { type: 'array', - optional: true, nullable: false, + optional: false, nullable: false, items: { type: 'string', optional: false, nullable: false, @@ -399,6 +407,8 @@ export const meta = { summalyProxy: { type: 'string', optional: false, nullable: true, + deprecated: true, + description: '[Deprecated] Use "urlPreviewSummaryProxyUrl" instead.', }, themeColor: { type: 'string', @@ -416,6 +426,30 @@ export const meta = { type: 'string', optional: false, nullable: false, }, + urlPreviewEnabled: { + type: 'boolean', + optional: false, nullable: false, + }, + urlPreviewTimeout: { + type: 'number', + optional: false, nullable: false, + }, + urlPreviewMaximumContentLength: { + type: 'number', + optional: false, nullable: false, + }, + urlPreviewRequireContentLength: { + type: 'boolean', + optional: false, nullable: false, + }, + urlPreviewUserAgent: { + type: 'string', + optional: false, nullable: true, + }, + urlPreviewSummaryProxyUrl: { + type: 'string', + optional: false, nullable: true, + }, }, }, } as const; @@ -497,7 +531,6 @@ export default class extends Endpoint { // eslint- setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically, enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos, proxyAccountId: instance.proxyAccountId, - summalyProxy: instance.summalyProxy, email: instance.email, smtpSecure: instance.smtpSecure, smtpHost: instance.smtpHost, @@ -527,9 +560,17 @@ export default class extends Endpoint { // eslint- perRemoteUserUserTimelineCacheMax: instance.perRemoteUserUserTimelineCacheMax, perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax, perUserListTimelineCacheMax: instance.perUserListTimelineCacheMax, + wellKnownWebsites: instance.wellKnownWebsites, notesPerOneAd: instance.notesPerOneAd, urlPreviewDenyList: instance.urlPreviewDenyList, featuredGameChannels: instance.featuredGameChannels, + summalyProxy: instance.urlPreviewSummaryProxyUrl, + urlPreviewEnabled: instance.urlPreviewEnabled, + urlPreviewTimeout: instance.urlPreviewTimeout, + urlPreviewMaximumContentLength: instance.urlPreviewMaximumContentLength, + urlPreviewRequireContentLength: instance.urlPreviewRequireContentLength, + urlPreviewUserAgent: instance.urlPreviewUserAgent, + urlPreviewSummaryProxyUrl: instance.urlPreviewSummaryProxyUrl, }; }); } diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 9efc7e254..6c393194e 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -88,7 +88,6 @@ export const paramDef = { type: 'string', }, }, - summalyProxy: { type: 'string', nullable: true }, deeplAuthKey: { type: 'string', nullable: true }, deeplIsPro: { type: 'boolean' }, enableEmail: { type: 'boolean' }, @@ -140,6 +139,11 @@ export const paramDef = { type: 'string', }, }, + wellKnownWebsites: { + type: 'array', nullable: true, items: { + type: 'string', + }, + }, urlPreviewDenyList: { type: 'array', nullable: true, items: { type: 'string', @@ -150,6 +154,16 @@ export const paramDef = { type: 'string', }, }, + summalyProxy: { + type: 'string', nullable: true, + description: '[Deprecated] Use "urlPreviewSummaryProxyUrl" instead.', + }, + urlPreviewEnabled: { type: 'boolean' }, + urlPreviewTimeout: { type: 'integer' }, + urlPreviewMaximumContentLength: { type: 'integer' }, + urlPreviewRequireContentLength: { type: 'boolean' }, + urlPreviewUserAgent: { type: 'string', nullable: true }, + urlPreviewSummaryProxyUrl: { type: 'string', nullable: true }, }, required: [], } as const; @@ -205,6 +219,10 @@ export default class extends Endpoint { // eslint- }).map(x => x.toLowerCase()); } + if (Array.isArray(ps.wellKnownWebsites)) { + set.wellKnownWebsites = ps.wellKnownWebsites.filter(Boolean); + } + if (Array.isArray(ps.urlPreviewDenyList)) { set.urlPreviewDenyList = ps.urlPreviewDenyList.filter(Boolean); } @@ -365,10 +383,6 @@ export default class extends Endpoint { // eslint- set.langs = ps.langs.filter(Boolean); } - if (ps.summalyProxy !== undefined) { - set.summalyProxy = ps.summalyProxy; - } - if (ps.enableEmail !== undefined) { set.enableEmail = ps.enableEmail; } @@ -541,6 +555,32 @@ export default class extends Endpoint { // eslint- set.bannedEmailDomains = ps.bannedEmailDomains; } + if (ps.urlPreviewEnabled !== undefined) { + set.urlPreviewEnabled = ps.urlPreviewEnabled; + } + + if (ps.urlPreviewTimeout !== undefined) { + set.urlPreviewTimeout = ps.urlPreviewTimeout; + } + + if (ps.urlPreviewMaximumContentLength !== undefined) { + set.urlPreviewMaximumContentLength = ps.urlPreviewMaximumContentLength; + } + + if (ps.urlPreviewRequireContentLength !== undefined) { + set.urlPreviewRequireContentLength = ps.urlPreviewRequireContentLength; + } + + if (ps.urlPreviewUserAgent !== undefined) { + const value = (ps.urlPreviewUserAgent ?? '').trim(); + set.urlPreviewUserAgent = value === '' ? null : ps.urlPreviewUserAgent; + } + + if (ps.summalyProxy !== undefined || ps.urlPreviewSummaryProxyUrl !== undefined) { + const value = ((ps.urlPreviewSummaryProxyUrl ?? ps.summalyProxy) ?? '').trim(); + set.urlPreviewSummaryProxyUrl = value === '' ? null : value; + } + const before = await this.metaService.fetch(true); await this.metaService.update(set); diff --git a/packages/backend/src/server/sso/JWTIdentifyProviderService.ts b/packages/backend/src/server/sso/JWTIdentifyProviderService.ts index 182b6af04..2671a7fcb 100644 --- a/packages/backend/src/server/sso/JWTIdentifyProviderService.ts +++ b/packages/backend/src/server/sso/JWTIdentifyProviderService.ts @@ -172,7 +172,9 @@ export class JWTIdentifyProviderService { const roles = await this.roleService.getUserRoles(user.id); const payload: JWTPayload = { - name: user.name ?? user.username, + name: user.name ? `${user.name} (@${user.username})` : `@${user.username}`, + given_name: user.name ?? undefined, + family_name: `@${user.username}`, preferred_username: user.username, profile: `${this.config.url}/@${user.username}`, picture: user.avatarUrl ?? undefined, diff --git a/packages/backend/src/server/sso/SAMLIdentifyProviderService.ts b/packages/backend/src/server/sso/SAMLIdentifyProviderService.ts index 96c32291f..15ca1eecc 100644 --- a/packages/backend/src/server/sso/SAMLIdentifyProviderService.ts +++ b/packages/backend/src/server/sso/SAMLIdentifyProviderService.ts @@ -492,20 +492,28 @@ export class SAMLIdentifyProviderService { '#text': user.id, }, }, - { - '@Name': 'displayname', + ...(user.name ? [{ + '@Name': 'firstName', '@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', 'saml:AttributeValue': { '@xsi:type': 'xs:string', - '#text': user.name ?? user.username, + '#text': user.name, + }, + }] : []), + { + '@Name': 'lastName', + '@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', + 'saml:AttributeValue': { + '@xsi:type': 'xs:string', + '#text': `@${user.username}`, }, }, { - '@Name': 'name', + '@Name': 'displayName', '@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', 'saml:AttributeValue': { '@xsi:type': 'xs:string', - '#text': user.username, + '#text': user.name ? `${user.name} (@${user.username})` : `@${user.username}`, }, }, { diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 2bbae9996..0000ee783 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -4,8 +4,9 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import { summaly } from '@misskey-dev/summaly'; import RE2 from 're2'; +import { summaly } from '@misskey-dev/summaly'; +import { SummalyResult } from '@misskey-dev/summaly/built/summary.js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; @@ -15,6 +16,7 @@ import { query } from '@/misc/prelude/url.js'; import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; import { ApiError } from '@/server/api/error.js'; +import { MiMeta } from '@/models/Meta.js'; import type { FastifyRequest, FastifyReply } from 'fastify'; @Injectable() @@ -63,24 +65,25 @@ export class UrlPreviewService { const meta = await this.metaService.fetch(); - this.logger.info(meta.summalyProxy + if (!meta.urlPreviewEnabled) { + reply.code(403); + return { + error: new ApiError({ + message: 'URL preview is disabled', + code: 'URL_PREVIEW_DISABLED', + id: '58b36e13-d2f5-0323-b0c6-76aa9dabefb8', + }), + }; + } + + this.logger.info(meta.urlPreviewSummaryProxyUrl ? `(Proxy) Getting preview of ${url}@${lang} ...` : `Getting preview of ${url}@${lang} ...`); + try { - const summary = meta.summalyProxy ? - await this.httpRequestService.getJson>(`${meta.summalyProxy}?${query({ - url: url, - lang: lang ?? 'ja-JP', - })}`) - : - await summaly(url, { - followRedirects: false, - lang: lang ?? 'ja-JP', - agent: this.config.proxy ? { - http: this.httpRequestService.httpAgent, - https: this.httpRequestService.httpsAgent, - } : undefined, - }); + const summary = meta.urlPreviewSummaryProxyUrl + ? await this.fetchSummaryFromProxy(url, meta, lang) + : await this.fetchSummary(url, meta, lang); this.logger.succ(`Got preview of ${url}: ${summary.title}`); @@ -118,6 +121,7 @@ export class UrlPreviewService { return summary; } catch (err) { this.logger.warn(`Failed to get preview of ${url}: ${err}`); + reply.code(422); reply.header('Cache-Control', 'max-age=86400, immutable'); return { @@ -129,4 +133,37 @@ export class UrlPreviewService { }; } } + + private fetchSummary(url: string, meta: MiMeta, lang?: string): Promise { + const agent = this.config.proxy + ? { + http: this.httpRequestService.httpAgent, + https: this.httpRequestService.httpsAgent, + } + : undefined; + + return summaly(url, { + followRedirects: false, + lang: lang ?? 'ja-JP', + agent: agent, + userAgent: meta.urlPreviewUserAgent ?? undefined, + operationTimeout: meta.urlPreviewTimeout, + contentLengthLimit: meta.urlPreviewMaximumContentLength, + contentLengthRequired: meta.urlPreviewRequireContentLength, + }); + } + + private fetchSummaryFromProxy(url: string, meta: MiMeta, lang?: string): Promise { + const proxy = meta.urlPreviewSummaryProxyUrl!; + const queryStr = query({ + url: url, + lang: lang ?? 'ja-JP', + userAgent: meta.urlPreviewUserAgent ?? undefined, + operationTimeout: meta.urlPreviewTimeout, + contentLengthLimit: meta.urlPreviewMaximumContentLength, + contentLengthRequired: meta.urlPreviewRequireContentLength, + }); + + return this.httpRequestService.getJson(`${proxy}?${queryStr}`); + } } diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 6299973a1..4a2469849 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -41,7 +41,7 @@ "chartjs-chart-matrix": "2.0.1", "chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-zoom": "2.0.1", - "chromatic": "11.1.1", + "chromatic": "11.2.0", "compare-versions": "6.1.0", "cropperjs": "2.0.0-beta.4", "date-fns": "3.6.0", @@ -60,7 +60,7 @@ "photoswipe": "5.4.3", "punycode": "2.3.1", "rollup": "4.13.0", - "sanitize-html": "2.12.1", + "sanitize-html": "2.13.0", "sass": "1.72.0", "shiki": "1.2.0", "strict-event-emitter-types": "2.0.0", @@ -70,34 +70,34 @@ "tinycolor2": "1.6.0", "tsc-alias": "1.8.8", "tsconfig-paths": "4.2.0", - "typescript": "5.4.2", + "typescript": "5.4.3", "uuid": "9.0.1", - "v-code-diff": "1.10.0", - "vite": "5.1.6", + "v-code-diff": "1.11.0", + "vite": "5.2.2", "vue": "3.4.15", "vuedraggable": "next" }, "devDependencies": { "@misskey-dev/eslint-plugin": "1.0.0", "@misskey-dev/summaly": "5.1.0", - "@storybook/addon-actions": "8.0.2", - "@storybook/addon-essentials": "8.0.2", - "@storybook/addon-interactions": "8.0.2", - "@storybook/addon-links": "8.0.2", - "@storybook/addon-mdx-gfm": "8.0.2", - "@storybook/addon-storysource": "8.0.2", - "@storybook/blocks": "8.0.2", - "@storybook/components": "8.0.2", - "@storybook/core-events": "8.0.2", - "@storybook/manager-api": "8.0.2", - "@storybook/preview-api": "8.0.2", - "@storybook/react": "8.0.2", - "@storybook/react-vite": "8.0.2", - "@storybook/test": "8.0.2", - "@storybook/theming": "8.0.2", - "@storybook/types": "8.0.2", - "@storybook/vue3": "8.0.2", - "@storybook/vue3-vite": "8.0.2", + "@storybook/addon-actions": "8.0.4", + "@storybook/addon-essentials": "8.0.4", + "@storybook/addon-interactions": "8.0.4", + "@storybook/addon-links": "8.0.4", + "@storybook/addon-mdx-gfm": "8.0.4", + "@storybook/addon-storysource": "8.0.4", + "@storybook/blocks": "8.0.4", + "@storybook/components": "8.0.4", + "@storybook/core-events": "8.0.4", + "@storybook/manager-api": "8.0.4", + "@storybook/preview-api": "8.0.4", + "@storybook/react": "8.0.4", + "@storybook/react-vite": "8.0.4", + "@storybook/test": "8.0.4", + "@storybook/theming": "8.0.4", + "@storybook/types": "8.0.4", + "@storybook/vue3": "8.0.4", + "@storybook/vue3-vite": "8.0.4", "@testing-library/vue": "8.0.3", "@types/escape-regexp": "0.0.3", "@types/estree": "1.0.5", @@ -116,7 +116,7 @@ "@vue/runtime-core": "3.4.15", "acorn": "8.11.3", "cross-env": "7.0.3", - "cypress": "13.7.0", + "cypress": "13.7.1", "eslint": "8.57.0", "eslint-plugin-import": "2.29.1", "eslint-plugin-vue": "9.23.0", @@ -131,13 +131,13 @@ "react": "18.2.0", "react-dom": "18.2.0", "start-server-and-test": "2.0.3", - "storybook": "8.0.2", + "storybook": "8.0.4", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", "vite-plugin-turbosnap": "1.0.3", "vitest": "0.34.6", "vitest-fetch-mock": "0.2.2", - "vue-component-type-helpers": "2.0.6", + "vue-component-type-helpers": "2.0.7", "vue-eslint-parser": "9.4.2", - "vue-tsc": "2.0.6" + "vue-tsc": "2.0.7" } } diff --git a/packages/frontend/src/components/MkLink.vue b/packages/frontend/src/components/MkLink.vue index 3f7aba2fe..c425ba08a 100644 --- a/packages/frontend/src/components/MkLink.vue +++ b/packages/frontend/src/components/MkLink.vue @@ -5,8 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only