diff options
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | locales/index.d.ts | 1 | ||||
| -rw-r--r-- | locales/ja-JP.yml | 1 | ||||
| -rw-r--r-- | packages/backend/migration/1703209889304-bannedEmailDomains.js | 18 | ||||
| -rw-r--r-- | packages/backend/src/core/EmailService.ts | 10 | ||||
| -rw-r--r-- | packages/backend/src/models/Meta.ts | 7 | ||||
| -rw-r--r-- | packages/backend/src/server/api/endpoints/admin/meta.ts | 9 | ||||
| -rw-r--r-- | packages/backend/src/server/api/endpoints/admin/update-meta.ts | 5 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkSignupDialog.form.vue | 4 | ||||
| -rw-r--r-- | packages/frontend/src/pages/admin/security.vue | 15 |
10 files changed, 68 insertions, 3 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c21684e77..71a90620e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,6 +94,7 @@ - Fix: MFMでルビの中のテキストがnyaizeされない問題を修正 ### Server +- Feat: 使い捨てメールのドメインを手動で設定できるように - Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように - Enhance: Meilisearchを有効にした検索で、ユーザーのミュートやブロックを考慮するように - Enhance: カスタム絵文字のインポート時の動作を改善 diff --git a/locales/index.d.ts b/locales/index.d.ts index fd96fd7625..b3589082e1 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1745,6 +1745,7 @@ export interface Locale { "disposable": string; "mx": string; "smtp": string; + "banned": string; }; "_ffVisibility": { "public": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 2c29bd20da..b59fb6e749 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1652,6 +1652,7 @@ _emailUnavailable: disposable: "恒久的に使用可能なアドレスではありません" mx: "正しいメールサーバーではありません" smtp: "メールサーバーが応答しません" + banned: "このメールアドレスでは登録できません" _ffVisibility: public: "公開" diff --git a/packages/backend/migration/1703209889304-bannedEmailDomains.js b/packages/backend/migration/1703209889304-bannedEmailDomains.js new file mode 100644 index 0000000000..5dc99c138f --- /dev/null +++ b/packages/backend/migration/1703209889304-bannedEmailDomains.js @@ -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"`); + } +} diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts index d4508d5313..6107b9601c 100644 --- a/packages/backend/src/core/EmailService.ts +++ b/packages/backend/src/core/EmailService.ts @@ -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(); @@ -184,12 +186,16 @@ export class EmailService { validated = { valid: true, reason: null }; } - 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' : diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 83e8962f5d..84ca762492 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -496,6 +496,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" }', }) public preservedUsernames: string[]; diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 07912154bd..6f8494d1d0 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -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, @@ -513,6 +521,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, 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 293a95a9a4..5f9de0523e 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -122,6 +122,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' }, @@ -526,6 +527,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); diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue index dd05a44e04..f171e449c8 100644 --- a/packages/frontend/src/components/MkSignupDialog.form.vue +++ b/packages/frontend/src/components/MkSignupDialog.form.vue @@ -38,6 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only <span v-else-if="emailState === 'unavailable:used'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.used }}</span> <span v-else-if="emailState === 'unavailable:format'" style="color: var(--error)"><i 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: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> <span v-else-if="emailState === 'unavailable'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.unavailable }}</span> @@ -110,7 +111,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); @@ -209,6 +210,7 @@ function onChangeEmail(): void { result.reason === 'used' ? 'unavailable:used' : result.reason === 'format' ? 'unavailable:format' : result.reason === 'disposable' ? 'unavailable:disposable' : + result.reason === 'banned' ? 'unavailable:banned' : result.reason === 'mx' ? 'unavailable:mx' : result.reason === 'smtp' ? 'unavailable:smtp' : 'unavailable'; diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue index 9835591fa8..bda29cee58 100644 --- a/packages/frontend/src/pages/admin/security.vue +++ b/packages/frontend/src/pages/admin/security.vue @@ -84,6 +84,17 @@ SPDX-License-Identifier: AGPL-3.0-only </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> <template v-else #suffix>Disabled</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(); }); |