From 318105443aa205252f694081bff1f35900b47c51 Mon Sep 17 00:00:00 2001 From: zhangshun <453905631@qq.com> Date: Mon, 25 May 2026 10:38:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=9B=BA=E5=AE=9A=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E5=90=AF=E5=8A=A8=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 6 ++++ server/src/auth.ts | 56 +++++++++++++++++++++++++++++++++++-- server/src/config.ts | 10 +++++++ web/src/main.ts | 18 ++++++++++-- web/src/pages/app/layout.ts | 5 ++-- web/src/types.ts | 4 +++ 6 files changed, 93 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 8ec7934..d597584 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,12 @@ PORT=3001 JWT_SECRET=change-me DB_PATH= +# login: show login page +# fixed: auto login with FIXED_LOGIN_USERNAME and open model library directly +APP_AUTH_MODE=login +FIXED_LOGIN_USERNAME=viewer +FIXED_LOGIN_ROLE=user + # local: write to server/storage and serve via /storage # cos: upload models to Tencent Cloud COS and read through CDN_BASE_URL STORAGE_DRIVER=local diff --git a/server/src/auth.ts b/server/src/auth.ts index 5b9bc83..903b016 100644 --- a/server/src/auth.ts +++ b/server/src/auth.ts @@ -1,6 +1,7 @@ import bcrypt from "bcryptjs"; import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; import { z } from "zod"; +import { config } from "./config.js"; import { db } from "./db.js"; import { AuthUser, UserRow } from "./types.js"; @@ -60,7 +61,57 @@ function normalizeExpiresAt(value: string | null | undefined) { return clean.length === 10 ? `${clean}T23:59:59` : clean; } +async function ensureFixedLoginUser() { + const username = config.fixedLogin.username.trim() || "viewer"; + const existing = db.prepare(` + SELECT id, username, password_hash, role, enabled, expires_at, created_at, updated_at + FROM users + WHERE username = ? + `).get(username) as unknown as UserRow | undefined; + if (existing) { + db.prepare(` + UPDATE users + SET role = ?, enabled = 1, expires_at = NULL, updated_at = CURRENT_TIMESTAMP + WHERE id = ? + `).run(config.fixedLogin.role, existing.id); + return db.prepare(` + SELECT id, username, password_hash, role, enabled, expires_at, created_at, updated_at + FROM users + WHERE id = ? + `).get(existing.id) as unknown as UserRow; + } + + const passwordHash = await bcrypt.hash(`fixed-${Date.now()}-${Math.random()}`, 10); + const result = db.prepare(` + INSERT INTO users (username, password_hash, role, enabled, expires_at) + VALUES (?, ?, ?, 1, NULL) + `).run(username, passwordHash, config.fixedLogin.role); + return db.prepare(` + SELECT id, username, password_hash, role, enabled, expires_at, created_at, updated_at + FROM users + WHERE id = ? + `).get(Number(result.lastInsertRowid)) as unknown as UserRow; +} + +function authUser(row: Pick): AuthUser { + return { + id: row.id, + username: row.username, + role: row.role + }; +} + export async function authRoutes(app: FastifyInstance) { + app.get("/api/auth/bootstrap", async () => { + if (config.authMode !== "fixed") { + return { mode: "login" }; + } + const user = await ensureFixedLoginUser(); + const payload = authUser(user); + const token = app.jwt.sign(payload); + return { mode: "fixed", token, user: payload }; + }); + app.post("/api/auth/register", async (request, reply) => { const body = registerSchema.parse(request.body); const passwordHash = await bcrypt.hash(body.password, 10); @@ -98,8 +149,9 @@ export async function authRoutes(app: FastifyInstance) { return reply.code(403).send({ message: "账号授权已过期" }); } - const token = app.jwt.sign({ id: user.id, username: user.username, role: user.role }); - return { token, user: { id: user.id, username: user.username, role: user.role } }; + const payload = authUser(user); + const token = app.jwt.sign(payload); + return { token, user: payload }; }); app.get("/api/auth/me", { preHandler: [requireAuth] }, async (request) => { diff --git a/server/src/config.ts b/server/src/config.ts index 7738042..2155857 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -1,6 +1,7 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; import dotenv from "dotenv"; +import type { UserRole } from "./types.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const serverRoot = path.resolve(__dirname, ".."); @@ -8,10 +9,19 @@ const serverRoot = path.resolve(__dirname, ".."); dotenv.config({ path: path.resolve(serverRoot, "..", ".env") }); dotenv.config({ path: path.resolve(serverRoot, ".env") }); +function fixedLoginRole(): UserRole { + return process.env.FIXED_LOGIN_ROLE === "admin" ? "admin" : "user"; +} + export const config = { host: process.env.HOST ?? "0.0.0.0", port: Number(process.env.PORT ?? 3001), jwtSecret: process.env.JWT_SECRET ?? "dev-only-change-me", + authMode: process.env.APP_AUTH_MODE === "fixed" ? "fixed" : "login", + fixedLogin: { + username: process.env.FIXED_LOGIN_USERNAME ?? "viewer", + role: fixedLoginRole() + }, dataDir: path.resolve(serverRoot, "data"), storageDir: path.resolve(serverRoot, "storage"), databasePath: process.env.DB_PATH ?? path.resolve(serverRoot, "data", "model-library.db"), diff --git a/web/src/main.ts b/web/src/main.ts index 30607f3..46392c2 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -2,12 +2,26 @@ import "jstree/dist/themes/default/style.min.css"; import "w2ui/w2ui-2.0.min.css"; import "./styles/base.css"; import { api } from "./services/api"; -import { getToken, setCurrentUser, clearSession } from "./services/authState"; -import type { AuthUser } from "./types"; +import { getToken, setCurrentUser, setSession, clearSession } from "./services/authState"; +import type { AuthBootstrapResponse, AuthUser } from "./types"; import { renderLogin } from "./pages/login/login"; import { renderApp } from "./pages/app/layout"; async function bootstrap() { + try { + const boot = await api("/api/auth/bootstrap"); + if (boot.mode === "fixed") { + document.body.dataset.authMode = "fixed"; + setSession(boot.token, boot.user); + await renderApp(); + return; + } + delete document.body.dataset.authMode; + } catch { + delete document.body.dataset.authMode; + clearSession(); + } + if (!getToken()) { renderLogin(renderApp); return; diff --git a/web/src/pages/app/layout.ts b/web/src/pages/app/layout.ts index 36d6f82..2ea2cbd 100644 --- a/web/src/pages/app/layout.ts +++ b/web/src/pages/app/layout.ts @@ -9,6 +9,7 @@ import "./dialogs.css"; export async function renderApp() { const currentUser = getCurrentUser(); const isAdmin = currentUser?.role === "admin"; + const isFixedLogin = document.body.dataset.authMode === "fixed"; document.querySelector("#app")!.innerHTML = `
@@ -18,7 +19,7 @@ export async function renderApp() {
${currentUser?.username ?? ""} - + ${isFixedLogin ? "" : ``}
@@ -67,7 +68,7 @@ export async function renderApp() { renderWorkspaceLayout(); - document.querySelector("#logoutBtn")!.addEventListener("click", () => { + document.querySelector("#logoutBtn")?.addEventListener("click", () => { clearSession(); renderLogin(renderApp); }); diff --git a/web/src/types.ts b/web/src/types.ts index 7b2e047..d06cc44 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -6,6 +6,10 @@ export type AuthUser = { export type UserRole = "admin" | "user"; +export type AuthBootstrapResponse = + | { mode: "login" } + | { mode: "fixed"; token: string; user: AuthUser }; + export type ManagedUser = { id: number; username: string;