Compare commits

...

12 Commits

Author SHA1 Message Date
鴇峰 朔華
e656927e50
Merge b6331402da into 3c81926f71 2024-12-22 13:58:42 +09:00
かっこかり
3c81926f71
fix(frontend): serverContextの値を利用する条件が間違っていたのを修正 (#15166)
Some checks are pending
Check SPDX-License-Identifier / check-spdx-license-id (push) Waiting to run
Check copyright year / check_copyright_year (push) Waiting to run
Publish Docker image (develop) / Build (linux/amd64) (push) Waiting to run
Publish Docker image (develop) / Build (linux/arm64) (push) Waiting to run
Publish Docker image (develop) / merge (push) Blocked by required conditions
Dockle / dockle (push) Waiting to run
Lint / pnpm_install (push) Waiting to run
Lint / lint (backend) (push) Blocked by required conditions
Lint / lint (frontend) (push) Blocked by required conditions
Lint / lint (frontend-embed) (push) Blocked by required conditions
Lint / lint (frontend-shared) (push) Blocked by required conditions
Lint / lint (misskey-bubble-game) (push) Blocked by required conditions
Lint / lint (misskey-js) (push) Blocked by required conditions
Lint / lint (misskey-reversi) (push) Blocked by required conditions
Lint / lint (sw) (push) Blocked by required conditions
Lint / typecheck (backend) (push) Blocked by required conditions
Lint / typecheck (misskey-js) (push) Blocked by required conditions
Lint / typecheck (sw) (push) Blocked by required conditions
Storybook / build (push) Waiting to run
Test (frontend) / vitest (22.11.0) (push) Waiting to run
Test (frontend) / e2e (chrome, 22.11.0) (push) Waiting to run
Test (production install and build) / production (22.11.0) (push) Waiting to run
2024-12-22 13:36:17 +09:00
鴇峰 朔華
b6331402da fix 2024-11-30 17:26:10 +09:00
鴇峰 朔華
9775ac3b21
Merge branch 'develop' into misskey-dev/feature/#19895 2024-11-30 16:54:08 +09:00
鴇峰 朔華
1d7a940cd4 CHANGELOG記載 2024-11-30 15:33:50 +09:00
鴇峰 朔華
49d335016f Fix: 判定方法を後方一致から完全一致に変更 2024-11-30 15:33:35 +09:00
鴇峰 朔華
4893f663ff Mod: バリデーションを追加 2024-11-30 09:58:40 +09:00
鴇峰 朔華
b95fb5410c Add: ホワイトリストのメールドメインしか登録できない機能を追加 2024-11-25 22:19:18 +09:00
鴇峰 朔華
a7270735b0 Add: メールドメインの自動ブラックリスト追加を追加 2024-11-25 21:38:32 +09:00
鴇峰 朔華
866cd43207 Add: コントロールパネルにAllowedEmailDomainsを追加 2024-11-25 20:08:59 +09:00
鴇峰 朔華
8229e27128 Add(backend): ホワイトリストとしてallowedEmailDomainsを追加 2024-11-25 20:00:17 +09:00
鴇峰 朔華
a930cddd1f Mod: メールドメインブラックリストをバリデーションより先に評価する 2024-11-24 21:15:21 +09:00
14 changed files with 219 additions and 16 deletions

View File

@ -1,5 +1,10 @@
## 2024.11.1
### Note
- 登録時メールアドレスドメインのブラックリストの判定方式が後方一致から完全一致に変更されました。サブドメインごとブロックする場合は先頭に`.`を付けることで後方一致になります。
- 例: `example.com``example.com` をブロックしますが、`sub.example.com` をブロックしません。
- 例: `.example.com``example.com` および `sub.example.com` をブロックします。
### General
-
@ -18,7 +23,9 @@
- Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 )
- Fix: 起動前の疎通チェックが機能しなくなっていた問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/737)
- Enhance: 登録時メールアドレスドメインのブラックリストの判定方式を改善
- Enhance: 登録時メールアドレスドメインのホワイトリストを追加
- Enhance: Active Email Validationでブロックされたドメインを自動でブラックリストに追加するオプションを追加
## 2024.11.0

View File

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class AddAllowedEmailDomains1732451011177 {
name = 'AddAllowedEmailDomains1732451011177'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "allowedEmailDomains" character varying(1024) array NOT NULL DEFAULT '{}'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "allowedEmailDomains"`);
}
}

View File

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class AddAutoAddBannedEmailDomain1732535648378 {
name = 'AddAutoAddBannedEmailDomain1732535648378'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "enableAutoAddBannedEmailDomain" boolean NOT NULL DEFAULT false`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableAutoAddBannedEmailDomain"`);
}
}

View File

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class AddEnableAllowedEmailDomainOnly1732538997055 {
name = 'AddEnableAllowedEmailDomainOnly1732538997055'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "enableAllowedEmailDomainsOnly" boolean NOT NULL DEFAULT false`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableAllowedEmailDomainsOnly"`);
}
}

View File

@ -16,6 +16,7 @@ import type { MiMeta, UserProfilesRepository } from '@/models/_.js';
import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { MetaService } from '@/core/MetaService.js';
@Injectable()
export class EmailService {
@ -34,6 +35,7 @@ export class EmailService {
private loggerService: LoggerService,
private utilityService: UtilityService,
private httpRequestService: HttpRequestService,
private metaService: MetaService,
) {
this.logger = this.loggerService.getLogger('email');
}
@ -162,8 +164,15 @@ export class EmailService {
@bindThis
public async validateEmailForAccount(emailAddress: string): Promise<{
available: boolean;
reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned' | 'network' | 'blacklist';
reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned' | 'network' | 'blacklist' | 'allowedOnly';
}> {
if (!this.utilityService.validateEmailFormat(emailAddress)) {
return {
available: false,
reason: 'format',
};
}
const exist = await this.userProfilesRepository.countBy({
emailVerified: true,
email: emailAddress,
@ -176,6 +185,33 @@ export class EmailService {
};
}
const emailDomain: string = emailAddress.split('@')[1];
// ホワイトリストに含まれている場合は即座にtrueを返す
if (this.utilityService.isAllowedHost(this.meta.allowedEmailDomains, emailDomain)) {
return {
available: true,
reason: null,
};
}
// ホワイトリストのみ許可の場合は即座にfalseを返す
if (this.meta.enableAllowedEmailDomainsOnly) {
return {
available: false,
reason: 'allowedOnly',
};
}
const isBanned = this.utilityService.isBlockedHost(this.meta.bannedEmailDomains, emailDomain);
if (isBanned) {
return {
available: false,
reason: 'banned',
};
}
let validated: {
valid: boolean,
reason?: string | null,
@ -208,22 +244,17 @@ export class EmailService {
blacklist: 'blacklist',
};
// 自動追加が有効な場合はブラックリストに追加する
if (this.meta.enableAutoAddBannedEmailDomain) {
await this.addBlockedHost(emailDomain);
}
return {
available: false,
reason: validated.reason ? formatReason[validated.reason] ?? null : null,
};
}
const emailDomain: string = emailAddress.split('@')[1];
const isBanned = this.utilityService.isBlockedHost(this.meta.bannedEmailDomains, emailDomain);
if (isBanned) {
return {
available: false,
reason: 'banned',
};
}
return {
available: true,
reason: null,
@ -363,4 +394,11 @@ export class EmailService {
};
}
}
private async addBlockedHost(domain: string) {
const set = {} as Partial<MiMeta>;
set.bannedEmailDomains = this.meta.bannedEmailDomains;
set.bannedEmailDomains.push(domain);
await this.metaService.update(set);
}
}

View File

@ -39,10 +39,30 @@ export class UtilityService {
return this.punyHost(uri) === this.toPuny(this.config.host);
}
// メールアドレスのバリデーションを行う
// https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
@bindThis
public validateEmailFormat(email: string): boolean {
const regexp = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
return regexp.test(email);
}
@bindThis
public isBlockedHost(blockedHosts: string[], host: string | null): boolean {
if (host == null) return false;
return blockedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`));
return blockedHosts.some(x => {
if (x.startsWith('.')) return `.${host.toLowerCase()}`.endsWith(x);
return host.toLowerCase() === x;
});
}
@bindThis
public isAllowedHost(allowedHosts: string[], host: string | null): boolean {
if (host == null) return false;
return allowedHosts.some(x => {
if (x.startsWith('.')) return `.${host.toLowerCase()}`.endsWith(x);
return host.toLowerCase() === x;
});
}
@bindThis

View File

@ -569,6 +569,23 @@ export class MiMeta {
})
public bannedEmailDomains: string[];
@Column('varchar', {
length: 1024,
array: true,
default: '{}',
})
public allowedEmailDomains: string[];
@Column('boolean', {
default: false,
})
public enableAutoAddBannedEmailDomain: boolean;
@Column('boolean', {
default: false,
})
public enableAllowedEmailDomainsOnly: boolean;
@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" }',
})

View File

@ -192,6 +192,14 @@ export const meta = {
optional: false, nullable: false,
},
},
allowedEmailDomains: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
},
},
preservedUsernames: {
type: 'array',
optional: false, nullable: false,
@ -340,6 +348,14 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
enableAutoAddBannedEmailDomain: {
type: 'boolean',
optional: false, nullable: false,
},
enableAllowedEmailDomainsOnly: {
type: 'boolean',
optional: false, nullable: false,
},
enableChartsForRemoteUser: {
type: 'boolean',
optional: false, nullable: false,
@ -637,12 +653,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
enableTruemailApi: instance.enableTruemailApi,
truemailInstance: instance.truemailInstance,
truemailAuthKey: instance.truemailAuthKey,
enableAutoAddBannedEmailDomain: instance.enableAutoAddBannedEmailDomain,
enableAllowedEmailDomainsOnly: instance.enableAllowedEmailDomainsOnly,
enableChartsForRemoteUser: instance.enableChartsForRemoteUser,
enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances,
enableStatsForFederatedInstances: instance.enableStatsForFederatedInstances,
enableServerMachineStats: instance.enableServerMachineStats,
enableIdenticonGeneration: instance.enableIdenticonGeneration,
bannedEmailDomains: instance.bannedEmailDomains,
allowedEmailDomains: instance.allowedEmailDomains,
policies: { ...DEFAULT_POLICIES, ...instance.policies },
manifestJsonOverride: instance.manifestJsonOverride,
enableFanoutTimeline: instance.enableFanoutTimeline,

View File

@ -141,6 +141,9 @@ export const paramDef = {
enableIdenticonGeneration: { type: 'boolean' },
serverRules: { type: 'array', items: { type: 'string' } },
bannedEmailDomains: { type: 'array', items: { type: 'string' } },
allowedEmailDomains: { type: 'array', items: { type: 'string' } },
enableAutoAddBannedEmailDomain: { type: 'boolean' },
enableAllowedEmailDomainsOnly: { type: 'boolean' },
preservedUsernames: { type: 'array', items: { type: 'string' } },
manifestJsonOverride: { type: 'string' },
enableFanoutTimeline: { type: 'boolean' },
@ -639,6 +642,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.bannedEmailDomains = ps.bannedEmailDomains;
}
if (ps.allowedEmailDomains !== undefined) {
set.allowedEmailDomains = ps.allowedEmailDomains;
}
if (ps.enableAutoAddBannedEmailDomain !== undefined) {
set.enableAutoAddBannedEmailDomain = ps.enableAutoAddBannedEmailDomain;
}
if (ps.enableAllowedEmailDomainsOnly !== undefined) {
set.enableAllowedEmailDomainsOnly = ps.enableAllowedEmailDomainsOnly;
}
if (ps.urlPreviewEnabled !== undefined) {
set.urlPreviewEnabled = ps.urlPreviewEnabled;
}

View File

@ -85,6 +85,26 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #prefix><i class="ti ti-key"></i></template>
<template #label>TrueMail API Auth Key</template>
</MkInput>
<MkSwitch v-model="emailValidationForm.state.enableAutoAddBannedEmailDomain">
<template #label>Enable Auto Add Banned Email Domain</template>
</MkSwitch>
</div>
</MkFolder>
<MkFolder>
<template #label>Allowed Email Domains</template>
<template v-if="allowedEmailDomainsForm.modified.value" #footer>
<MkFormFooter :form="allowedEmailDomainsForm"/>
</template>
<div class="_gaps_m">
<MkTextarea v-model="allowedEmailDomainsForm.state.allowedEmailDomains">
<template #label>Allowed Email Domains List</template>
</MkTextarea>
<MkSwitch v-model="allowedEmailDomainsForm.state.enableAllowedEmailDomainsOnly">
<template #label>Enable Allowed Email Domains Only</template>
</MkSwitch>
</div>
</MkFolder>
@ -181,6 +201,7 @@ const emailValidationForm = useForm({
enableTruemailApi: meta.enableTruemailApi,
truemailInstance: meta.truemailInstance,
truemailAuthKey: meta.truemailAuthKey,
enableAutoAddBannedEmailDomain: meta.enableAutoAddBannedEmailDomain,
}, async (state) => {
await os.apiWithDialog('admin/update-meta', {
enableActiveEmailValidation: state.enableActiveEmailValidation,
@ -189,6 +210,18 @@ const emailValidationForm = useForm({
enableTruemailApi: state.enableTruemailApi,
truemailInstance: state.truemailInstance,
truemailAuthKey: state.truemailAuthKey,
enableAutoAddBannedEmailDomain: state.enableAutoAddBannedEmailDomain,
});
fetchInstance(true);
});
const allowedEmailDomainsForm = useForm({
allowedEmailDomains: meta.allowedEmailDomains?.join('\n') || '',
enableAllowedEmailDomainsOnly: meta.enableAllowedEmailDomainsOnly,
}, async (state) => {
await os.apiWithDialog('admin/update-meta', {
allowedEmailDomains: state.allowedEmailDomains.split('\n'),
enableAllowedEmailDomainsOnly: state.enableAllowedEmailDomainsOnly,
});
fetchInstance(true);
});

View File

@ -49,7 +49,7 @@ import { genEmbedCode } from '@/scripts/get-embed-code.js';
import { assertServerContext, serverContext } from '@/server-context.js';
// context
const CTX_CLIP = $i && assertServerContext(serverContext, 'clip') ? serverContext.clip : null;
const CTX_CLIP = !$i && assertServerContext(serverContext, 'clip') ? serverContext.clip : null;
const props = defineProps<{
clipId: string,

View File

@ -67,7 +67,7 @@ import { serverContext, assertServerContext } from '@/server-context.js';
import { $i } from '@/account.js';
// context
const CTX_NOTE = $i && assertServerContext(serverContext, 'note') ? serverContext.note : null;
const CTX_NOTE = !$i && assertServerContext(serverContext, 'note') ? serverContext.note : null;
const props = defineProps<{
noteId: string;

View File

@ -54,7 +54,7 @@ const XGallery = defineAsyncComponent(() => import('./gallery.vue'));
const XRaw = defineAsyncComponent(() => import('./raw.vue'));
// context
const CTX_USER = $i && assertServerContext(serverContext, 'user') ? serverContext.user : null;
const CTX_USER = !$i && assertServerContext(serverContext, 'user') ? serverContext.user : null;
const props = withDefaults(defineProps<{
acct: string;

View File

@ -5138,6 +5138,7 @@ export type operations = {
prohibitedWords: string[];
prohibitedWordsForNameOfUser: string[];
bannedEmailDomains?: string[];
allowedEmailDomains?: string[];
preservedUsernames: string[];
hcaptchaSecretKey: string | null;
mcaptchaSecretKey: string | null;
@ -5175,6 +5176,8 @@ export type operations = {
enableTruemailApi: boolean;
truemailInstance: string | null;
truemailAuthKey: string | null;
enableAutoAddBannedEmailDomain: boolean;
enableAllowedEmailDomainsOnly: boolean;
enableChartsForRemoteUser: boolean;
enableChartsForFederatedInstances: boolean;
enableStatsForFederatedInstances: boolean;
@ -9578,6 +9581,9 @@ export type operations = {
enableIdenticonGeneration?: boolean;
serverRules?: string[];
bannedEmailDomains?: string[];
allowedEmailDomains?: string[];
enableAutoAddBannedEmailDomain?: boolean;
enableAllowedEmailDomainsOnly?: boolean;
preservedUsernames?: string[];
manifestJsonOverride?: string;
enableFanoutTimeline?: boolean;