From db2b05dd7dd38a97a0624bf2da0b6e8661e6d3ec Mon Sep 17 00:00:00 2001 From: SMNET Studio Date: Tue, 11 Aug 2026 23:32:44 +0800 Subject: [PATCH] feat: add LINUXDO_AUTH_ENABLED switch to disable LINUX DO Connect login --- .env.example | 4 ++++ apps/api/public/admin.html | 11 +++++++++++ apps/api/public/app.html | 22 ++++++++++++++++------ apps/api/src/cli-doctor.ts | 3 +++ apps/api/src/config.ts | 7 +++++++ apps/api/src/index.ts | 4 +++- apps/api/src/routes.ts | 10 +++++++++- apps/api/src/runtime-settings-spec.ts | 9 +++++++++ docs/oauth-linuxdo.md | 13 +++++++++++++ 9 files changed, 75 insertions(+), 8 deletions(-) diff --git a/.env.example b/.env.example index 411c5db..4d9c70c 100644 --- a/.env.example +++ b/.env.example @@ -148,6 +148,10 @@ LINUXDO_CLIENT_SECRET= LINUXDO_REDIRECT_URI=http://127.0.0.1:8787/api/v1/auth/callback # Comma-separated LINUX DO user ids and/or usernames who become admins LINUXDO_ADMIN_IDS=12345,your_username +# Master switch for LINUX DO Connect login. Set to false to disable the +# "使用 LINUX DO 登录" button and refuse /api/v1/auth/login + /callback even +# when LINUXDO_CLIENT_* are configured (e.g. keep only username/password auth). +# LINUXDO_AUTH_ENABLED=true # Optional overrides: # LINUXDO_AUTHORIZE_URL=https://connect.linux.do/oauth2/authorize # LINUXDO_TOKEN_URL=https://connect.linux.do/oauth2/token diff --git a/apps/api/public/admin.html b/apps/api/public/admin.html index 45303a7..5ed4fa7 100644 --- a/apps/api/public/admin.html +++ b/apps/api/public/admin.html @@ -8319,9 +8319,20 @@ } } catch { $("gate").classList.add("show"); + hideAdminOauthBtn(); } } + async function hideAdminOauthBtn() { + try { + const cfg = await api("/api/v1/auth/config"); + if (cfg && cfg.oauthEnabled === false) { + const btn = $("loginBtn"); + if (btn) btn.style.display = "none"; + } + } catch (_) {} + } + function showTabError(tab, err) { const sec = document.getElementById(tab); if (!sec) return; diff --git a/apps/api/public/app.html b/apps/api/public/app.html index 374cb5c..077e39a 100644 --- a/apps/api/public/app.html +++ b/apps/api/public/app.html @@ -3929,12 +3929,22 @@ await resolveInviteFromUrl(); try { const cfg = await api("/api/v1/auth/config"); - if (cfg && cfg.localAuthEnabled === false) { - setAuthTab("login"); - if ($("authTabs")) $("authTabs").style.display = "none"; - if ($("authLoginPanel")) $("authLoginPanel").style.display = "none"; - if ($("authRegisterPanel")) $("authRegisterPanel").style.display = "none"; - setInviteBanner(null); + if (cfg) { + if (cfg.localAuthEnabled === false) { + setAuthTab("login"); + if ($("authTabs")) $("authTabs").style.display = "none"; + if ($("authLoginPanel")) $("authLoginPanel").style.display = "none"; + if ($("authRegisterPanel")) $("authRegisterPanel").style.display = "none"; + setInviteBanner(null); + } + if (cfg.oauthEnabled === false) { + const btn = $("loginBtn"); + if (btn) btn.style.display = "none"; + const divider = $("loginGate")?.querySelector(".auth-divider"); + if (divider) divider.style.display = "none"; + const note = $("loginGate")?.querySelector(".auth-footer-note"); + if (note) note.style.display = "none"; + } } } catch (_) {} } diff --git a/apps/api/src/cli-doctor.ts b/apps/api/src/cli-doctor.ts index ce7d290..e4dfbbe 100644 --- a/apps/api/src/cli-doctor.ts +++ b/apps/api/src/cli-doctor.ts @@ -75,6 +75,9 @@ async function main(): Promise { exitCode = 1; } else { ok(`OAuth redirect=${oauth.redirectUri}`); + if (!cfg.linuxdoAuthEnabled) { + warn("LINUXDO_AUTH_ENABLED=false — LINUX DO 登录已关闭(login/callback 拒绝)"); + } } if (cfg.adminIds.size === 0) { warn("LINUXDO_ADMIN_IDS 为空 — 无人自动成为管理员"); diff --git a/apps/api/src/config.ts b/apps/api/src/config.ts index 0e4726c..b9c213f 100644 --- a/apps/api/src/config.ts +++ b/apps/api/src/config.ts @@ -251,6 +251,12 @@ export interface AppConfig { inviteQuotaWindowHours: number; inviteQuotaMax: number; firstUserIsAdmin: boolean; + /** + * LINUX DO Connect OAuth login. Default on when LINUXDO_CLIENT_* configured. + * Set LINUXDO_AUTH_ENABLED=false to disable the "使用 LINUX DO 登录" path + * entirely (login + callback both refuse) even when credentials exist. + */ + linuxdoAuthEnabled: boolean; /** * Optional ops labels for multi-node fleet display (not public URLs). * WORKER_ID itself is read by BotWorkerManager from process.env. @@ -428,6 +434,7 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig { inviteQuotaWindowHours: Number(env.INVITE_QUOTA_WINDOW_HOURS ?? "24"), inviteQuotaMax: Number(env.INVITE_QUOTA_MAX ?? "3"), firstUserIsAdmin: env.FIRST_USER_IS_ADMIN !== "false", + linuxdoAuthEnabled: env.LINUXDO_AUTH_ENABLED !== "false", llmBaseUrl: env.LLM_BASE_URL ?? "https://api.openai.com/v1", llmApiKey: env.LLM_API_KEY ?? "", llmModel: env.LLM_MODEL ?? "gpt-4o-mini", diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 6ea8935..37b60a0 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -451,7 +451,9 @@ async function main(): Promise { ); console.log( oauth - ? `[oauth] LINUX DO enabled → ${oauth.redirectUri}` + ? `[oauth] LINUX DO enabled → ${oauth.redirectUri}${ + cfg.linuxdoAuthEnabled ? "" : " (登录已关闭 LINUXDO_AUTH_ENABLED=false)" + }` : "[oauth] LINUX DO 未配置(设置 LINUXDO_CLIENT_ID/SECRET/REDIRECT_URI)", ); diff --git a/apps/api/src/routes.ts b/apps/api/src/routes.ts index 3c7da57..fb13b62 100644 --- a/apps/api/src/routes.ts +++ b/apps/api/src/routes.ts @@ -648,7 +648,7 @@ export async function registerRoutes( setPublicCache(reply, CC_AUTH_CONFIG, CDN_AUTH_CONFIG); const oauth = loadLinuxDoConfig(); return { - oauthEnabled: Boolean(oauth), + oauthEnabled: Boolean(oauth) && ctx.cfg.linuxdoAuthEnabled, provider: "linux.do", localAuthEnabled: ctx.cfg.localAuthEnabled, inviteRequiredForLocal: ctx.cfg.inviteRequiredForLocal, @@ -658,6 +658,11 @@ export async function registerRoutes( app.get("/api/v1/auth/login", async (req, reply) => { const oauth = loadLinuxDoConfig(); + if (!ctx.cfg.linuxdoAuthEnabled) { + return reply + .code(503) + .send({ error: "LINUX DO 登录已关闭(LINUXDO_AUTH_ENABLED=false)" }); + } if (!oauth) { return reply .code(503) @@ -671,6 +676,9 @@ export async function registerRoutes( app.get("/api/v1/auth/callback", async (req, reply) => { const oauth = loadLinuxDoConfig(); + if (!ctx.cfg.linuxdoAuthEnabled) { + return reply.code(503).type("text/plain; charset=utf-8").send("LINUX DO 登录已关闭"); + } if (!oauth) return reply.code(503).send("oauth not configured"); const q = req.query as { code?: string; state?: string; error?: string }; if (q.error) return reply.code(400).send(`oauth error: ${q.error}`); diff --git a/apps/api/src/runtime-settings-spec.ts b/apps/api/src/runtime-settings-spec.ts index caa2ac4..127ae7e 100644 --- a/apps/api/src/runtime-settings-spec.ts +++ b/apps/api/src/runtime-settings-spec.ts @@ -73,6 +73,7 @@ export type RuntimeSettingKey = | "tryChatMaxHistory" | "personaForkEnabled" // auth / invites + | "linuxdoAuthEnabled" | "localAuthEnabled" | "passwordMinLength" | "inviteRequiredForLocal" @@ -743,6 +744,14 @@ export const SETTING_SPECS: SettingSpec[] = [ }, // ── 注册与邀请 ── + { + key: "linuxdoAuthEnabled", + env: "LINUXDO_AUTH_ENABLED", + group: "auth", + label: "启用 LINUX DO 登录", + type: "bool", + hint: "关闭后「使用 LINUX DO 登录」按钮隐藏,login/callback 一律拒绝(需已配置 LINUXDO_CLIENT_*)", + }, { key: "localAuthEnabled", env: "LOCAL_AUTH_ENABLED", diff --git a/docs/oauth-linuxdo.md b/docs/oauth-linuxdo.md index ac73ff6..657572a 100644 --- a/docs/oauth-linuxdo.md +++ b/docs/oauth-linuxdo.md @@ -30,6 +30,19 @@ PUBLIC_BASE_URL=http://127.0.0.1:8787 `LINUXDO_ADMIN_IDS`:匹配 OAuth 返回的 **用户 id** 或 **username** 即视为管理员。 +### 关闭 LINUX DO 登录 + +如需禁用 LINUX DO Connect 登录(仅保留用户名密码/邀请码): + +```env +LINUXDO_AUTH_ENABLED=false +``` + +- 前端(`/app`、`/admin`)隐藏「使用 LINUX DO 登录」按钮 +- `/api/v1/auth/login` 与 `/api/v1/auth/callback` 一律返回 503 +- 即使 `LINUXDO_CLIENT_ID/SECRET` 仍配置着也不生效 +- 该开关可在 `/admin → 设置` 运行时切换,无需重启(`localAuthEnabled` 同理) + ## 3. 流程 1. 用户访问 `/app` → 可用 **用户名密码** 登录,或点「LINUX DO 登录」