mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-05-06 20:32:55 +08:00
feat: add address_block_list for new address (#185)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
dist/
|
||||
test/
|
||||
.vscode/
|
||||
|
||||
13
CHANGELOG.md
13
CHANGELOG.md
@@ -2,11 +2,21 @@
|
||||
|
||||
## main branch to be released
|
||||
|
||||
### DB Changes
|
||||
|
||||
新增 `settings` 表,用于存储通用配置信息
|
||||
|
||||
- `db/2024-05-01-patch.sql`
|
||||
|
||||
### Changes
|
||||
|
||||
- `ENABLE_USER_CREATE_EMAIL` 是否允许用户创建邮件
|
||||
- 允许 admin 创建无前缀的邮件
|
||||
- 添加 `SMTP proxy server`,支持 SMTP 发送邮件
|
||||
- 修复某些情况浏览器无法加载 `wasm` 时使用 js 解析邮件
|
||||
- 页脚添加 `COPYRIGHT`
|
||||
- UI 允许用户切换邮件展示模式 `v-html` / `iframe`
|
||||
- 添加 `admin` 账户配置页面,支持配置用户注册名称黑名单
|
||||
|
||||
## v0.3.0
|
||||
|
||||
@@ -36,7 +46,6 @@ set
|
||||
* feat: admin page add account mail count && sendbox default all && sen… by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/172
|
||||
* feat: all mail use MailBox Component by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/173
|
||||
|
||||
|
||||
**Full Changelog**: https://github.com/dreamhunter2333/cloudflare_temp_email/compare/0.2.10...v0.3.0
|
||||
|
||||
## v0.2.10
|
||||
@@ -52,7 +61,7 @@ set
|
||||
## v0.2.9
|
||||
|
||||
- 添加富文本编辑器
|
||||
- admin 联系方式,不配置则不显示,可配置任意字符串 `ADMIN_CONTACT = "xx@xx.xxx"`
|
||||
- admin 联系方式,不配置则不显示,可配置任意字符串 `ADMIN_CONTACT = "xx@xx.xxx"`
|
||||
- 默认发送邮件余额,如果不设置,将为 0 `DEFAULT_SEND_BALANCE = 1`
|
||||
|
||||
## v0.2.8
|
||||
|
||||
14
README.md
14
README.md
@@ -25,15 +25,12 @@
|
||||
- [CHANGELOG](#changelog)
|
||||
- [在线演示](#在线演示)
|
||||
- [功能/TODO](#功能todo)
|
||||
- [Reference](#reference)
|
||||
|
||||
## 功能/TODO
|
||||
|
||||
- [x] Cloudflare D1 作为数据库
|
||||
- [x] 使用 Cloudflare Pages 部署前端
|
||||
- [x] 使用 Cloudflare Workers 部署后端
|
||||
- [x] email 转发使用 Cloudflare Email Routing
|
||||
- [x] 使用 `password` 重新登录之前的邮箱
|
||||
- [x] 获取自定义名字的邮箱
|
||||
- [x] 获取自定义名字的邮箱,`admin` 可配置黑名单
|
||||
- [x] 支持多语言
|
||||
- [x] 增加访问密码,可作为私人站点
|
||||
- [x] 增加自动回复功能
|
||||
@@ -43,3 +40,10 @@
|
||||
- [x] 支持 `DKIM`
|
||||
- [x] `admin` 后台创建无前缀邮箱
|
||||
- [x] 添加 `SMTP proxy server`,支持 SMTP 发送邮件
|
||||
|
||||
## Reference
|
||||
|
||||
- Cloudflare D1 作为数据库
|
||||
- 使用 Cloudflare Pages 部署前端
|
||||
- 使用 Cloudflare Workers 部署后端
|
||||
- email 转发使用 Cloudflare Email Routing
|
||||
|
||||
6
db/2024-05-01-patch.sql
Normal file
6
db/2024-05-01-patch.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
@@ -70,3 +70,10 @@ CREATE TABLE IF NOT EXISTS sendbox (
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_sendbox_address ON sendbox(address);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
@@ -9,6 +9,7 @@ import Statistics from "./admin/Statistics.vue"
|
||||
import SendBox from './admin/SendBox.vue';
|
||||
import Account from './admin/Account.vue';
|
||||
import CreateAccount from './admin/CreateAccount.vue';
|
||||
import AccountSettings from './admin/AccountSettings.vue';
|
||||
import Mails from './admin/Mails.vue';
|
||||
import MailsUnknow from './admin/MailsUnknow.vue';
|
||||
import Maintenance from './admin/Maintenance.vue';
|
||||
@@ -35,6 +36,7 @@ const { t } = useI18n({
|
||||
mails: 'Emails',
|
||||
account: 'Account',
|
||||
account_create: 'Create Account',
|
||||
account_settings: 'Account Settings',
|
||||
unknow: 'Mails with unknow receiver',
|
||||
senderAccess: 'Sender Access Control',
|
||||
sendBox: 'Send Box',
|
||||
@@ -47,6 +49,7 @@ const { t } = useI18n({
|
||||
mails: '邮件',
|
||||
account: '账号',
|
||||
account_create: '创建账号',
|
||||
account_settings: '账号设置',
|
||||
unknow: '无收件人邮件',
|
||||
senderAccess: '发件权限控制',
|
||||
sendBox: '发件箱',
|
||||
@@ -84,6 +87,9 @@ onMounted(async () => {
|
||||
<n-tab-pane name="account_create" :tab="t('account_create')">
|
||||
<CreateAccount />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="account_settings" :tab="t('account_settings')">
|
||||
<AccountSettings />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="mails" :tab="t('mails')">
|
||||
<Mails />
|
||||
</n-tab-pane>
|
||||
|
||||
84
frontend/src/views/admin/AccountSettings.vue
Normal file
84
frontend/src/views/admin/AccountSettings.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useGlobalState } from '../../store'
|
||||
import { api } from '../../api'
|
||||
|
||||
const {
|
||||
localeCache, loading, openSettings,
|
||||
} = useGlobalState()
|
||||
const message = useMessage()
|
||||
|
||||
const { t } = useI18n({
|
||||
locale: localeCache.value || 'zh',
|
||||
messages: {
|
||||
en: {
|
||||
save: 'Save',
|
||||
successTip: 'Save Success',
|
||||
address_block_list: 'Address Block Keywords for Users(Admin can skip)',
|
||||
address_block_list_placeholder: 'Please enter the keywords you want to block',
|
||||
},
|
||||
zh: {
|
||||
save: '保存',
|
||||
successTip: '保存成功',
|
||||
address_block_list: '用户地址屏蔽关键词(管理员可跳过检查)',
|
||||
address_block_list_placeholder: '请输入您想要屏蔽的关键词',
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const addressBlockList = ref([])
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const res = await api.fetch(`/admin/account_settings`)
|
||||
addressBlockList.value = res.blockList || []
|
||||
} catch (error) {
|
||||
message.error(error.message || "error");
|
||||
}
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
try {
|
||||
await api.fetch(`/admin/account_settings`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
blockList: addressBlockList.value || []
|
||||
})
|
||||
})
|
||||
message.success(t('successTip'))
|
||||
} catch (error) {
|
||||
message.error(error.message || "error");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchData();
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="center">
|
||||
<n-card style="max-width: 600px;">
|
||||
<n-form-item-row :label="t('address_block_list')">
|
||||
<n-select v-model:value="addressBlockList" filterable multiple tag
|
||||
:placeholder="t('address_block_list_placeholder')" />
|
||||
</n-form-item-row>
|
||||
<n-button @click="save" type="primary" block :loading="loading">
|
||||
{{ t('save') }}
|
||||
</n-button>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.center {
|
||||
display: flex;
|
||||
text-align: left;
|
||||
place-items: center;
|
||||
justify-content: center;
|
||||
margin: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -91,11 +91,9 @@ onMounted(async () => {
|
||||
:options="openSettings.domains" />
|
||||
</n-input-group>
|
||||
</n-form-item-row>
|
||||
<div class="right">
|
||||
<n-button @click="newEmail" type="primary" :loading="loading">
|
||||
{{ t('creatNewEmail') }}
|
||||
</n-button>
|
||||
</div>
|
||||
<n-button @click="newEmail" type="primary" block :loading="loading">
|
||||
{{ t('creatNewEmail') }}
|
||||
</n-button>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
@@ -108,10 +106,4 @@ onMounted(async () => {
|
||||
justify-content: center;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.right {
|
||||
text-align: right;
|
||||
place-items: right;
|
||||
justify-content: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -23,6 +23,6 @@ features:
|
||||
details: 支持 password 登录邮箱,使用访问密码可作为私人站点,支持附件功能
|
||||
- title: 使用 rust wasm 解析邮件
|
||||
details: 使用 rust wasm 解析邮件,支持邮件各种RFC标准,支持附件, 速度极快
|
||||
- title: 支持发送邮件
|
||||
details: 支持通过域名邮箱发送 txt 或者 html 邮件,支持 DKIM 签名
|
||||
- title: 支持发送邮件(UI/API/SMTP)
|
||||
details: 支持通过域名邮箱发送 txt 或者 html 邮件,支持 DKIM 签名, UI/API/SMTP 发送邮件
|
||||
---
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Hono } from 'hono'
|
||||
import { Jwt } from 'hono/utils/jwt'
|
||||
import { sendAdminInternalMail } from './utils'
|
||||
import { newAddress } from './common'
|
||||
import { CONSTANTS } from './constants'
|
||||
|
||||
const api = new Hono()
|
||||
|
||||
@@ -329,4 +330,36 @@ api.post('/admin/cleanup', async (c) => {
|
||||
})
|
||||
})
|
||||
|
||||
api.get('/admin/account_settings', async (c) => {
|
||||
try {
|
||||
const value = await c.env.DB.prepare(
|
||||
`SELECT value FROM settings where key = ?`
|
||||
).bind(CONSTANTS.ADDRESS_BLOCK_LIST_KEY).first("value");
|
||||
return c.json({
|
||||
blockList: value ? JSON.parse(value) : []
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return c.json({})
|
||||
}
|
||||
})
|
||||
|
||||
api.post('/admin/account_settings', async (c) => {
|
||||
const { blockList } = await c.req.json();
|
||||
if (!blockList) {
|
||||
return c.text("Invalid blockList", 400)
|
||||
}
|
||||
await c.env.DB.prepare(
|
||||
`INSERT or REPLACE INTO settings (key, value) VALUES (?, ?)`
|
||||
+ ` ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = datetime('now')`
|
||||
).bind(
|
||||
CONSTANTS.ADDRESS_BLOCK_LIST_KEY,
|
||||
JSON.stringify(blockList),
|
||||
JSON.stringify(blockList)
|
||||
).run();
|
||||
return c.json({
|
||||
success: true
|
||||
})
|
||||
})
|
||||
|
||||
export { api }
|
||||
|
||||
3
worker/src/constants.js
Normal file
3
worker/src/constants.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export const CONSTANTS = {
|
||||
ADDRESS_BLOCK_LIST_KEY: 'address_block_list',
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Hono } from 'hono'
|
||||
|
||||
import { getDomains, getPasswords, getBooleanValue } from './utils';
|
||||
import { newAddress } from './common'
|
||||
import { CONSTANTS } from './constants'
|
||||
|
||||
const api = new Hono()
|
||||
|
||||
@@ -169,6 +170,18 @@ api.get('/api/new_address', async (c) => {
|
||||
if (!name) {
|
||||
name = Math.random().toString(36).substring(2, 15);
|
||||
}
|
||||
// check name block list
|
||||
try {
|
||||
const value = await c.env.DB.prepare(
|
||||
`SELECT value FROM settings where key = ?`
|
||||
).bind(CONSTANTS.ADDRESS_BLOCK_LIST_KEY).first("value");
|
||||
const blockList = value ? JSON.parse(value) : [];
|
||||
if (blockList.some((item) => name.includes(item))) {
|
||||
return c.text(`Name [${name}] is blocked`, 400)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return newAddress(c, name, domain, true);
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user