diff --git a/CHANGELOG.md b/CHANGELOG.md index 368ccf7d..9c856afd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ - Docs: Update new-address-api.md (#360) - feat: worker 增加 `ADMIN_USER_ROLE` 配置, 用于配置管理员用户角色,此角色的用户可访问 admin 管理页面 (#363) -- feat: worker 增加 `SHOW_GITHUB` 配置, 用于配置是否显示 github 链接 +- feat: worker 增加 `DISABLE_SHOW_GITHUB` 配置, 用于配置是否显示 github 链接 +- feat: worker 增加 `NO_LIMIT_SEND_ROLE` 配置, 用于配置可以无限发送邮件的角色 ## v0.6.1 diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js index a74bbe9b..a323b532 100644 --- a/frontend/src/store/index.js +++ b/frontend/src/store/index.js @@ -25,7 +25,7 @@ export const useGlobalState = createGlobalState( cfTurnstileSiteKey: '', enableWebhook: false, isS3Enabled: false, - showGithub: false, + showGithub: true, }) const settings = ref({ fetched: false, diff --git a/vitepress-docs/docs/en/cli.md b/vitepress-docs/docs/en/cli.md index 1bf652a9..73d4fd34 100644 --- a/vitepress-docs/docs/en/cli.md +++ b/vitepress-docs/docs/en/cli.md @@ -109,9 +109,10 @@ ENABLE_AUTO_REPLY = false # ENABLE_WEBHOOK = true # Footer text # COPYRIGHT = "Dream Hunter" -# SHOW_GITHUB = true # Show GitHub link +# DISABLE_SHOW_GITHUB = true # Disable Show GitHub link # default send balance, if not set, it will be 0 # DEFAULT_SEND_BALANCE = 1 +# NO_LIMIT_SEND_ROLE = "vip" # the role which can send emails without limit # Turnstile verification configuration # CF_TURNSTILE_SITE_KEY = "" # CF_TURNSTILE_SECRET_KEY = "" diff --git a/vitepress-docs/docs/zh/guide/cli/worker.md b/vitepress-docs/docs/zh/guide/cli/worker.md index b3e39c57..83b876f8 100644 --- a/vitepress-docs/docs/zh/guide/cli/worker.md +++ b/vitepress-docs/docs/zh/guide/cli/worker.md @@ -80,9 +80,10 @@ ENABLE_AUTO_REPLY = false # ENABLE_WEBHOOK = true # 前端界面页脚文本 # COPYRIGHT = "Dream Hunter" -# SHOW_GITHUB = true # 是否显示 GitHub 链接 +# DISABLE_SHOW_GITHUB = true # 是否显示 GitHub 链接 # 默认发送邮件余额,如果不设置,将为 0 # DEFAULT_SEND_BALANCE = 1 +# NO_LIMIT_SEND_ROLE = "vip" # 可以无限发送邮件的角色 # Turnstile 人机验证配置 # CF_TURNSTILE_SITE_KEY = "" # CF_TURNSTILE_SECRET_KEY = "" diff --git a/worker/src/commom_api.ts b/worker/src/commom_api.ts index 0a96acde..67b709ef 100644 --- a/worker/src/commom_api.ts +++ b/worker/src/commom_api.ts @@ -35,7 +35,7 @@ api.get('/open_api/settings', async (c) => { "enableWebhook": getBooleanValue(c.env.ENABLE_WEBHOOK), "isS3Enabled": isS3Enabled(c), "version": CONSTANTS.VERSION, - "showGithub": getBooleanValue(c.env.SHOW_GITHUB), + "showGithub": !getBooleanValue(c.env.DISABLE_SHOW_GITHUB), }); }) diff --git a/worker/src/mails_api/index.ts b/worker/src/mails_api/index.ts index fd82ef5b..7da6a12a 100644 --- a/worker/src/mails_api/index.ts +++ b/worker/src/mails_api/index.ts @@ -1,7 +1,7 @@ import { Hono } from 'hono' import { HonoCustomType } from "../types"; -import { getBooleanValue, getJsonSetting, checkCfTurnstile } from '../utils'; +import { getBooleanValue, getJsonSetting, checkCfTurnstile, getStringValue } from '../utils'; import { newAddress, handleListQuery, deleteAddressWithData, getAddressPrefix, getAllowDomains } from '../common' import { CONSTANTS } from '../constants' import auto_reply from './auto_reply' @@ -49,6 +49,7 @@ api.delete('/api/mails/:id', async (c) => { api.get('/api/settings', async (c) => { const { address, address_id } = c.get("jwtPayload") + const user_role = c.get("userRolePayload") if (address_id && address_id > 0) { try { const db_address_id = await c.env.DB.prepare( @@ -82,7 +83,8 @@ api.get('/api/settings', async (c) => { } catch (e) { console.warn("Failed to update address") } - const balance = await c.env.DB.prepare( + const is_no_limit_send_balance = user_role && user_role === getStringValue(c.env.NO_LIMIT_SEND_ROLE); + const balance = is_no_limit_send_balance ? 99999 : await c.env.DB.prepare( `SELECT balance FROM address_sender where address = ? and enabled = 1` ).bind(address).first("balance"); return c.json({ diff --git a/worker/src/mails_api/send_mail_api.ts b/worker/src/mails_api/send_mail_api.ts index 0ee56bf7..9ba2bf64 100644 --- a/worker/src/mails_api/send_mail_api.ts +++ b/worker/src/mails_api/send_mail_api.ts @@ -4,7 +4,7 @@ import { createMimeMessage } from 'mimetext'; import { Resend } from 'resend'; import { CONSTANTS } from '../constants' -import { getJsonSetting, getDomains, getIntValue, getBooleanValue } from '../utils'; +import { getJsonSetting, getDomains, getIntValue, getBooleanValue, getStringValue } from '../utils'; import { GeoData } from '../models' import { handleListQuery } from '../common' import { HonoCustomType } from '../types'; @@ -105,13 +105,17 @@ export const sendMail = async ( if (!domains.includes(mailDomain)) { throw new Error("Invalid domain") } - // check permission - const balance = await c.env.DB.prepare( - `SELECT balance FROM address_sender + const user_role = c.get("userRolePayload"); + const is_no_limit_send_balance = user_role && user_role === getStringValue(c.env.NO_LIMIT_SEND_ROLE); + if (!is_no_limit_send_balance) { + // check permission + const balance = await c.env.DB.prepare( + `SELECT balance FROM address_sender where address = ? and enabled = 1` - ).bind(address).first("balance"); - if (!balance || balance <= 0) { - throw new Error("No balance") + ).bind(address).first("balance"); + if (!balance || balance <= 0) { + throw new Error("No balance") + } } const { from_name, to_mail, to_name, @@ -154,7 +158,7 @@ export const sendMail = async ( throw new Error("Please enable resend or verified address list") } // update balance - if (!sendByVerifiedAddressList) { + if (!sendByVerifiedAddressList && !is_no_limit_send_balance) { try { const { success } = await c.env.DB.prepare( `UPDATE address_sender SET balance = balance - 1 where address = ?` diff --git a/worker/src/types.d.ts b/worker/src/types.d.ts index c54f14c5..d52d45fc 100644 --- a/worker/src/types.d.ts +++ b/worker/src/types.d.ts @@ -33,9 +33,10 @@ export type Bindings = { ENABLE_USER_DELETE_EMAIL: string | boolean | undefined ENABLE_INDEX_ABOUT: string | boolean | undefined DEFAULT_SEND_BALANCE: number | string | undefined + NO_LIMIT_SEND_ROLE: string | undefined | null ADMIN_CONTACT: string | undefined COPYRIGHT: string | undefined - SHOW_GITHUB: string | boolean | undefined + DISABLE_SHOW_GITHUB: string | boolean | undefined FORWARD_ADDRESS_LIST: string | string[] | undefined // s3 config @@ -72,7 +73,8 @@ type UserPayload = { type Variables = { userPayload: UserPayload, - jwtPayload: JwtPayload + userRolePayload: string | undefined | null, + jwtPayload: JwtPayload, } type HonoCustomType = { diff --git a/worker/src/worker.ts b/worker/src/worker.ts index e7b5fddc..df93671d 100644 --- a/worker/src/worker.ts +++ b/worker/src/worker.ts @@ -75,6 +75,26 @@ const checkUserPayload = async ( } } +const checkoutUserRolePayload = async ( + c: Context +): Promise => { + try { + const token = c.req.raw.headers.get("x-user-access-token"); + if (!token) return; + const payload = await Jwt.verify(token, c.env.JWT_SECRET, "HS256"); + // check expired + if (!payload.exp) return; + // exp is in seconds + if (payload.exp < Math.floor(Date.now() / 1000)) { + return; + } + if (typeof payload?.user_role !== "string") return; + c.set("userRolePayload", payload.user_role); + } catch (e) { + console.error(e); + } +} + // api auth app.use('/api/*', async (c, next) => { // check header x-custom-auth @@ -90,6 +110,11 @@ app.use('/api/*', async (c, next) => { await next(); return; } + if (c.req.path.startsWith("/api/settings") + || c.req.path.startsWith("/api/send_mail") + ) { + await checkoutUserRolePayload(c); + } return jwt({ secret: c.env.JWT_SECRET, alg: "HS256" })(c, next); }); // user_api auth diff --git a/worker/wrangler.toml.template b/worker/wrangler.toml.template index 9ef187a1..d41514b4 100644 --- a/worker/wrangler.toml.template +++ b/worker/wrangler.toml.template @@ -51,7 +51,7 @@ ENABLE_AUTO_REPLY = false # ENABLE_WEBHOOK = true # Footer text # COPYRIGHT = "Dream Hunter" -# SHOW_GITHUB = true +# DISABLE_SHOW_GITHUB = true # default send balance, if not set, it will be 0 # DEFAULT_SEND_BALANCE = 1 # Turnstile verification