Merge remote-tracking branch 'misskey-original/develop' into develop
# Conflicts: # packages/backend/src/core/EmailService.ts # packages/frontend/src/components/MkSignupDialog.form.vue
This commit is contained in:
		
						commit
						09fbfee252
					
				|  | @ -15,22 +15,23 @@ | |||
| ## 2023.12.0 | ||||
| 
 | ||||
| ### Note | ||||
| - Node.js 20.10.0が最小要件になりました | ||||
| - 依存関係の更新に伴い、Node.js 20.10.0が最小要件になりました | ||||
| - 絵文字の追加辞書を既にインストールしている場合は、お手数ですが再インストールのほどお願いします | ||||
| - 絵文字ピッカーにピン留め表示する絵文字設定が「リアクション用」と「絵文字入力用」に分かれました。以前の設定は「リアクション用」として使用されます。   | ||||
| 
 | ||||
| 	**影響:**   | ||||
| 	それにより、投稿フォームから表示される絵文字ピッカーのピン留め絵文字がリセットされたように感じるかもしれません(新設された投稿用のピン留め絵文字が使われるため)。    | ||||
| 	それにより、投稿フォームから表示される絵文字ピッカーのピン留め絵文字がリセットされたように感じるかもしれません(新設された"ピン留め(全般)"の設定が使われるため)。    | ||||
| 	投稿用のピン留め絵文字をアップデート前の状態にするには、以下の手順で操作します。 | ||||
| 
 | ||||
| 	1. 「設定」メニューに移動し、「絵文字ピッカー」タブを選択します。 | ||||
| 	2. 「ピン留 (全般)」のタブを選択します。 | ||||
| 	3. 「リアクション設定からコピーする」ボタンを押すことで、アップデート前の状態に戻すことができます。 | ||||
| 	3. 「リアクション設定から上書きする」ボタンを押すことで、アップデート前の状態に戻すことができます。 | ||||
| 
 | ||||
| ### General | ||||
| - Feat: メールアドレスの認証にverifymail.ioを使えるように (cherry-pick from https://github.com/TeamNijimiss/misskey/commit/971ba07a44550f68d2ba31c62066db2d43a0caed) | ||||
| - Feat: モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能を追加 (cherry-pick from https://github.com/TeamNijimiss/misskey/commit/e0eb5a752f6e5616d6312bb7c9790302f9dbff83) | ||||
| - Feat: TL上からノートが見えなくなるワードミュートであるハードミュートを追加 | ||||
| - Enhance: 指定したドメインのメールアドレスの登録を弾くことができるように | ||||
| - Enhance: 公開ロールにアサインされたときに通知が作成されるように | ||||
| - Enhance: アイコンデコレーションを複数設定できるように | ||||
| - Enhance: アイコンデコレーションの位置を微調整できるように | ||||
|  | @ -71,6 +72,7 @@ | |||
| - Enhance: チャンネルに新規の投稿がある場合にバッジを表示させる | ||||
| - Enhance: サウンド設定に「サウンドを出力しない」と「Misskeyがアクティブな時のみサウンドを出力する」を追加 | ||||
| - Enhance: 設定したタグをトレンドに表示させないようにする項目を管理画面で設定できるように | ||||
| - Enhance: 絵文字ピッカーのカテゴリに「/」を入れることでフォルダ分け表示できるように | ||||
| - Fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正 | ||||
| - Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367 | ||||
| - Fix: コードエディタが正しく表示されない問題を修正 | ||||
|  | @ -241,7 +243,6 @@ | |||
| ### Client | ||||
| - Enhance: TLの返信表示オプションを記憶するように | ||||
| - Enhance: 投稿されてから時間が経過しているノートであることを視覚的に分かりやすく | ||||
| - Feat: 絵文字ピッカーのカテゴリに「/」を入れることでフォルダ分け表示できるように | ||||
| 
 | ||||
| ### Server | ||||
| - Enhance: タイムライン取得時のパフォーマンスを向上 | ||||
|  |  | |||
|  | @ -1917,6 +1917,7 @@ _notification: | |||
|     pollEnded: "Sondages se cloturant" | ||||
|     receiveFollowRequest: "Demande d'abonnement reçue" | ||||
|     followRequestAccepted: "Demande d'abonnement acceptée" | ||||
|     roleAssigned: "Rôle reçu" | ||||
|     achievementEarned: "Accomplissement" | ||||
|     app: "Notifications provenant des apps" | ||||
|   _actions: | ||||
|  |  | |||
|  | @ -1791,6 +1791,7 @@ export interface Locale { | |||
|         "disposable": string; | ||||
|         "mx": string; | ||||
|         "smtp": string; | ||||
|         "banned": string; | ||||
|     }; | ||||
|     "_ffVisibility": { | ||||
|         "public": string; | ||||
|  |  | |||
|  | @ -1698,6 +1698,7 @@ _emailUnavailable: | |||
|   disposable: "恒久的に使用可能なアドレスではありません" | ||||
|   mx: "正しいメールサーバーではありません" | ||||
|   smtp: "メールサーバーが応答しません" | ||||
|   banned: "このメールアドレスでは登録できません" | ||||
| 
 | ||||
| _ffVisibility: | ||||
|   public: "公開" | ||||
|  |  | |||
|  | @ -2193,6 +2193,7 @@ _notification: | |||
|     pollEnded: "투표가 종료됨" | ||||
|     receiveFollowRequest: "팔로우 요청을 받았을 때" | ||||
|     followRequestAccepted: "팔로우 요청이 승인되었을 때" | ||||
|     roleAssigned: "역할이 부여 됨" | ||||
|     achievementEarned: "도전 과제 획득" | ||||
|     app: "연동된 앱을 통한 알림" | ||||
|   _actions: | ||||
|  |  | |||
|  | @ -2193,6 +2193,7 @@ _notification: | |||
|     pollEnded: "問卷調查結束" | ||||
|     receiveFollowRequest: "已收到追隨請求" | ||||
|     followRequestAccepted: "追隨請求已接受" | ||||
|     roleAssigned: "已授予角色" | ||||
|     achievementEarned: "獲得成就" | ||||
|     app: "應用程式通知" | ||||
|   _actions: | ||||
|  |  | |||
|  | @ -0,0 +1,18 @@ | |||
| /* | ||||
|  * SPDX-FileCopyrightText: syuilo and other misskey contributors | ||||
|  * SPDX-License-Identifier: AGPL-3.0-only | ||||
|  */ | ||||
| 
 | ||||
| export class bannedEmailDomains1703209889304 { | ||||
| 		constructor() { | ||||
| 				this.name = 'bannedEmailDomains1703209889304'; | ||||
| 		} | ||||
| 
 | ||||
| 		async up(queryRunner) { | ||||
| 				await queryRunner.query(`ALTER TABLE "meta" ADD "bannedEmailDomains" character varying(1024) array NOT NULL DEFAULT '{}'`); | ||||
| 		} | ||||
| 
 | ||||
| 		async down(queryRunner) { | ||||
| 				await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "bannedEmailDomains"`); | ||||
| 		} | ||||
| } | ||||
|  | @ -9,6 +9,7 @@ import { Inject, Injectable } from '@nestjs/common'; | |||
| import { validate as validateEmail } from 'deep-email-validator'; | ||||
| import { SubOutputFormat } from 'deep-email-validator/dist/output/output.js'; | ||||
| import { MetaService } from '@/core/MetaService.js'; | ||||
| import { UtilityService } from '@/core/UtilityService.js'; | ||||
| import { DI } from '@/di-symbols.js'; | ||||
| import type { Config } from '@/config.js'; | ||||
| import type Logger from '@/logger.js'; | ||||
|  | @ -30,6 +31,7 @@ export class EmailService { | |||
| 
 | ||||
| 		private metaService: MetaService, | ||||
| 		private loggerService: LoggerService, | ||||
| 		private utilityService: UtilityService, | ||||
| 		private httpRequestService: HttpRequestService, | ||||
| 	) { | ||||
| 		this.logger = this.loggerService.getLogger('email'); | ||||
|  | @ -155,7 +157,7 @@ export class EmailService { | |||
| 	@bindThis | ||||
| 	public async validateEmailForAccount(emailAddress: string): Promise<{ | ||||
| 		available: boolean; | ||||
| 		reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp'; | ||||
| 		reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned'; | ||||
| 	}> { | ||||
| 		const meta = await this.metaService.fetch(); | ||||
| 
 | ||||
|  | @ -164,21 +166,20 @@ export class EmailService { | |||
| 			email: emailAddress, | ||||
| 		}); | ||||
| 
 | ||||
| 		const verifymailApi = meta.enableVerifymailApi && meta.verifymailAuthKey != null; | ||||
| 		let validated; | ||||
| 
 | ||||
| 		if (meta.enableActiveEmailValidation && meta.verifymailAuthKey) { | ||||
| 			if (verifymailApi) { | ||||
| 		if (meta.enableActiveEmailValidation) { | ||||
| 			if (meta.enableVerifymailApi && meta.verifymailAuthKey != null) { | ||||
| 				validated = await this.verifyMail(emailAddress, meta.verifymailAuthKey); | ||||
| 			} else { | ||||
| 				validated = meta.enableActiveEmailValidation ? await validateEmail({ | ||||
| 				validated = await validateEmail({ | ||||
| 					email: emailAddress, | ||||
| 					validateRegex: true, | ||||
| 					validateMx: true, | ||||
| 					validateTypo: false, // TLDを見ているみたいだけどclubとか弾かれるので
 | ||||
| 					validateDisposable: true, // 捨てアドかどうかチェック
 | ||||
| 					validateSMTP: false, // 日本だと25ポートが殆どのプロバイダーで塞がれていてタイムアウトになるので
 | ||||
| 				}) : { valid: true, reason: null }; | ||||
| 				}); | ||||
| 			} | ||||
| 		} else { | ||||
| 			validated = { valid: true, reason: null }; | ||||
|  | @ -187,9 +188,6 @@ export class EmailService { | |||
| 			const dispose = await this.httpRequestService.send('https://raw.githubusercontent.com/mattyatea/disposable-email-domains/master/disposable_email_blocklist.conf', { | ||||
| 				method: 'GET', | ||||
| 			}); | ||||
| 			const dispoes_2 = await this.httpRequestService.send('https://raw.githubusercontent.com/disposable/disposable-email-domains/master/domains_strict.txt', { | ||||
| 				method: 'GET', | ||||
| 			}); | ||||
| 			const disposableEmailDomains = (await dispose.text()).split('\n'); | ||||
| 			const domain = emailAddress.split('@')[1]; | ||||
| 			console.log(domain) | ||||
|  | @ -197,12 +195,17 @@ export class EmailService { | |||
| 				validated = { valid: false, reason: 'disposable' }; | ||||
| 			} | ||||
| 		} | ||||
| 		const available = exist === 0 && validated.valid; | ||||
| 
 | ||||
| 		const emailDomain: string = emailAddress.split('@')[1]; | ||||
| 		const isBanned = this.utilityService.isBlockedHost(meta.bannedEmailDomains, emailDomain); | ||||
| 
 | ||||
| 		const available = exist === 0 && validated.valid && !isBanned; | ||||
| 
 | ||||
| 		return { | ||||
| 			available, | ||||
| 			reason: available ? null : | ||||
| 			exist !== 0 ? 'used' : | ||||
| 			isBanned ? 'banned' : | ||||
| 			validated.reason === 'regex' ? 'format' : | ||||
| 			validated.reason === 'disposable' ? 'disposable' : | ||||
| 			validated.reason === 'mx' ? 'mx' : | ||||
|  |  | |||
|  | @ -262,7 +262,7 @@ export class NoteCreateService implements OnApplicationShutdown { | |||
| 		} | ||||
| 
 | ||||
| 		// Check blocking
 | ||||
| 		if (this.isQuote(data)) { | ||||
| 		if (data.renote && !this.isQuote(data)) { | ||||
| 			if (data.renote.userHost === null) { | ||||
| 				if (data.renote.userId !== user.id) { | ||||
| 					const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id); | ||||
|  |  | |||
|  | @ -510,6 +510,13 @@ export class MiMeta { | |||
| 	}) | ||||
| 	public manifestJsonOverride: string; | ||||
| 
 | ||||
| 	@Column('varchar', { | ||||
| 		length: 1024, | ||||
| 		array: true, | ||||
| 		default: '{}', | ||||
| 	}) | ||||
| 	public bannedEmailDomains: string[]; | ||||
| 
 | ||||
| 	@Column('varchar', { | ||||
| 		length: 1024, array: true, default: '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }', | ||||
| 	}) | ||||
|  |  | |||
|  | @ -145,6 +145,14 @@ export const meta = { | |||
| 					type: 'string', | ||||
| 				}, | ||||
| 			}, | ||||
| 			bannedEmailDomains: { | ||||
| 				type: 'array', | ||||
| 				optional: true, nullable: false, | ||||
| 				items: { | ||||
| 					type: 'string', | ||||
| 					optional: false, nullable: false, | ||||
| 				}, | ||||
| 			}, | ||||
| 			preservedUsernames: { | ||||
| 				type: 'array', | ||||
| 				optional: false, nullable: false, | ||||
|  | @ -518,6 +526,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 				enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances, | ||||
| 				enableServerMachineStats: instance.enableServerMachineStats, | ||||
| 				enableIdenticonGeneration: instance.enableIdenticonGeneration, | ||||
| 				bannedEmailDomains: instance.bannedEmailDomains, | ||||
| 				policies: { ...DEFAULT_POLICIES, ...instance.policies }, | ||||
| 				manifestJsonOverride: instance.manifestJsonOverride, | ||||
| 				enableFanoutTimeline: instance.enableFanoutTimeline, | ||||
|  |  | |||
|  | @ -124,6 +124,7 @@ export const paramDef = { | |||
| 		enableServerMachineStats: { type: 'boolean' }, | ||||
| 		enableIdenticonGeneration: { type: 'boolean' }, | ||||
| 		serverRules: { type: 'array', items: { type: 'string' } }, | ||||
| 		bannedEmailDomains: { type: 'array', items: { type: 'string' } }, | ||||
| 		preservedUsernames: { type: 'array', items: { type: 'string' } }, | ||||
| 		manifestJsonOverride: { type: 'string' }, | ||||
| 		enableFanoutTimeline: { type: 'boolean' }, | ||||
|  | @ -541,6 +542,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 				set.notesPerOneAd = ps.notesPerOneAd; | ||||
| 			} | ||||
| 
 | ||||
| 			if (ps.bannedEmailDomains !== undefined) { | ||||
| 				set.bannedEmailDomains = ps.bannedEmailDomains; | ||||
| 			} | ||||
| 
 | ||||
| 			const before = await this.metaService.fetch(true); | ||||
| 
 | ||||
| 			await this.metaService.update(set); | ||||
|  |  | |||
|  | @ -57,7 +57,8 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
|                 class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.format }}</span> | ||||
|             <span v-else-if="emailState === 'unavailable:disposable'" style="color: var(--error)"><i | ||||
|                 class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.disposable }}</span> | ||||
|             <span v-else-if="emailState === 'unavailable:mx'" style="color: var(--error)"><i | ||||
|             <span v-else-if="emailState === 'unavailable:banned'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.banned }}</span> | ||||
| 					<span v-else-if="emailState === 'unavailable:mx'" style="color: var(--error)"><i | ||||
|                 class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.mx }}</span> | ||||
|             <span v-else-if="emailState === 'unavailable:smtp'" style="color: var(--error)"><i | ||||
|                 class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.smtp }}</span> | ||||
|  | @ -149,7 +150,7 @@ const retypedPassword = ref<string>(''); | |||
| const invitationCode = ref<string>(''); | ||||
| const email = ref(''); | ||||
| const usernameState = ref<null | 'wait' | 'ok' | 'unavailable' | 'error' | 'invalid-format' | 'min-range' | 'max-range'>(null); | ||||
| const emailState = ref<null | 'wait' | 'ok' | 'unavailable:used' | 'unavailable:format' | 'unavailable:disposable' | 'unavailable:mx' | 'unavailable:smtp' | 'unavailable' | 'error'>(null); | ||||
| const emailState = ref<null | 'wait' | 'ok' | 'unavailable:used' | 'unavailable:format' | 'unavailable:disposable' | 'unavailable:banned' | 'unavailable:mx' | 'unavailable:smtp' | 'unavailable' | 'error'>(null); | ||||
| const passwordStrength = ref<'' | 'low' | 'medium' | 'high'>(''); | ||||
| const passwordRetypeState = ref<null | 'match' | 'not-match'>(null); | ||||
| const submitting = ref<boolean>(false); | ||||
|  | @ -248,7 +249,8 @@ function onChangeEmail(): void { | |||
|         result.reason === 'used' ? 'unavailable:used' : | ||||
|             result.reason === 'format' ? 'unavailable:format' : | ||||
|                 result.reason === 'disposable' ? 'unavailable:disposable' : | ||||
|                     result.reason === 'mx' ? 'unavailable:mx' : | ||||
|                     result.reason === 'banned' ? 'unavailable:banned' : | ||||
| 			result.reason === 'mx' ? 'unavailable:mx' : | ||||
|                         result.reason === 'smtp' ? 'unavailable:smtp' : | ||||
|                             'unavailable'; | ||||
|   }).catch((err) => { | ||||
|  |  | |||
|  | @ -74,15 +74,26 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 							<template #label>Enable</template> | ||||
| 						</MkSwitch> | ||||
| 						<MkSwitch v-model="enableVerifymailApi" @update:modelValue="save"> | ||||
| 							<template #label>Use Verifymail API</template> | ||||
| 							<template #label>Use Verifymail.io API</template> | ||||
| 						</MkSwitch> | ||||
| 						<MkInput v-model="verifymailAuthKey" @update:modelValue="save"> | ||||
| 							<template #prefix><i class="ti ti-key"></i></template> | ||||
| 							<template #label>Verifymail API Auth Key</template> | ||||
| 							<template #label>Verifymail.io API Auth Key</template> | ||||
| 						</MkInput> | ||||
| 					</div> | ||||
| 				</MkFolder> | ||||
| 
 | ||||
| 				<MkFolder> | ||||
| 					<template #label>Banned Email Domains</template> | ||||
| 
 | ||||
| 					<div class="_gaps_m"> | ||||
| 						<MkTextarea v-model="bannedEmailDomains"> | ||||
| 							<template #label>Banned Email Domains List</template> | ||||
| 						</MkTextarea> | ||||
| 						<MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> | ||||
| 					</div> | ||||
| 				</MkFolder> | ||||
| 
 | ||||
| 				<MkFolder> | ||||
| 					<template #label>Log IP address</template> | ||||
| 					<template v-if="enableIpLogging" #suffix>Enabled</template> | ||||
|  | @ -124,6 +135,7 @@ import FormSuspense from '@/components/form/suspense.vue'; | |||
| import MkRange from '@/components/MkRange.vue'; | ||||
| import MkInput from '@/components/MkInput.vue'; | ||||
| import MkButton from '@/components/MkButton.vue'; | ||||
| import MkTextarea from '@/components/MkTextarea.vue'; | ||||
| import * as os from '@/os.js'; | ||||
| import { fetchInstance } from '@/instance.js'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
|  | @ -141,6 +153,7 @@ const enableIpLogging = ref<boolean>(false); | |||
| const enableActiveEmailValidation = ref<boolean>(false); | ||||
| const enableVerifymailApi = ref<boolean>(false); | ||||
| const verifymailAuthKey = ref<string | null>(null); | ||||
| const bannedEmailDomains = ref<string>(''); | ||||
| 
 | ||||
| async function init() { | ||||
| 	const meta = await os.api('admin/meta'); | ||||
|  | @ -161,6 +174,7 @@ async function init() { | |||
| 	enableActiveEmailValidation.value = meta.enableActiveEmailValidation; | ||||
| 	enableVerifymailApi.value = meta.enableVerifymailApi; | ||||
| 	verifymailAuthKey.value = meta.verifymailAuthKey; | ||||
| 	bannedEmailDomains.value = meta.bannedEmailDomains.join('\n'); | ||||
| } | ||||
| 
 | ||||
| function save() { | ||||
|  | @ -180,6 +194,7 @@ function save() { | |||
| 		enableActiveEmailValidation: enableActiveEmailValidation.value, | ||||
| 		enableVerifymailApi: enableVerifymailApi.value, | ||||
| 		verifymailAuthKey: verifymailAuthKey.value, | ||||
| 		bannedEmailDomains: bannedEmailDomains.value.split('\n'), | ||||
| 	}).then(() => { | ||||
| 		fetchInstance(); | ||||
| 	}); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue