Skip to content

Commit 0565978

Browse files
feat: 账号设置页面增加 邮件转发规则 和 禁止接收未知地址邮件 配置 (dreamhunter2333#710)
1 parent 89d8944 commit 0565978

File tree

6 files changed

+199
-12
lines changed

6 files changed

+199
-12
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
## main(v1.0.4)
55

66
- feat: |UI| 优化极简模式主页, 增加全部邮件页面功能(删除/下载/附件/...), 可在 `外观` 中切换
7+
- feat: admin 账号设置页面增加 `邮件转发规则` 配置
8+
- feat: admin 账号设置页面增加 `禁止接收未知地址邮件` 配置
79

810
## v1.0.3
911

frontend/src/views/admin/AccountSettings.vue

Lines changed: 150 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
<script setup>
2-
import { onMounted, ref } from 'vue';
2+
import { onMounted, ref, h } from 'vue';
33
import { useI18n } from 'vue-i18n'
4+
import { NButton, NPopconfirm, NInput, NSelect } from 'naive-ui'
45
56
import { useGlobalState } from '../../store'
67
import { api } from '../../api'
78
8-
const { loading } = useGlobalState()
9+
const { loading, openSettings } = useGlobalState()
910
const message = useMessage()
1011
1112
const { t } = useI18n({
@@ -20,6 +21,20 @@ const { t } = useI18n({
2021
noLimitSendAddressList: 'No Balance Limit Send Address List',
2122
verified_address_list: 'Verified Address List(Can send email by cf internal api)',
2223
fromBlockList: 'Block Keywords for receive email',
24+
block_receive_unknow_address_email: 'Block receive unknow address email',
25+
email_forwarding_config: 'Email Forwarding Configuration',
26+
domain_list: 'Domain List',
27+
forward_address: 'Forward Address',
28+
actions: 'Actions',
29+
select_domain: 'Select Domain',
30+
forward_placeholder: '[email protected]',
31+
delete_rule: 'Delete',
32+
delete_rule_confirm: 'Are you sure you want to delete this rule?',
33+
delete_success: 'Delete Success',
34+
forwarding_rule_warning: 'Each rule will run, if domains is empty, all emails will be forwarded, forward address needs to be a verified address',
35+
add: 'Add',
36+
cancel: 'Cancel',
37+
config: 'Config',
2338
},
2439
zh: {
2540
tip: '您可以手动输入以下多选输入框, 回车增加',
@@ -31,6 +46,20 @@ const { t } = useI18n({
3146
noLimitSendAddressList: '无余额限制发送地址列表',
3247
verified_address_list: '已验证地址列表(可通过 cf 内部 api 发送邮件)',
3348
fromBlockList: '接收邮件地址屏蔽关键词',
49+
block_receive_unknow_address_email: '禁止接收未知地址邮件',
50+
email_forwarding_config: '邮件转发配置',
51+
domain_list: '域名列表',
52+
forward_address: '转发地址',
53+
actions: '操作',
54+
select_domain: '选择域名',
55+
forward_placeholder: '[email protected]',
56+
delete_rule: '删除',
57+
delete_rule_confirm: '确定要删除这条规则吗?',
58+
delete_success: '删除成功',
59+
forwarding_rule_warning: '每条规则都会运行,如果 domains 为空,则转发所有邮件,转发地址需要为已验证的地址',
60+
add: '添加',
61+
cancel: '取消',
62+
config: '配置',
3463
}
3564
}
3665
});
@@ -40,6 +69,90 @@ const sendAddressBlockList = ref([])
4069
const noLimitSendAddressList = ref([])
4170
const verifiedAddressList = ref([])
4271
const fromBlockList = ref([])
72+
const emailRuleSettings = ref({
73+
blockReceiveUnknowAddressEmail: false,
74+
emailForwardingList: []
75+
})
76+
77+
const showEmailForwardingModal = ref(false)
78+
const emailForwardingList = ref([])
79+
80+
81+
const emailForwardingColumns = [
82+
{
83+
title: t('domain_list'),
84+
key: 'domains',
85+
render: (row, index) => {
86+
return h(NSelect, {
87+
value: Array.isArray(row.domains) ? row.domains : [],
88+
onUpdateValue: (val) => {
89+
emailForwardingList.value[index].domains = val
90+
},
91+
options: openSettings.value?.domains || [],
92+
multiple: true,
93+
filterable: true,
94+
tag: true,
95+
placeholder: t('select_domain')
96+
})
97+
}
98+
},
99+
{
100+
title: t('forward_address'),
101+
key: 'forward',
102+
render: (row, index) => {
103+
return h(NInput, {
104+
value: row.forward,
105+
onUpdateValue: (val) => {
106+
emailForwardingList.value[index].forward = val
107+
},
108+
placeholder: '[email protected]'
109+
})
110+
}
111+
},
112+
{
113+
title: t('actions'),
114+
key: 'actions',
115+
render: (row, index) => {
116+
return h('div', { style: 'display: flex; gap: 8px;' }, [
117+
h(NPopconfirm, {
118+
onPositiveClick: () => {
119+
emailForwardingList.value = emailForwardingList.value.filter((_, i) => i !== index)
120+
message.success(t('delete_success'))
121+
}
122+
}, {
123+
default: () => t('delete_rule_confirm'),
124+
trigger: () => h(NButton, {
125+
size: 'small',
126+
type: 'error'
127+
}, { default: () => t('delete_rule') })
128+
})
129+
])
130+
}
131+
}
132+
]
133+
134+
const openEmailForwardingModal = () => {
135+
// 从 emailRuleSettings 转换出列表数据
136+
emailForwardingList.value = emailRuleSettings.value.emailForwardingList ?
137+
[...emailRuleSettings.value.emailForwardingList] : []
138+
showEmailForwardingModal.value = true
139+
}
140+
141+
const addNewEmailForwardingItem = () => {
142+
emailForwardingList.value = [
143+
...emailForwardingList.value,
144+
{
145+
domains: [],
146+
forward: ''
147+
}
148+
]
149+
}
150+
151+
const saveEmailForwardingConfig = () => {
152+
emailRuleSettings.value.emailForwardingList = [...emailForwardingList.value]
153+
showEmailForwardingModal.value = false
154+
}
155+
43156
44157
const fetchData = async () => {
45158
try {
@@ -49,6 +162,10 @@ const fetchData = async () => {
49162
verifiedAddressList.value = res.verifiedAddressList || []
50163
fromBlockList.value = res.fromBlockList || []
51164
noLimitSendAddressList.value = res.noLimitSendAddressList || []
165+
emailRuleSettings.value = res.emailRuleSettings || {
166+
blockReceiveUnknowAddressEmail: false,
167+
emailForwardingList: []
168+
}
52169
} catch (error) {
53170
message.error(error.message || "error");
54171
}
@@ -64,6 +181,7 @@ const save = async () => {
64181
verifiedAddressList: verifiedAddressList.value || [],
65182
fromBlockList: fromBlockList.value || [],
66183
noLimitSendAddressList: noLimitSendAddressList.value || [],
184+
emailRuleSettings: emailRuleSettings.value,
67185
})
68186
})
69187
message.success(t('successTip'))
@@ -81,9 +199,14 @@ onMounted(async () => {
81199
<template>
82200
<div class="center">
83201
<n-card :bordered="false" embedded style="max-width: 600px;">
84-
<n-alert :show-icon="false" type="warning" style="margin-bottom: 10px;">
85-
{{ t("tip") }}
202+
<n-alert :show-icon="false" :bordered="false" type="warning" style="margin-bottom: 10px;">
203+
<span>{{ t("tip") }}</span>
86204
</n-alert>
205+
<n-flex justify="end">
206+
<n-button @click="save" type="primary" :loading="loading">
207+
{{ t('save') }}
208+
</n-button>
209+
</n-flex>
87210
<n-form-item-row :label="t('address_block_list')">
88211
<n-select v-model:value="addressBlockList" filterable multiple tag
89212
:placeholder="t('address_block_list_placeholder')" />
@@ -103,11 +226,31 @@ onMounted(async () => {
103226
<n-form-item-row :label="t('fromBlockList')">
104227
<n-select v-model:value="fromBlockList" filterable multiple tag :placeholder="t('fromBlockList')" />
105228
</n-form-item-row>
106-
<n-button @click="save" type="primary" block :loading="loading">
107-
{{ t('save') }}
108-
</n-button>
229+
<n-form-item-row :label="t('block_receive_unknow_address_email')">
230+
<n-checkbox v-model:checked="emailRuleSettings.blockReceiveUnknowAddressEmail" />
231+
</n-form-item-row>
232+
<n-form-item-row :label="t('email_forwarding_config')">
233+
<n-button @click="openEmailForwardingModal">{{ t('config') }}</n-button>
234+
</n-form-item-row>
109235
</n-card>
110236
</div>
237+
238+
<!-- 邮件转发配置弹窗 -->
239+
<n-modal v-model:show="showEmailForwardingModal" preset="card" :title="t('email_forwarding_config')"
240+
style="max-width: 800px;">
241+
<n-space vertical>
242+
<n-alert :show-icon="false" :bordered="false" type="warning">
243+
<span>{{ t('forwarding_rule_warning') }}</span>
244+
</n-alert>
245+
<n-space justify="end">
246+
<n-button @click="addNewEmailForwardingItem">{{ t('add') }}</n-button>
247+
</n-space>
248+
<n-data-table :columns="emailForwardingColumns" :data="emailForwardingList" :bordered="false" striped />
249+
<n-space justify="end">
250+
<n-button @click="saveEmailForwardingConfig" type="primary">{{ t('save') }}</n-button>
251+
</n-space>
252+
</n-space>
253+
</n-modal>
111254
</template>
112255

113256
<style scoped>

worker/src/admin_api/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import worker_config from './worker_config'
1414
import admin_mail_api from './admin_mail_api'
1515
import { sendMailbyAdmin } from './send_mail'
1616
import db_api from './db_api'
17+
import { EmailRuleSettings } from '../models'
1718

1819
export const api = new Hono<HonoCustomType>()
1920

@@ -217,13 +218,15 @@ api.get('/admin/account_settings', async (c) => {
217218
const sendBlockList = await getJsonSetting(c, CONSTANTS.SEND_BLOCK_LIST_KEY);
218219
const verifiedAddressList = await getJsonSetting(c, CONSTANTS.VERIFIED_ADDRESS_LIST_KEY);
219220
const fromBlockList = c.env.KV ? await c.env.KV.get<string[]>(CONSTANTS.EMAIL_KV_BLACK_LIST, 'json') : [];
221+
const emailRuleSettings = await getJsonSetting<EmailRuleSettings>(c, CONSTANTS.EMAIL_RULE_SETTINGS_KEY);
220222
const noLimitSendAddressList = await getJsonSetting(c, CONSTANTS.NO_LIMIT_SEND_ADDRESS_LIST_KEY);
221223
return c.json({
222224
blockList: blockList || [],
223225
sendBlockList: sendBlockList || [],
224226
verifiedAddressList: verifiedAddressList || [],
225227
fromBlockList: fromBlockList || [],
226-
noLimitSendAddressList: noLimitSendAddressList || []
228+
noLimitSendAddressList: noLimitSendAddressList || [],
229+
emailRuleSettings: emailRuleSettings || {}
227230
})
228231
} catch (error) {
229232
console.error(error);
@@ -235,7 +238,7 @@ api.post('/admin/account_settings', async (c) => {
235238
/** @type {{ blockList: Array<string>, sendBlockList: Array<string> }} */
236239
const {
237240
blockList, sendBlockList, noLimitSendAddressList,
238-
verifiedAddressList, fromBlockList
241+
verifiedAddressList, fromBlockList, emailRuleSettings
239242
} = await c.req.json();
240243
if (!blockList || !sendBlockList || !verifiedAddressList) {
241244
return c.text("Invalid blockList or sendBlockList", 400)
@@ -265,6 +268,10 @@ api.post('/admin/account_settings', async (c) => {
265268
c, CONSTANTS.NO_LIMIT_SEND_ADDRESS_LIST_KEY,
266269
JSON.stringify(noLimitSendAddressList || [])
267270
)
271+
await saveSetting(
272+
c, CONSTANTS.EMAIL_RULE_SETTINGS_KEY,
273+
JSON.stringify(emailRuleSettings || {})
274+
)
268275
return c.json({
269276
success: true
270277
})

worker/src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const CONSTANTS = {
1313
OAUTH2_SETTINGS_KEY: 'oauth2_settings',
1414
VERIFIED_ADDRESS_LIST_KEY: 'verified_address_list',
1515
NO_LIMIT_SEND_ADDRESS_LIST_KEY: 'no_limit_send_address_list',
16+
EMAIL_RULE_SETTINGS_KEY: 'email_rule_settings',
1617

1718
// KV
1819
TG_KV_PREFIX: "temp-mail-telegram",

worker/src/email/index.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { Context } from "hono";
22

3-
import { getEnvStringList, getJsonObjectValue } from "../utils";
3+
import { getEnvStringList, getJsonObjectValue, getJsonSetting } from "../utils";
44
import { sendMailToTelegram } from "../telegram_api";
55
import { auto_reply } from "./auto_reply";
66
import { isBlocked } from "./black_list";
77
import { triggerWebhook, triggerAnotherWorker, commonParseMail } from "../common";
88
import { check_if_junk_mail } from "./check_junk";
99
import { remove_attachment_if_need } from "./check_attachment";
10+
import { EmailRuleSettings } from "../models";
11+
import { CONSTANTS } from "../constants";
1012

1113

1214
async function email(message: ForwardableEmailMessage, env: Bindings, ctx: ExecutionContext) {
@@ -32,6 +34,25 @@ async function email(message: ForwardableEmailMessage, env: Bindings, ctx: Execu
3234
console.error("check junk mail error", error);
3335
}
3436

37+
// check if unknown address mail
38+
try {
39+
const emailRuleSettings = await getJsonSetting<EmailRuleSettings>(
40+
{ env: env } as Context<HonoCustomType>, CONSTANTS.EMAIL_RULE_SETTINGS_KEY
41+
);
42+
if (emailRuleSettings?.blockReceiveUnknowAddressEmail) {
43+
const db_address_id = await env.DB.prepare(
44+
`SELECT id FROM address where name = ? `
45+
).bind(message.to).first("id");
46+
if (!db_address_id) {
47+
message.setReject("Unknown address");
48+
console.log(`Unknown address mail from ${message.from} to ${message.to}`);
49+
return;
50+
}
51+
}
52+
} catch (error) {
53+
console.error("check unknown address mail error", error);
54+
}
55+
3556
// remove attachment if configured or size > 2MB
3657
try {
3758
await remove_attachment_if_need(env, parsedEmailContext, message.from, message.to, message.rawSize);
@@ -70,11 +91,19 @@ async function email(message: ForwardableEmailMessage, env: Bindings, ctx: Execu
7091
try {
7192
// 遍历 FORWARD_ADDRESS_LIST
7293
const subdomainForwardAddressList = getJsonObjectValue<SubdomainForwardAddressList[]>(env.SUBDOMAIN_FORWARD_ADDRESS_LIST) || [];
73-
for (const subdomainForwardAddress of subdomainForwardAddressList) {
94+
const emailRuleSettings = await getJsonSetting<EmailRuleSettings>(
95+
{ env: env } as Context<HonoCustomType>, CONSTANTS.EMAIL_RULE_SETTINGS_KEY
96+
);
97+
// 合并两个配置, env 里的配置优先级更高
98+
const allSubdomainForwardAddressList = [
99+
...(subdomainForwardAddressList || []),
100+
...(emailRuleSettings?.emailForwardingList || []),
101+
];
102+
for (const subdomainForwardAddress of allSubdomainForwardAddressList) {
74103
// 检查邮件是否匹配 domains
75104
if (subdomainForwardAddress.domains && subdomainForwardAddress.domains.length > 0) {
76105
for (const domain of subdomainForwardAddress.domains) {
77-
if (message.to.endsWith(domain)) {
106+
if (message.to.endsWith(domain) && subdomainForwardAddress.forward) {
78107
// 转发邮件
79108
await message.forward(subdomainForwardAddress.forward);
80109
// 支持多邮箱转发收件,不进行截止

worker/src/models/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,8 @@ export type UserOauth2Settings = {
145145
enableMailAllowList?: boolean | undefined;
146146
mailAllowList?: string[] | undefined;
147147
}
148+
149+
export type EmailRuleSettings = {
150+
blockReceiveUnknowAddressEmail: boolean;
151+
emailForwardingList: SubdomainForwardAddressList[]
152+
}

0 commit comments

Comments
 (0)