diff --git a/CHANGELOG.md b/CHANGELOG.md index 86a7dffd8..edab27c36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - UI: 回复邮件按钮, 引用原始邮件文本 - 添加发送邮件地址黑名单 - 添加 `CF Turnstile` 人机验证配置 +- 添加 `/external/api/send_mail` 发送邮件 api, 使用 body 验证 ## v0.3.2 diff --git a/smtp_proxy_server/server.py b/smtp_proxy_server/server.py index 9e707911d..8059a11fe 100644 --- a/smtp_proxy_server/server.py +++ b/smtp_proxy_server/server.py @@ -87,6 +87,7 @@ async def handle_DATA(self, server: SMTP, session: Session, envelope: Envelope) _logger.info(f"Parsed mail from {from_name} to {to_mail_map}") # Send mail send_body = { + "token": session.auth_data.password.decode(), "from_name": from_name, "to_name": to_mail_map.get(to_mail), "to_mail": to_mail, @@ -99,9 +100,8 @@ async def handle_DATA(self, server: SMTP, session: Session, envelope: Envelope) _logger.info(f"Send mail {send_body}") try: res = requests.post( - f"{settings.proxy_url}/api/send_mail", + f"{settings.proxy_url}/external/api/send_mail", json=send_body, headers={ - "Authorization": f"Bearer {session.auth_data.password.decode()}", "Content-Type": "application/json" } ) diff --git a/vitepress-docs/docs/zh/guide/feature/send-mail-api.md b/vitepress-docs/docs/zh/guide/feature/send-mail-api.md index cec1afeb1..62174530e 100644 --- a/vitepress-docs/docs/zh/guide/feature/send-mail-api.md +++ b/vitepress-docs/docs/zh/guide/feature/send-mail-api.md @@ -17,7 +17,25 @@ send_body = { res = requests.post( "http://localhost:8787/api/send_mail", json=send_body, headers={ - "Authorization": f"Bearer {session.auth_data.password.decode()}", + "Authorization": f"Bearer {你的JWT密码}", + "x-custom-auth": "<你的网站密码>", + "Content-Type": "application/json" + } +) + +# 使用 body 验证 +send_body = { + "token": "<你的JWT密码> + "from_name": "发件人名字", + "to_name": "收件人名字", + "to_mail": "收件人地址", + "subject": "邮件主题", + "is_html": False, # 根据内容设置是否为 HTML + "content": "<邮件内容:html 或者 文本>", +} +res = requests.post( + "http://localhost:8787/external/api/send_mail", + json=send_body, headers={ "Content-Type": "application/json" } ) diff --git a/worker/src/send_mail_api.js b/worker/src/send_mail_api.js index 96b119a6e..9c94e7c3c 100644 --- a/worker/src/send_mail_api.js +++ b/worker/src/send_mail_api.js @@ -1,4 +1,5 @@ import { Hono } from 'hono' +import { Jwt } from 'hono/utils/jwt' import { CONSTANTS } from './constants' import { getJsonSetting } from './utils'; @@ -28,9 +29,7 @@ api.post('/api/requset_send_mail_access', async (c) => { return c.json({ status: "ok" }) }) - -api.post('/api/send_mail', async (c) => { - const { address } = c.get("jwtPayload") +const sendMail = async (c, address) => { // check permission const balance = await c.env.DB.prepare( `SELECT balance FROM address_sender @@ -132,6 +131,25 @@ api.post('/api/send_mail', async (c) => { console.warn(`Failed to save to sendbox for ${address}`); } return c.json({ status: "ok" }); +} + +api.post('/api/send_mail', async (c) => { + const { address } = c.get("jwtPayload") + return await sendMail(c, address); +}) + +api.post('/external/api/send_mail', async (c) => { + const { token } = await c.req.json(); + try { + const { address } = await Jwt.verify(token, c.env.JWT_SECRET); + if (!address) { + return c.text("No address", 400) + } + return await sendMail(c, address); + } catch (e) { + console.error("Failed to verify token", e); + return c.text("Unauthorized", 401) + } }) const getSendbox = async (c, address, limit, offset) => { diff --git a/worker/src/utils.js b/worker/src/utils.js index 180642803..23f2178e3 100644 --- a/worker/src/utils.js +++ b/worker/src/utils.js @@ -134,6 +134,9 @@ export const checkCfTurnstile = async (c, token) => { if (!c.env.CF_TURNSTILE_SITE_KEY) { return; } + if (!token) { + throw new Error("Captcha token is required"); + } const reqIp = c.req.raw.headers.get("cf-connecting-ip") let formData = new FormData(); formData.append('secret', c.env.CF_TURNSTILE_SECRET_KEY);