From 41bed8b1db5b7c5e03452beee409a6f40bcb2cb3 Mon Sep 17 00:00:00 2001 From: Dream Hunter Date: Sat, 4 May 2024 23:52:06 +0800 Subject: [PATCH] feat: add /external/api/send_mail for body verify (#202) --- CHANGELOG.md | 1 + smtp_proxy_server/server.py | 4 ++-- .../docs/zh/guide/feature/send-mail-api.md | 20 +++++++++++++++- worker/src/send_mail_api.js | 24 ++++++++++++++++--- worker/src/utils.js | 3 +++ 5 files changed, 46 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86a7dffd..edab27c3 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 9e707911..8059a11f 100644 --- a/smtp_proxy_server/server.py +++ b/smtp_proxy_server/server.py @@ -87,6 +87,7 @@ class CustomSMTPHandler: _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 @@ class CustomSMTPHandler: _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 cec1afeb..62174530 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 96b119a6..9c94e7c3 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 18064280..23f2178e 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);