From ba81e304c17439a7169f0ac04066edc1ce8778f8 Mon Sep 17 00:00:00 2001 From: SMNET Studio Date: Wed, 12 Aug 2026 00:56:46 +0800 Subject: [PATCH] fix: don't cache /api/v1/auth/config so LINUXDO_AUTH_ENABLED applies immediately; document local-only admin setup --- .env.example | 2 ++ apps/api/src/cache-headers.ts | 4 ---- apps/api/src/routes.ts | 5 ++--- docs/cloudflare.md | 10 ++++------ docs/oauth-linuxdo.md | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 41 insertions(+), 13 deletions(-) diff --git a/.env.example b/.env.example index 4d9c70c..861473d 100644 --- a/.env.example +++ b/.env.example @@ -170,6 +170,8 @@ LINUXDO_ADMIN_IDS=12345,your_username # Per user: max INVITE_QUOTA_MAX codes every INVITE_QUOTA_WINDOW_HOURS hours # INVITE_QUOTA_WINDOW_HOURS=24 # INVITE_QUOTA_MAX=3 +# 纯本地部署:LINUXDO_ADMIN_IDS 留空 + FIRST_USER_IS_ADMIN=true 时, +# 第一个本地注册用户自动成为管理员(首个用户无需邀请码)。见 docs/oauth-linuxdo.md。 # Product DEFAULT_PERSONA_SLUG=catgirl diff --git a/apps/api/src/cache-headers.ts b/apps/api/src/cache-headers.ts index f52afca..bf106c5 100644 --- a/apps/api/src/cache-headers.ts +++ b/apps/api/src/cache-headers.ts @@ -40,10 +40,6 @@ export const CC_PRIVATE_STICKER = */ export const CC_PRIVATE_QR = "private, no-store" as const; -/** Auth config (static-ish). */ -export const CC_AUTH_CONFIG = "public, max-age=60" as const; -export const CDN_AUTH_CONFIG = "public, max-age=300" as const; - /** Default for authenticated / dynamic APIs. */ export const CC_PRIVATE_NO_STORE = "private, no-store" as const; diff --git a/apps/api/src/routes.ts b/apps/api/src/routes.ts index fb13b62..aa8d482 100644 --- a/apps/api/src/routes.ts +++ b/apps/api/src/routes.ts @@ -193,16 +193,15 @@ import { StickerSecurityError, } from "./sticker-security.js"; import { - CC_AUTH_CONFIG, CC_CDN_STICKER, CC_NO_STORE, CC_PRIVATE_NO_STORE, CC_PRIVATE_QR, CC_PRIVATE_STICKER, - CDN_AUTH_CONFIG, CDN_CDN_STICKER, etagFromHash, ifNoneMatchHits, + setPrivateNoStore, setPublicCache, } from "./cache-headers.js"; import { qrSvg } from "./qrcode.js"; @@ -645,7 +644,7 @@ export async function registerRoutes( const authInvitePeekLimiter = new RateLimiter(40, 60_000); app.get("/api/v1/auth/config", async (_req, reply) => { - setPublicCache(reply, CC_AUTH_CONFIG, CDN_AUTH_CONFIG); + setPrivateNoStore(reply); const oauth = loadLinuxDoConfig(); return { oauthEnabled: Boolean(oauth) && ctx.cfg.linuxdoAuthEnabled, diff --git a/docs/cloudflare.md b/docs/cloudflare.md index a4db0f0..0c0afe9 100644 --- a/docs/cloudflare.md +++ b/docs/cloudflare.md @@ -24,7 +24,7 @@ | `/app` `/admin` | `public, max-age=60` | `max-age=3600, swr=86400` | 无(壳静态;数据走 API) | | `/og.jpg` | `public, max-age=86400, immutable` | `max-age=604800` | 无 | | `/cdn/s/:id?v=` | `public, max-age=31536000, immutable` | 同左 | **无**;仅 public+approved+enabled | -| `/api/v1/auth/config` | `public, max-age=60` | `max-age=300` | 无 | +| `/api/v1/auth/config` | `private, no-store` | — | 无(登录方式开关需实时) | | `/health` | `no-store` | — | 无 | | 其余 `/api/v1/**` | `private, no-store` | — | Cookie 会话 | | 鉴权表情图 | `private, no-store` | — | 登录 / admin | @@ -55,7 +55,7 @@ HTML 带 `ETag` + `Cache-Tag: html-shell`;公开表情带 `Cache-Tag: sticker- - **Name:** `bypass-api-private` - **When:** - `(starts_with(http.request.uri.path, "/api/v1/") and not http.request.uri.path eq "/api/v1/auth/config")` + `starts_with(http.request.uri.path, "/api/v1/")` 或方法为 `POST` / `PUT` / `PATCH` / `DELETE` - **Then:** Cache eligibility = **Bypass cache** @@ -84,11 +84,9 @@ HTML 带 `ETag` + `Cache-Tag: html-shell`;公开表情带 `Cache-Tag: sticker- - **Then:** Eligible for cache;Edge TTL Respect origin;**Ignore cookies** - Cache key **保留 query string**(`v=` 内容哈希) -### Rule 5 — auth/config 短缓存 +### Rule 5 — ~~auth/config 短缓存~~(已移除) -- **Name:** `cache-auth-config` -- **When:** `http.request.uri.path eq "/api/v1/auth/config"` -- **Then:** Eligible for cache;Edge TTL 5 minutes 或 Respect origin;Ignore cookies +> `/api/v1/auth/config` 现返回 `no-store`(登录方式开关需实时生效,不应缓存)。Cloudflare 默认尊重源站 `Cache-Control`,无 `public` 的响应不进共享缓存,故该路径**无需**任何缓存规则。 ### 默认 diff --git a/docs/oauth-linuxdo.md b/docs/oauth-linuxdo.md index 657572a..02d8355 100644 --- a/docs/oauth-linuxdo.md +++ b/docs/oauth-linuxdo.md @@ -43,6 +43,39 @@ LINUXDO_AUTH_ENABLED=false - 即使 `LINUXDO_CLIENT_ID/SECRET` 仍配置着也不生效 - 该开关可在 `/admin → 设置` 运行时切换,无需重启(`localAuthEnabled` 同理) +> 注意:如果**不重启服务**或浏览器/CDN 仍缓存旧的 `/api/v1/auth/config` 响应, +> 前端按钮可能不会立即消失。此接口现在返回 `no-store`,但请确保已重启进程、 +> 并清除 Cloudflare 边缘缓存(`/api/v1/auth/config` 曾允许最长 5 分钟缓存)。 +> +> **`linuxdoAuthEnabled` 是运行时设置**:`/admin → 设置` 页面存到 Redis 的 +> 覆盖值会**优先于 `.env`**。若曾在该页把开关切到 `true`(或 Redis 残留旧值), +> 需回到 `/admin → 设置` 把它关闭(或「重置全部设置」),`.env` 的 +> `LINUXDO_AUTH_ENABLED=false` 才会生效。 + +### 完全不用 LINUX DO:纯本地管理员 + +不配置/不启用 LINUX DO,仅用「用户名+密码」管理,方法如下: + +```env +LINUXDO_AUTH_ENABLED=false +LOCAL_AUTH_ENABLED=true # 用户名+密码登录(默认已开) +FIRST_USER_IS_ADMIN=true # 首个注册用户自动成为管理员(默认) +LINUXDO_ADMIN_IDS= # 留空,交给 FIRST_USER_IS_ADMIN 引导 +``` + +**步骤**: + +1. 按上面配置后启动服务 +2. 打开 `/app` → **注册**(第一个注册用户因 `FIRST_USER_IS_ADMIN` 自动成为超管) + - 若 `INVITE_REQUIRED_FOR_LOCAL=true` 且是第一个用户(库中尚无任何用户), + `bootstrap` 逻辑会跳过邀请码要求,直接成为管理员 +3. 用该账号登录 `/admin`,进入 **用户** 页即可给其他账号「授予管理员」 + +> `LINUXDO_ADMIN_IDS` 为空且首个本地用户注册时,`createLocalUser` 的 +> `bootstrapAdmin` 分支(`firstUserIsAdmin && totalUsers===0 && adminIds.size===0`) +> 会将其标记为管理员——这就是纯本地部署下第一位管理员的来源。 +> 之后新增管理员请在 `/admin → 用户` 手动授予。 + ## 3. 流程 1. 用户访问 `/app` → 可用 **用户名密码** 登录,或点「LINUX DO 登录」