diff --git a/.gitignore b/.gitignore index 47c8315f..021ff1e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ dist/ test/ +.vscode/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a872ac6..9c540f96 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/README.md b/README.md index a364b54e..7e9306c3 100644 --- a/README.md +++ b/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 diff --git a/db/2024-05-01-patch.sql b/db/2024-05-01-patch.sql new file mode 100644 index 00000000..542d2681 --- /dev/null +++ b/db/2024-05-01-patch.sql @@ -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 +); diff --git a/db/schema.sql b/db/schema.sql index 5ac38d05..563890e1 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -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 +); diff --git a/frontend/src/views/Admin.vue b/frontend/src/views/Admin.vue index 0a7b4833..674ce851 100644 --- a/frontend/src/views/Admin.vue +++ b/frontend/src/views/Admin.vue @@ -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 () => { + + + diff --git a/frontend/src/views/admin/AccountSettings.vue b/frontend/src/views/admin/AccountSettings.vue new file mode 100644 index 00000000..df85c27b --- /dev/null +++ b/frontend/src/views/admin/AccountSettings.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/frontend/src/views/admin/CreateAccount.vue b/frontend/src/views/admin/CreateAccount.vue index 9cdf63b3..63e7e2bd 100644 --- a/frontend/src/views/admin/CreateAccount.vue +++ b/frontend/src/views/admin/CreateAccount.vue @@ -91,11 +91,9 @@ onMounted(async () => { :options="openSettings.domains" /> -
- - {{ t('creatNewEmail') }} - -
+ + {{ t('creatNewEmail') }} + @@ -108,10 +106,4 @@ onMounted(async () => { justify-content: center; margin: 20px; } - -.right { - text-align: right; - place-items: right; - justify-content: right; -} diff --git a/vitepress-docs/docs/index.md b/vitepress-docs/docs/index.md index 6f21cc2d..12d44173 100644 --- a/vitepress-docs/docs/index.md +++ b/vitepress-docs/docs/index.md @@ -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 发送邮件 --- diff --git a/worker/src/admin_api.js b/worker/src/admin_api.js index 097be69d..f2208da5 100644 --- a/worker/src/admin_api.js +++ b/worker/src/admin_api.js @@ -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 } diff --git a/worker/src/constants.js b/worker/src/constants.js new file mode 100644 index 00000000..6f1630d6 --- /dev/null +++ b/worker/src/constants.js @@ -0,0 +1,3 @@ +export const CONSTANTS = { + ADDRESS_BLOCK_LIST_KEY: 'address_block_list', +} diff --git a/worker/src/router.js b/worker/src/router.js index 768dbb09..b77eb756 100644 --- a/worker/src/router.js +++ b/worker/src/router.js @@ -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); })