Skip to content

Commit

Permalink
feat: add address_block_list for new address (#185)
Browse files Browse the repository at this point in the history
  • Loading branch information
dreamhunter2333 authored May 2, 2024
1 parent e81142f commit 83b9bc9
Show file tree
Hide file tree
Showing 12 changed files with 178 additions and 20 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
dist/
test/
.vscode/
13 changes: 11 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -52,7 +61,7 @@ set
## v0.2.9

- 添加富文本编辑器
- admin 联系方式,不配置则不显示,可配置任意字符串 `ADMIN_CONTACT = "[email protected]"`
- admin 联系方式,不配置则不显示,可配置任意字符串 `ADMIN_CONTACT = "[email protected]"`
- 默认发送邮件余额,如果不设置,将为 0 `DEFAULT_SEND_BALANCE = 1`

## v0.2.8
Expand Down
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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] 增加自动回复功能
Expand All @@ -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 changes: 6 additions & 0 deletions db/2024-05-01-patch.sql
Original file line number Diff line number Diff line change
@@ -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
);
7 changes: 7 additions & 0 deletions db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
6 changes: 6 additions & 0 deletions frontend/src/views/Admin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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',
Expand All @@ -47,6 +49,7 @@ const { t } = useI18n({
mails: '邮件',
account: '账号',
account_create: '创建账号',
account_settings: '账号设置',
unknow: '无收件人邮件',
senderAccess: '发件权限控制',
sendBox: '发件箱',
Expand Down Expand Up @@ -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>
Expand Down
84 changes: 84 additions & 0 deletions frontend/src/views/admin/AccountSettings.vue
Original file line number Diff line number Diff line change
@@ -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>
14 changes: 3 additions & 11 deletions frontend/src/views/admin/CreateAccount.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -108,10 +106,4 @@ onMounted(async () => {
justify-content: center;
margin: 20px;
}
.right {
text-align: right;
place-items: right;
justify-content: right;
}
</style>
4 changes: 2 additions & 2 deletions vitepress-docs/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 发送邮件
---
33 changes: 33 additions & 0 deletions worker/src/admin_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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 changes: 3 additions & 0 deletions worker/src/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const CONSTANTS = {
ADDRESS_BLOCK_LIST_KEY: 'address_block_list',
}
13 changes: 13 additions & 0 deletions worker/src/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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);
})

Expand Down

0 comments on commit 83b9bc9

Please sign in to comment.