From af89a17f7f21ab417b5a1578cf7a696564e3184d Mon Sep 17 00:00:00 2001 From: anonsaber Date: Thu, 1 Oct 2026 09:19:49 +0800 Subject: [PATCH] feat(auth): extend admin session to 30 minutes --- README.md | 2 +- README.zh-CN.md | 2 +- docs/design.md | 2 +- docs/design.zh-CN.md | 2 +- docs/opengaps.md | 2 +- docs/opengaps.zh-CN.md | 2 +- docs/reference.md | 6 +++--- docs/reference.zh-CN.md | 7 +++---- src/state.rs | 4 ++-- web/config.js | 12 ++++++------ web/index.html | 2 +- 11 files changed, 21 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 4f62d6f..ad538ce 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ docker run --env-file .env -p 8080:8080 messageweave:latest ``` Then open `http://localhost:8080` in a browser and enter your `CONFIG_ENCRYPTION_KEY` -value. The SPA exchanges it with `POST /api/admin/session` for a 900-second admin session, +value. The SPA exchanges it with `POST /api/admin/session` for a 1,800-second admin session, then writes the business secrets to Redis with `PUT /api/business-config`. No environment variable is read or written again for business configuration. diff --git a/README.zh-CN.md b/README.zh-CN.md index c000919..366b4cd 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -36,7 +36,7 @@ docker run --env-file .env -p 8080:8080 messageweave:latest ``` 然后在浏览器打开 `http://localhost:8080`,输入你的 `CONFIG_ENCRYPTION_KEY` 值。SPA 用它 -经 `POST /api/admin/session` 换取 900 秒的 admin session,再用 `PUT /api/business-config` +经 `POST /api/admin/session` 换取 1,800 秒(30 分钟)的管理会话,再用 `PUT /api/business-config` 把业务密钥写入 Redis。此后业务配置不再读取或写入任何环境变量。 不用 Docker 的本地构建:`cargo build --locked` 产出 `message-weave` 二进制。 diff --git a/docs/design.md b/docs/design.md index 3d050ed..571d111 100644 --- a/docs/design.md +++ b/docs/design.md @@ -421,7 +421,7 @@ Testing: **No integrated test directory**, and no stand-alone util tool module. ### 7.3.1 Configuration Management API - `GET /` provides a SPA embedded in the Rust binary; `/assets/config.js` and `/assets/styles.css` provide page resources. The service does not read or write local files while running (`C-NO-LOCAL-WRITE`). - `GET /api/status` publicly returns `{ "ready": boolean, "mode": "configured" | "configuration-setup", "missing": string[], "version": string }`, which only lists the missing environment variable names. When `REDIS_URL` or `CONFIG_ENCRYPTION_KEY` is missing, SPA only displays the configuration boot status and missing variables; the management session authorization area is displayed only after the service status is confirmed to be ready=true. `version` is the `BUILD_VERSION` (`+`) baked by `build.rs` during compilation. The SPA footer uses this to confirm whether the deployment is actually implemented. -- `POST /api/admin/session` accepts `Authorization: Bearer ` and returns `{ "session": "", "expires_in": 900 }`; the admin session only saves the digest in Redis and expires after 900 seconds. `POST /api/admin/session/revoke` revokes the current session and returns `204` successfully. +- `POST /api/admin/session` accepts `Authorization: Bearer ` and returns `{ "session": "", "expires_in": 1800 }`; the admin session only saves the digest in Redis and expires after 1,800 seconds. `POST /api/admin/session/revoke` revokes the current session and returns `204` successfully. - The admin page only saves the session in JavaScript memory. Request settings `credentials: omit`, `cache: no-store`, without using cookies, localStorage or sessionStorage. The admin API accepts a valid admin session; the compatibility path also accepts `WORKER_TOKEN`. Worker transparently transmits the authentication header (`SAF-LB-PASSTHRU`) as it is. - `GET /api/config` and `PUT /api/config` only read and write non-sensitive running parameters: ```json diff --git a/docs/design.zh-CN.md b/docs/design.zh-CN.md index a1f5637..1c0d056 100644 --- a/docs/design.zh-CN.md +++ b/docs/design.zh-CN.md @@ -421,7 +421,7 @@ message-weave/ ### 7.3.1 配置管理 API - `GET /` 提供嵌入 Rust 二进制的 SPA;`/assets/config.js` 与 `/assets/styles.css` 提供页面资源。服务不在运行时读取或写入本地文件(`C-NO-LOCAL-WRITE`)。 - `GET /api/status` 公开返回 `{ "ready": boolean, "mode": "configured" | "configuration-setup", "missing": string[], "version": string }`,只列缺少的环境变量名称。缺少 `REDIS_URL` 或 `CONFIG_ENCRYPTION_KEY` 时,SPA 只显示配置引导状态与缺失变量;服务状态确认 ready=true 后才显示管理会话授权区。`version` 是 `build.rs` 在编译期烘焙的 `BUILD_VERSION`(`+`),SPA 页脚据此确认部署是否真正落地。 -- `POST /api/admin/session` 接受 `Authorization: Bearer `,返回 `{ "session": "", "expires_in": 900 }`;admin session 仅存 Redis 中的摘要并在 900 秒后过期。`POST /api/admin/session/revoke` 撤销当前 session,成功返回 `204`。 +- `POST /api/admin/session` 接受 `Authorization: Bearer `,返回 `{ "session": "", "expires_in": 1800 }`;Redis 中保存的是会话令牌的摘要,30 分钟后过期。`POST /api/admin/session/revoke` 可立即撤销会话,成功返回 `204`。 - 管理页面仅在 JavaScript 内存中保存 session。请求设置 `credentials: omit`、`cache: no-store`,不使用 Cookie、localStorage 或 sessionStorage。管理 API 接受有效 admin session;兼容路径也接受 `WORKER_TOKEN`。Worker 原样透传鉴权头(`SAF-LB-PASSTHRU`)。 - `GET /api/config` 与 `PUT /api/config` 只读取和写入非敏感运行参数: ```json diff --git a/docs/opengaps.md b/docs/opengaps.md index 42887a5..23cec8a 100644 --- a/docs/opengaps.md +++ b/docs/opengaps.md @@ -59,7 +59,7 @@ Contract baseline (see `docs/reference.md` §1 for the complete semantics of key | `ttl::PUSH_SUBSCRIPTION_SECONDS` | 300 | `push:subscription:{id}` / `push:subscription-code:{code}` | | `ttl::PUSH_REGISTRATION_SECONDS` | 604_800 | `push:registration:{sha256(callback_url)}` | | `ttl::PUSH_ORPHAN_SECONDS` | 604_800 | `push:orphan:{subscription_id}` | -| `ttl::ADMIN_SESSION_SECONDS` | 900 | `admin-session:{token}` (also the `expires_in` returned by `/admin/session`) | +| `ttl::ADMIN_SESSION_SECONDS` | 1,800 | `admin-session:{token}` (also the `expires_in` returned by `/admin/session`) | | `ttl::RETRY_COUNTER_SECONDS` | 86_400 | `retry:{stream}:{message.id}` | | `ttl::CONSENT_TEMPORARY_SECONDS` | 3_600 | `consent:ai:{chat_id}` ("temporary/once" shared with explicit `/ai on`) | | `ttl::CONSENT_TODAY_SECONDS` | 86_400 | `consent:ai:{chat_id}` ("Today") | diff --git a/docs/opengaps.zh-CN.md b/docs/opengaps.zh-CN.md index 9ccbf39..55a9ae0 100644 --- a/docs/opengaps.zh-CN.md +++ b/docs/opengaps.zh-CN.md @@ -59,7 +59,7 @@ TTL 那条真机绿灯已经用你给的 Upstash URL 跑过了(见 §1), | `ttl::PUSH_SUBSCRIPTION_SECONDS` | 300 | `push:subscription:{id}` / `push:subscription-code:{code}` | | `ttl::PUSH_REGISTRATION_SECONDS` | 604_800 | `push:registration:{sha256(callback_url)}` | | `ttl::PUSH_ORPHAN_SECONDS` | 604_800 | `push:orphan:{subscription_id}` | -| `ttl::ADMIN_SESSION_SECONDS` | 900 | `admin-session:{token}`(同时是 `/admin/session` 返回的 `expires_in`) | +| `ttl::ADMIN_SESSION_SECONDS` | 1,800 | `admin-session:{token}`(同时是 `/admin/session` 返回的 `expires_in`) | | `ttl::RETRY_COUNTER_SECONDS` | 86_400 | `retry:{stream}:{message.id}` | | `ttl::CONSENT_TEMPORARY_SECONDS` | 3_600 | `consent:ai:{chat_id}`(「临时 / 一次」与显式 `/ai on` 共用) | | `ttl::CONSENT_TODAY_SECONDS` | 86_400 | `consent:ai:{chat_id}`(「今天」) | diff --git a/docs/reference.md b/docs/reference.md index 7d295ef..09c065d 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -38,7 +38,7 @@ Conventions used throughout: | `config:business:revision` | No EX | atomic `INCR` on every config write; `SET 1 NX` on first-time init | `business_config_revision` | | `config:outbound` | No EX | `set_outbound_config` | `get_outbound_config` | | `config:enabled` | No EX | `set_enabled` | `is_enabled` | -| `config:admin_session` | EX 900 | `put_admin_session` | `admin_session_valid`; cleared by `revoke_admin_session` | +| `config:admin_session` | EX 1800 (30 minutes) | `put_admin_session` | `admin_session_valid`; cleared by `revoke_admin_session` | Notes: @@ -46,8 +46,8 @@ Notes: request-bearing entry point calls `refresh_business_config`, which only rebuilds the worker when the remote revision exceeds the cached local value — that guard is what keeps a stale or malformed snapshot from turning into a rebuild loop. -- `config:admin_session` carries no TTL metadata beyond the literal `EX 900` passed at write - time; the handler reports the same window in its response body as `expires_in: 900` +- `config:admin_session` carries no TTL metadata beyond the literal `EX 1800` passed at write + time; the handler reports the same window in its response body as `expires_in: 1800` on the handler side. - `config:enabled` is consulted as a gate; missing or `false` keeps business processing off. diff --git a/docs/reference.zh-CN.md b/docs/reference.zh-CN.md index 10a1f19..39c705a 100644 --- a/docs/reference.zh-CN.md +++ b/docs/reference.zh-CN.md @@ -38,7 +38,7 @@ | `配置:业务:修订版` |没有 EX |每次配置写入时原子“INCR”;首次初始化时“SET 1 NX”| `business_config_revision` | | `配置:出站` |没有 EX | `set_outbound_config` | `get_outbound_config` | | `配置:启用` |没有 EX | `设置启用` | `已启用` | -| `配置:admin_session` |前 900 | `put_admin_session` | `admin_session_valid`;由 `revoke_admin_session` 清除 | +| `配置:admin_session` | EX 1800(30 分钟) | `put_admin_session` | `admin_session_valid`;由 `revoke_admin_session` 清除 | 笔记: @@ -46,9 +46,8 @@ 承载请求的入口点调用“refresh_business_config”,这仅 当远程修订超出缓存的本地值时重建工作程序 - 即 Guard 可以防止陈旧或格式错误的快照变成重建循环。 -- `config:admin_session` 不携带超出写入时传递的文字 `EX 900` 的 TTL 元数据 - 时间;处理程序在其响应正文中报告相同的窗口“expires_in: 900” - 在处理程序一侧。 +- `config:admin_session` 的 TTL 由写入时传入的 `EX 1800` 决定;处理程序也在响应正文中 + 通过 `expires_in: 1800` 返回相同的有效期。 - `config:enabled` 被视为一个门;缺失或“错误”会导致业务处理中断。 ### 1.2 AI 授权 diff --git a/src/state.rs b/src/state.rs index b972930..70b9cea 100644 --- a/src/state.rs +++ b/src/state.rs @@ -65,7 +65,7 @@ pub(crate) mod ttl { pub const PUSH_ORPHAN_SECONDS: u64 = WEEK; /// `/admin` digest session. Must match the `expires_in` reported to the /// admin client. - pub const ADMIN_SESSION_SECONDS: u64 = 900; + pub const ADMIN_SESSION_SECONDS: u64 = 1_800; /// Retry counter retention. `retry_or_dlq` interpolates this into its Lua /// script, so there is exactly one literal for it. pub const RETRY_COUNTER_SECONDS: u64 = DAY; @@ -1523,7 +1523,7 @@ mod tests { assert_eq!(ttl::PUSH_SUBSCRIPTION_SECONDS, 300); assert_eq!(ttl::PUSH_REGISTRATION_SECONDS, 604_800); assert_eq!(ttl::PUSH_ORPHAN_SECONDS, 604_800); - assert_eq!(ttl::ADMIN_SESSION_SECONDS, 900); + assert_eq!(ttl::ADMIN_SESSION_SECONDS, 1_800); assert_eq!(ttl::DEDUP_TG_SECONDS, 86_400); assert_eq!(ttl::DEDUP_JMAP_SECONDS, 86_400); diff --git a/web/config.js b/web/config.js index 5053587..d2d0f0c 100644 --- a/web/config.js +++ b/web/config.js @@ -30,7 +30,7 @@ "auth.eyebrow": "Authorization required", "auth.title": "Create admin session", "auth.bodyPre": "Enter ", - "auth.bodyPost": ". The session lasts 15 minutes and lives only in this page's memory.", + "auth.bodyPost": ". The session lasts 30 minutes and lives only in this page's memory.", "auth.label": "Business encryption key", "auth.placeholder": "Paste the value", "auth.submit": "Create session", @@ -144,7 +144,7 @@ "runtime.savedToast": "Runtime parameters saved to Redis and applied immediately to subsequent requests.", "runtime.defaultsToast": "Runtime parameters restored to default-value preview. They are written to Redis only after clicking \"Save runtime parameters\".", "privacy.title": "Secrets are never echoed or persisted to the browser.", - "privacy.body": "Business configuration is submitted only to the same-origin API and encrypted into Redis by the backend; this page never reads mailbox contents and uses no local files or database.", + "privacy.body": "Your settings are encrypted before they are saved on the server. Email contents are not shown here.", "footer.note": "Configuration managed by your server", "common.required": "Required", "common.optional": "Optional", @@ -247,7 +247,7 @@ "auth.eyebrow": "需要授权", "auth.title": "管理员授权", "auth.bodyPre": "输入 ", - "auth.bodyPost": ",会话有效 15 分钟,仅保存在当前页面。", + "auth.bodyPost": ",会话有效 30 分钟,仅保存在当前页面。", "auth.label": "业务密钥", "auth.placeholder": "粘贴部署密钥", "auth.submit": "授权", @@ -357,8 +357,8 @@ "runtime.readToast": "运行参数已读取。业务配置仍需手动完整填写。", "runtime.savedToast": "运行参数已保存到 Redis,并立即应用于后续请求。", "runtime.defaultsToast": "运行参数已恢复为默认值预览。点击“保存运行参数”后才会更新 Redis。", - "privacy.title": "密钥不会显示或保存到浏览器。", - "privacy.body": "配置仅经同源 API 提交,并由后端加密保存。", + "privacy.title": "密钥不会显示或保存在浏览器里。", + "privacy.body": "你的设置会加密后保存在服务器上。这里不会显示邮件内容。", "footer.note": "配置由你的服务端管理", "common.required": "必填", "common.optional": "可选", @@ -836,7 +836,7 @@ function scheduleSessionExpiry(seconds) { if (expiryTimer) window.clearTimeout(expiryTimer); - const duration = Math.max(1, Math.min(Number(seconds) || 900, 900)); + const duration = Math.max(1, Math.min(Number(seconds) || 1_800, 1_800)); sessionExpiry.textContent = t("session.expiry", { n: Math.ceil(duration / 60) }); expiryTimer = window.setTimeout(() => { expireSession(t("session.expired")); diff --git a/web/index.html b/web/index.html index 9b12bc3..af1694d 100644 --- a/web/index.html +++ b/web/index.html @@ -53,7 +53,7 @@

Authorization required

Create admin session

-

Enter CONFIG_ENCRYPTION_KEY. The session lasts 15 minutes and lives only in this page's memory.

+

Enter CONFIG_ENCRYPTION_KEY. The session lasts 30 minutes and lives only in this page's memory.