mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-09-06 16:07:14 +08:00
feat: add address_block_list for new address (#185)
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
dist/
|
dist/
|
||||||
test/
|
test/
|
||||||
|
.vscode/
|
||||||
|
|||||||
+11
-2
@@ -2,11 +2,21 @@
|
|||||||
|
|
||||||
## main branch to be released
|
## main branch to be released
|
||||||
|
|
||||||
|
### DB Changes
|
||||||
|
|
||||||
|
新增 `settings` 表,用于存储通用配置信息
|
||||||
|
|
||||||
|
- `db/2024-05-01-patch.sql`
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
- `ENABLE_USER_CREATE_EMAIL` 是否允许用户创建邮件
|
- `ENABLE_USER_CREATE_EMAIL` 是否允许用户创建邮件
|
||||||
- 允许 admin 创建无前缀的邮件
|
- 允许 admin 创建无前缀的邮件
|
||||||
- 添加 `SMTP proxy server`,支持 SMTP 发送邮件
|
- 添加 `SMTP proxy server`,支持 SMTP 发送邮件
|
||||||
- 修复某些情况浏览器无法加载 `wasm` 时使用 js 解析邮件
|
- 修复某些情况浏览器无法加载 `wasm` 时使用 js 解析邮件
|
||||||
- 页脚添加 `COPYRIGHT`
|
- 页脚添加 `COPYRIGHT`
|
||||||
|
- UI 允许用户切换邮件展示模式 `v-html` / `iframe`
|
||||||
|
- 添加 `admin` 账户配置页面,支持配置用户注册名称黑名单
|
||||||
|
|
||||||
## v0.3.0
|
## 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: 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
|
* 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
|
**Full Changelog**: https://github.com/dreamhunter2333/cloudflare_temp_email/compare/0.2.10...v0.3.0
|
||||||
|
|
||||||
## v0.2.10
|
## v0.2.10
|
||||||
@@ -52,7 +61,7 @@ set
|
|||||||
## v0.2.9
|
## v0.2.9
|
||||||
|
|
||||||
- 添加富文本编辑器
|
- 添加富文本编辑器
|
||||||
- admin 联系方式,不配置则不显示,可配置任意字符串 `ADMIN_CONTACT = "xx@xx.xxx"`
|
- admin 联系方式,不配置则不显示,可配置任意字符串 `ADMIN_CONTACT = "xx@xx.xxx"`
|
||||||
- 默认发送邮件余额,如果不设置,将为 0 `DEFAULT_SEND_BALANCE = 1`
|
- 默认发送邮件余额,如果不设置,将为 0 `DEFAULT_SEND_BALANCE = 1`
|
||||||
|
|
||||||
## v0.2.8
|
## v0.2.8
|
||||||
|
|||||||
@@ -25,15 +25,12 @@
|
|||||||
- [CHANGELOG](#changelog)
|
- [CHANGELOG](#changelog)
|
||||||
- [在线演示](#在线演示)
|
- [在线演示](#在线演示)
|
||||||
- [功能/TODO](#功能todo)
|
- [功能/TODO](#功能todo)
|
||||||
|
- [Reference](#reference)
|
||||||
|
|
||||||
## 功能/TODO
|
## 功能/TODO
|
||||||
|
|
||||||
- [x] Cloudflare D1 作为数据库
|
|
||||||
- [x] 使用 Cloudflare Pages 部署前端
|
|
||||||
- [x] 使用 Cloudflare Workers 部署后端
|
|
||||||
- [x] email 转发使用 Cloudflare Email Routing
|
|
||||||
- [x] 使用 `password` 重新登录之前的邮箱
|
- [x] 使用 `password` 重新登录之前的邮箱
|
||||||
- [x] 获取自定义名字的邮箱
|
- [x] 获取自定义名字的邮箱,`admin` 可配置黑名单
|
||||||
- [x] 支持多语言
|
- [x] 支持多语言
|
||||||
- [x] 增加访问密码,可作为私人站点
|
- [x] 增加访问密码,可作为私人站点
|
||||||
- [x] 增加自动回复功能
|
- [x] 增加自动回复功能
|
||||||
@@ -43,3 +40,10 @@
|
|||||||
- [x] 支持 `DKIM`
|
- [x] 支持 `DKIM`
|
||||||
- [x] `admin` 后台创建无前缀邮箱
|
- [x] `admin` 后台创建无前缀邮箱
|
||||||
- [x] 添加 `SMTP proxy server`,支持 SMTP 发送邮件
|
- [x] 添加 `SMTP proxy server`,支持 SMTP 发送邮件
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- Cloudflare D1 作为数据库
|
||||||
|
- 使用 Cloudflare Pages 部署前端
|
||||||
|
- 使用 Cloudflare Workers 部署后端
|
||||||
|
- email 转发使用 Cloudflare Email Routing
|
||||||
|
|||||||
@@ -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 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 SendBox from './admin/SendBox.vue';
|
||||||
import Account from './admin/Account.vue';
|
import Account from './admin/Account.vue';
|
||||||
import CreateAccount from './admin/CreateAccount.vue';
|
import CreateAccount from './admin/CreateAccount.vue';
|
||||||
|
import AccountSettings from './admin/AccountSettings.vue';
|
||||||
import Mails from './admin/Mails.vue';
|
import Mails from './admin/Mails.vue';
|
||||||
import MailsUnknow from './admin/MailsUnknow.vue';
|
import MailsUnknow from './admin/MailsUnknow.vue';
|
||||||
import Maintenance from './admin/Maintenance.vue';
|
import Maintenance from './admin/Maintenance.vue';
|
||||||
@@ -35,6 +36,7 @@ const { t } = useI18n({
|
|||||||
mails: 'Emails',
|
mails: 'Emails',
|
||||||
account: 'Account',
|
account: 'Account',
|
||||||
account_create: 'Create Account',
|
account_create: 'Create Account',
|
||||||
|
account_settings: 'Account Settings',
|
||||||
unknow: 'Mails with unknow receiver',
|
unknow: 'Mails with unknow receiver',
|
||||||
senderAccess: 'Sender Access Control',
|
senderAccess: 'Sender Access Control',
|
||||||
sendBox: 'Send Box',
|
sendBox: 'Send Box',
|
||||||
@@ -47,6 +49,7 @@ const { t } = useI18n({
|
|||||||
mails: '邮件',
|
mails: '邮件',
|
||||||
account: '账号',
|
account: '账号',
|
||||||
account_create: '创建账号',
|
account_create: '创建账号',
|
||||||
|
account_settings: '账号设置',
|
||||||
unknow: '无收件人邮件',
|
unknow: '无收件人邮件',
|
||||||
senderAccess: '发件权限控制',
|
senderAccess: '发件权限控制',
|
||||||
sendBox: '发件箱',
|
sendBox: '发件箱',
|
||||||
@@ -84,6 +87,9 @@ onMounted(async () => {
|
|||||||
<n-tab-pane name="account_create" :tab="t('account_create')">
|
<n-tab-pane name="account_create" :tab="t('account_create')">
|
||||||
<CreateAccount />
|
<CreateAccount />
|
||||||
</n-tab-pane>
|
</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')">
|
<n-tab-pane name="mails" :tab="t('mails')">
|
||||||
<Mails />
|
<Mails />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
|||||||
@@ -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" />
|
:options="openSettings.domains" />
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
</n-form-item-row>
|
</n-form-item-row>
|
||||||
<div class="right">
|
<n-button @click="newEmail" type="primary" block :loading="loading">
|
||||||
<n-button @click="newEmail" type="primary" :loading="loading">
|
{{ t('creatNewEmail') }}
|
||||||
{{ t('creatNewEmail') }}
|
</n-button>
|
||||||
</n-button>
|
|
||||||
</div>
|
|
||||||
</n-card>
|
</n-card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -108,10 +106,4 @@ onMounted(async () => {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right {
|
|
||||||
text-align: right;
|
|
||||||
place-items: right;
|
|
||||||
justify-content: right;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -23,6 +23,6 @@ features:
|
|||||||
details: 支持 password 登录邮箱,使用访问密码可作为私人站点,支持附件功能
|
details: 支持 password 登录邮箱,使用访问密码可作为私人站点,支持附件功能
|
||||||
- title: 使用 rust wasm 解析邮件
|
- title: 使用 rust wasm 解析邮件
|
||||||
details: 使用 rust wasm 解析邮件,支持邮件各种RFC标准,支持附件, 速度极快
|
details: 使用 rust wasm 解析邮件,支持邮件各种RFC标准,支持附件, 速度极快
|
||||||
- title: 支持发送邮件
|
- title: 支持发送邮件(UI/API/SMTP)
|
||||||
details: 支持通过域名邮箱发送 txt 或者 html 邮件,支持 DKIM 签名
|
details: 支持通过域名邮箱发送 txt 或者 html 邮件,支持 DKIM 签名, UI/API/SMTP 发送邮件
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Hono } from 'hono'
|
|||||||
import { Jwt } from 'hono/utils/jwt'
|
import { Jwt } from 'hono/utils/jwt'
|
||||||
import { sendAdminInternalMail } from './utils'
|
import { sendAdminInternalMail } from './utils'
|
||||||
import { newAddress } from './common'
|
import { newAddress } from './common'
|
||||||
|
import { CONSTANTS } from './constants'
|
||||||
|
|
||||||
const api = new Hono()
|
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 }
|
export { api }
|
||||||
|
|||||||
@@ -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 { getDomains, getPasswords, getBooleanValue } from './utils';
|
||||||
import { newAddress } from './common'
|
import { newAddress } from './common'
|
||||||
|
import { CONSTANTS } from './constants'
|
||||||
|
|
||||||
const api = new Hono()
|
const api = new Hono()
|
||||||
|
|
||||||
@@ -169,6 +170,18 @@ api.get('/api/new_address', async (c) => {
|
|||||||
if (!name) {
|
if (!name) {
|
||||||
name = Math.random().toString(36).substring(2, 15);
|
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);
|
return newAddress(c, name, domain, true);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user