mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-09-06 16:07:14 +08:00
feat: add admin API IP whitelist (#1131)
* feat: add admin API IP whitelist * fix: harden admin IP whitelist checks
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
- feat: |邮件| 新增可选的已读/未读状态,支持点击邮件自动已读和手动切换状态
|
- feat: |邮件| 新增可选的已读/未读状态,支持点击邮件自动已读和手动切换状态
|
||||||
- feat: |Admin| 数据库页面新增 D1 存储容量展示,支持选择并保存 Free 或 Workers Paid 套餐,对比当前数据库大小和容量上限
|
- feat: |Admin| 数据库页面新增 D1 存储容量展示,支持选择并保存 Free 或 Workers Paid 套餐,对比当前数据库大小和容量上限
|
||||||
- feat: |Admin| 创建邮箱页面支持一键生成随机邮箱名称(issue #1126)
|
- feat: |Admin| 创建邮箱页面支持一键生成随机邮箱名称(issue #1126)
|
||||||
|
- feat: |Admin API| 新增 `ADMIN_API_IP_WHITELIST`,可按来源 IP 限制所有管理接口访问
|
||||||
- feat: |用户系统| 用户中心新增发送邮件、与收件箱一致的可按绑定地址过滤的发件箱,以及地址管理凭证弹框;提供使用用户 JWT 的地址设置、发信权限申请、发信及发件箱 API
|
- feat: |用户系统| 用户中心新增发送邮件、与收件箱一致的可按绑定地址过滤的发件箱,以及地址管理凭证弹框;提供使用用户 JWT 的地址设置、发信权限申请、发信及发件箱 API
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
- feat: |Mail| Add optional read/unread status with click-to-read and manual status switching
|
- feat: |Mail| Add optional read/unread status with click-to-read and manual status switching
|
||||||
- feat: |Admin| Add D1 storage capacity details to the database page, with persistent Free and Workers Paid plan selection and a comparison between the current database size and capacity limit
|
- feat: |Admin| Add D1 storage capacity details to the database page, with persistent Free and Workers Paid plan selection and a comparison between the current database size and capacity limit
|
||||||
- feat: |Admin| Add one-click random email-name generation to the address creation page (issue #1126)
|
- feat: |Admin| Add one-click random email-name generation to the address creation page (issue #1126)
|
||||||
|
- feat: |Admin API| Add `ADMIN_API_IP_WHITELIST` to restrict all admin endpoints by source IP
|
||||||
- feat: |User| Add mail composition, inbox-style sent-item filtering by bound address, and the shared address-credentials dialog to the user center, backed by User JWT APIs for address settings, send-access requests, sending, and sent-item management
|
- feat: |User| Add mail composition, inbox-style sent-item filtering by bound address, and the shared address-credentials dialog to the user center, backed by User JWT APIs for address settings, send-access requests, sending, and sent-item management
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ JWT_SECRET = "xxx"
|
|||||||
|
|
||||||
# Admin console password, if not configured, console access is not allowed
|
# Admin console password, if not configured, console access is not allowed
|
||||||
# ADMIN_PASSWORDS = ["123", "456"]
|
# ADMIN_PASSWORDS = ["123", "456"]
|
||||||
|
# Admin API IP whitelist; applies to admin-password and Admin user-token access
|
||||||
|
# ADMIN_API_IP_WHITELIST = ["203.0.113.10"]
|
||||||
|
|
||||||
# Whether to allow users to create emails, not allowed if not configured
|
# Whether to allow users to create emails, not allowed if not configured
|
||||||
ENABLE_USER_CREATE_EMAIL = true
|
ENABLE_USER_CREATE_EMAIL = true
|
||||||
|
|||||||
@@ -23,8 +23,11 @@
|
|||||||
| Variable Name | Type | Description | Example |
|
| Variable Name | Type | Description | Example |
|
||||||
| ------------------------------ | --------- | ------------------------------------------------------- | ---------------- |
|
| ------------------------------ | --------- | ------------------------------------------------------- | ---------------- |
|
||||||
| `PASSWORDS` | JSON | Website private passwords, required after configuration | `["123", "456"]` |
|
| `PASSWORDS` | JSON | Website private passwords, required after configuration | `["123", "456"]` |
|
||||||
|
| `ADMIN_API_IP_WHITELIST` | JSON | Admin API IP whitelist; when configured, only listed IPs may access `/admin/*` | `["203.0.113.10"]` |
|
||||||
| `DISABLE_ADMIN_PASSWORD_CHECK` | Text/JSON | Warning: Admin console without password or user check | `false` |
|
| `DISABLE_ADMIN_PASSWORD_CHECK` | Text/JSON | Warning: Admin console without password or user check | `false` |
|
||||||
|
|
||||||
|
When `ADMIN_API_IP_WHITELIST` is unset or empty, source IPs are not restricted. Once configured, it applies to both admin-password and Admin user-token access, trusts only Cloudflare's `CF-Connecting-IP` header, and denies requests without that header.
|
||||||
|
|
||||||
## Email Related Variables
|
## Email Related Variables
|
||||||
|
|
||||||
| Variable Name | Type | Description | Example |
|
| Variable Name | Type | Description | Example |
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ JWT_SECRET = "xxx"
|
|||||||
|
|
||||||
# admin 控制台密码, 不配置则不允许访问控制台
|
# admin 控制台密码, 不配置则不允许访问控制台
|
||||||
# ADMIN_PASSWORDS = ["123", "456"]
|
# ADMIN_PASSWORDS = ["123", "456"]
|
||||||
|
# Admin API IP 白名单,同时限制管理员密码和 Admin 用户令牌访问
|
||||||
|
# ADMIN_API_IP_WHITELIST = ["203.0.113.10"]
|
||||||
|
|
||||||
# 是否允许用户创建邮件, 不配置则不允许
|
# 是否允许用户创建邮件, 不配置则不允许
|
||||||
ENABLE_USER_CREATE_EMAIL = true
|
ENABLE_USER_CREATE_EMAIL = true
|
||||||
|
|||||||
@@ -23,8 +23,11 @@
|
|||||||
| 变量名 | 类型 | 说明 | 示例 |
|
| 变量名 | 类型 | 说明 | 示例 |
|
||||||
| ------------------------------ | --------- | ------------------------------------ | ---------------- |
|
| ------------------------------ | --------- | ------------------------------------ | ---------------- |
|
||||||
| `PASSWORDS` | JSON | 网站私有密码, 配置后需要密码才能访问 | `["123", "456"]` |
|
| `PASSWORDS` | JSON | 网站私有密码, 配置后需要密码才能访问 | `["123", "456"]` |
|
||||||
|
| `ADMIN_API_IP_WHITELIST` | JSON | Admin API IP 白名单,配置后所有 `/admin/*` 请求仅允许列表中的 IP | `["203.0.113.10"]` |
|
||||||
| `DISABLE_ADMIN_PASSWORD_CHECK` | 文本/JSON | 警告: 管理员控制台没有密码或用户检查 | `false` |
|
| `DISABLE_ADMIN_PASSWORD_CHECK` | 文本/JSON | 警告: 管理员控制台没有密码或用户检查 | `false` |
|
||||||
|
|
||||||
|
`ADMIN_API_IP_WHITELIST` 未配置或为空数组时不限制来源 IP。配置后,它会同时限制管理员密码和 Admin 用户令牌访问,只信任 Cloudflare 提供的 `CF-Connecting-IP`,缺少该请求头也会拒绝访问。
|
||||||
|
|
||||||
## 邮箱相关变量
|
## 邮箱相关变量
|
||||||
|
|
||||||
| 变量名 | 类型 | 说明 | 示例 |
|
| 变量名 | 类型 | 说明 | 示例 |
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const messages: LocaleMessages = {
|
|||||||
UserAcceesTokenExpiredMsg: "Your access token has expired, please refresh the page",
|
UserAcceesTokenExpiredMsg: "Your access token has expired, please refresh the page",
|
||||||
UserRoleIsNotAdminMsg: "Your user role is not admin, no access to visit this page",
|
UserRoleIsNotAdminMsg: "Your user role is not admin, no access to visit this page",
|
||||||
NeedAdminPasswordMsg: "You need to provide the admin password to access this page",
|
NeedAdminPasswordMsg: "You need to provide the admin password to access this page",
|
||||||
|
AdminApiIpNotAllowedMsg: "Your IP address isn't on the admin whitelist",
|
||||||
|
|
||||||
KVNotAvailableMsg: "KV is not available, please contact the administrator",
|
KVNotAvailableMsg: "KV is not available, please contact the administrator",
|
||||||
DBNotAvailableMsg: "DB is not available, please contact the administrator",
|
DBNotAvailableMsg: "DB is not available, please contact the administrator",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export type LocaleMessages = {
|
|||||||
UserAcceesTokenExpiredMsg: string
|
UserAcceesTokenExpiredMsg: string
|
||||||
UserRoleIsNotAdminMsg: string
|
UserRoleIsNotAdminMsg: string
|
||||||
NeedAdminPasswordMsg: string
|
NeedAdminPasswordMsg: string
|
||||||
|
AdminApiIpNotAllowedMsg: string
|
||||||
|
|
||||||
KVNotAvailableMsg: string
|
KVNotAvailableMsg: string
|
||||||
DBNotAvailableMsg: string
|
DBNotAvailableMsg: string
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const messages: LocaleMessages = {
|
|||||||
UserAcceesTokenExpiredMsg: "您的访问令牌已过期, 请刷新页面",
|
UserAcceesTokenExpiredMsg: "您的访问令牌已过期, 请刷新页面",
|
||||||
UserRoleIsNotAdminMsg: "您的用户角色不是管理员, 无权访问",
|
UserRoleIsNotAdminMsg: "您的用户角色不是管理员, 无权访问",
|
||||||
NeedAdminPasswordMsg: "您需要提供管理员密码才能访问此页面",
|
NeedAdminPasswordMsg: "您需要提供管理员密码才能访问此页面",
|
||||||
|
AdminApiIpNotAllowedMsg: "当前 IP 不在管理员白名单中",
|
||||||
|
|
||||||
KVNotAvailableMsg: "KV 不可用, 请联系管理员",
|
KVNotAvailableMsg: "KV 不可用, 请联系管理员",
|
||||||
DBNotAvailableMsg: "DB 不可用, 请联系管理员",
|
DBNotAvailableMsg: "DB 不可用, 请联系管理员",
|
||||||
|
|||||||
Vendored
+1
@@ -49,6 +49,7 @@ type Bindings = {
|
|||||||
DOMAIN_LABELS: string | string[] | undefined
|
DOMAIN_LABELS: string | string[] | undefined
|
||||||
PASSWORDS: string | string[] | undefined
|
PASSWORDS: string | string[] | undefined
|
||||||
ADMIN_PASSWORDS: string | string[] | undefined
|
ADMIN_PASSWORDS: string | string[] | undefined
|
||||||
|
ADMIN_API_IP_WHITELIST: string | string[] | undefined
|
||||||
DISABLE_ADMIN_PASSWORD_CHECK: string | boolean | undefined
|
DISABLE_ADMIN_PASSWORD_CHECK: string | boolean | undefined
|
||||||
JWT_SECRET: string
|
JWT_SECRET: string
|
||||||
BLACK_LIST: string | undefined
|
BLACK_LIST: string | undefined
|
||||||
|
|||||||
+17
-3
@@ -14,7 +14,7 @@ import { api as telegramApi } from './telegram_api'
|
|||||||
import i18n from './i18n';
|
import i18n from './i18n';
|
||||||
import { email } from './email';
|
import { email } from './email';
|
||||||
import { scheduled } from './scheduled';
|
import { scheduled } from './scheduled';
|
||||||
import { getPasswords, getBooleanValue, getDomains, checkIsAdmin } from './utils';
|
import { getPasswords, getBooleanValue, getDomains, checkIsAdmin, getEnvStringList } from './utils';
|
||||||
import { checkAccessControl } from './ip_blacklist';
|
import { checkAccessControl } from './ip_blacklist';
|
||||||
|
|
||||||
const API_PATHS = [
|
const API_PATHS = [
|
||||||
@@ -221,14 +221,28 @@ app.use('/user_api/*', async (c, next) => {
|
|||||||
});
|
});
|
||||||
// admin auth
|
// admin auth
|
||||||
app.use('/admin/*', async (c, next) => {
|
app.use('/admin/*', async (c, next) => {
|
||||||
|
const lang = c.req.raw.headers.get("x-lang") || c.env.DEFAULT_LANG;
|
||||||
|
const msgs = i18n.getMessages(lang);
|
||||||
|
try {
|
||||||
|
const ipWhitelist = getEnvStringList(c.env.ADMIN_API_IP_WHITELIST)
|
||||||
|
.filter(ip => typeof ip === "string")
|
||||||
|
.map(ip => ip.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
if (ipWhitelist.length > 0) {
|
||||||
|
const reqIp = c.req.raw.headers.get("cf-connecting-ip")?.trim();
|
||||||
|
if (!reqIp || !ipWhitelist.includes(reqIp)) {
|
||||||
|
return c.text(msgs.AdminApiIpNotAllowedMsg, 403);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to check admin API IP whitelist", e);
|
||||||
|
}
|
||||||
|
|
||||||
// check header x-admin-auth
|
// check header x-admin-auth
|
||||||
if (checkIsAdmin(c)) {
|
if (checkIsAdmin(c)) {
|
||||||
await next();
|
await next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const lang = c.req.raw.headers.get("x-lang") || c.env.DEFAULT_LANG;
|
|
||||||
const msgs = i18n.getMessages(lang);
|
|
||||||
// check if user is admin
|
// check if user is admin
|
||||||
const access_token = c.req.raw.headers.get("x-user-access-token");
|
const access_token = c.req.raw.headers.get("x-user-access-token");
|
||||||
if (c.env.ADMIN_USER_ROLE && access_token) {
|
if (c.env.ADMIN_USER_ROLE && access_token) {
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ PREFIX = "tmp"
|
|||||||
# PASSWORDS = ["123", "456"]
|
# PASSWORDS = ["123", "456"]
|
||||||
# For admin panel
|
# For admin panel
|
||||||
# ADMIN_PASSWORDS = ["123", "456"]
|
# ADMIN_PASSWORDS = ["123", "456"]
|
||||||
|
# Restrict all /admin/* APIs to exact client IPv4 or IPv6 addresses
|
||||||
|
# ADMIN_API_IP_WHITELIST = ["203.0.113.10", "2001:db8::1"]
|
||||||
# warning: no password or user check for admin portal
|
# warning: no password or user check for admin portal
|
||||||
# DISABLE_ADMIN_PASSWORD_CHECK = false
|
# DISABLE_ADMIN_PASSWORD_CHECK = false
|
||||||
# ADMIN CONTACT, CAN BE ANY STRING
|
# ADMIN CONTACT, CAN BE ANY STRING
|
||||||
|
|||||||
Reference in New Issue
Block a user