feat: 删除原系统中https的登陆逻辑

This commit is contained in:
肖应宇 2026-03-13 09:51:05 +08:00
parent e9b9cc91ef
commit 29e11f8877
8 changed files with 1 additions and 314 deletions

View File

@ -85,7 +85,6 @@ services:
# 挂载前端与服务端源码为只读,确保容器运行当前工作区代码(避免旧镜像残留) # 挂载前端与服务端源码为只读,确保容器运行当前工作区代码(避免旧镜像残留)
- ./server/src:/app/server/src:ro - ./server/src:/app/server/src:ro
- ./index.html:/app/index.html:ro - ./index.html:/app/index.html:ro
- ./login.html:/app/login.html:ro
- ./admin:/app/admin:ro - ./admin:/app/admin:ro
- ./views:/app/views:ro - ./views:/app/views:ro
- ./js:/app/js:ro - ./js:/app/js:ro

View File

@ -1440,42 +1440,6 @@
<script src="js/storage/storage.js"></script> <script src="js/storage/storage.js"></script>
<!-- 先初始化存储适配器(提供 isFrontendMode 标记) --> <!-- 先初始化存储适配器(提供 isFrontendMode 标记) -->
<script src="js/storage/storage-adapter.js"></script> <script src="js/storage/storage-adapter.js"></script>
<script src="js/boot/backend-gate.js"></script>
<!-- 后端模式访问信任门禁:后端模式且未登录时,跳转到登录页 -->
<script>
(function() {
function gateIfBackend() {
try {
if (window.DEPLOYMENT_MODE === 'backend') {
var token = localStorage.getItem('auth_token');
if (!token) {
// 允许 ?mode=frontend 显式绕过(用于演示/调试)
var m = (new URLSearchParams(window.location.search).get('mode') || '').toLowerCase();
if (m === 'frontend') return;
var redirect = encodeURIComponent(window.location.href);
window.location.replace('/login.html?redirect=' + redirect);
}
}
} catch(e) { /* ignore */ }
}
// 若显式强制 backend则立即校验否则等待自动探测事件
try {
var forced = (new URLSearchParams(window.location.search).get('mode') || '').toLowerCase();
var envMode = (window.ENV_DEPLOYMENT_MODE || '').toLowerCase();
if (forced === 'backend' || envMode === 'backend') {
gateIfBackend();
}
} catch(_) {}
// 监听存储模式自动切换事件
window.addEventListener('pb:storage-mode-changed', function(evt) {
if (evt && evt.detail && evt.detail.mode === 'backend') {
gateIfBackend();
}
});
})();
</script>
<!-- 再加载术语库存储(会依据适配器模式决定是否探测后端) --> <!-- 再加载术语库存储(会依据适配器模式决定是否探测后端) -->
<script src="js/storage/glossary-storage.js"></script> <script src="js/storage/glossary-storage.js"></script>
<script src="https://gcore.jsdelivr.net/npm/mammoth@1.4.21/mammoth.browser.min.js"></script> <script src="https://gcore.jsdelivr.net/npm/mammoth@1.4.21/mammoth.browser.min.js"></script>

View File

@ -1,133 +0,0 @@
// backend-gate.js — 在“后端模式”未登录时,拦截并跳转到登录页
// 适用范围:主站/落地页/其他静态页面(不影响 /admin 管理台)
(function () {
try {
var loc = window.location;
var path = loc.pathname || '';
// 纯本地文件访问file://)时,强制视为前端模式:不做任何后端探测或跳转
if (loc.protocol === 'file:') return;
// 排除管理台与登录页自身
if (path.startsWith('/admin') || path.endsWith('/login.html')) return;
function q(key) {
try { return new URLSearchParams(loc.search).get(key); } catch { return null; }
}
function normalizeRedirectParam(urlObj) {
try {
var red = urlObj.searchParams.get('redirect');
if (!red) return;
function deepDecode(s, limit) {
var i = 0, prev = s;
while (i++ < (limit || 8)) {
try {
var next = decodeURIComponent(prev);
if (next === prev) break;
prev = next;
} catch { break; }
}
return prev;
}
var decoded = deepDecode(red, 8);
// 连续嵌套或超长,直接归一为首页
if (red.length > 512 || decoded.length > 512 || decoded.indexOf('/login.html?redirect=') !== -1) {
urlObj.searchParams.set('redirect', '/');
return;
}
var target = null;
try { target = new URL(decoded, urlObj.origin); } catch {}
if (!target || target.origin !== urlObj.origin || target.pathname.endsWith('/login.html')) {
urlObj.searchParams.set('redirect', '/');
}
} catch {}
}
function modeForced() {
var m = (q('mode') || '').toLowerCase();
if (m === 'backend') return true;
var env = (window.ENV_DEPLOYMENT_MODE || '').toLowerCase();
return env === 'backend';
}
function modeFrontendForced() {
var m = (q('mode') || '').toLowerCase();
return m === 'frontend';
}
function hasToken() {
try { return !!localStorage.getItem('auth_token'); } catch { return false; }
}
function buildSafeRedirectTarget() {
try {
var u = new URL(window.location.href);
// 避免递归嵌套:去除已有的 redirect 参数
u.searchParams.delete('redirect');
// 若当前已是登录页,则回首页
if (u.pathname.endsWith('/login.html')) {
u.pathname = '/';
u.search = '';
}
// 限制同源
if (u.origin !== window.location.origin) return '/';
return u.toString();
} catch { return '/'; }
}
function redirectToLogin() {
// 如果当前 URL 已包含 redirect 指向 login.html异常嵌套则强制回首页
try {
var current = new URL(window.location.href);
normalizeRedirectParam(current);
// 将标准化后的当前 URL 写回,避免下次读取到异常 redirect
history.replaceState(null, '', current.toString());
} catch {}
var safe = encodeURIComponent(buildSafeRedirectTarget());
window.location.replace('/login.html?redirect=' + safe);
}
function gateIfBackendKnown() {
// 若明确为后端模式,且未登录且未显式前端绕过 → 跳转登录
if (modeFrontendForced()) return; // 允许 ?mode=frontend 绕过
if (hasToken()) return;
redirectToLogin();
}
async function healthCheck(timeoutMs) {
try {
var ctrl = new AbortController();
var id = setTimeout(function(){ try{ctrl.abort();}catch{} }, timeoutMs);
var base = window.ENV_API_BASE_URL || '/api';
var res = await fetch(base + '/health', { signal: ctrl.signal, cache: 'no-store' });
clearTimeout(id);
return !!(res && res.ok);
} catch { return false; }
}
// 情况 1已强制后端 → 立即门禁
if (modeForced()) {
gateIfBackendKnown();
return;
}
// 情况 2依赖 storage-adapter 的自动切换事件(若其已加载)
if (typeof window !== 'undefined') {
window.addEventListener('pb:storage-mode-changed', function (evt) {
if (evt && evt.detail && evt.detail.mode === 'backend') gateIfBackendKnown();
});
}
// 情况 3页面未加载 storage-adapter例如落地页→ 自行做一次短健康检查
// 仅当未显式前端绕过时执行
if (!modeFrontendForced()) {
healthCheck(700).then(function (hasBackend) {
if (hasBackend) gateIfBackendKnown();
});
}
} catch (e) {
// 忽略所有门禁过程中的异常,避免影响前端模式体验
}
})();

View File

@ -82,9 +82,8 @@ class BackendStorage {
if (!response.ok) { if (!response.ok) {
if (response.status === 401) { if (response.status === 401) {
// Token 过期,需要重新登录 // Token 过期,清除token
AuthManager.removeToken(); AuthManager.removeToken();
window.location.href = '/login.html';
} }
throw new Error(`API Error: ${response.status}`); throw new Error(`API Error: ${response.status}`);
} }

View File

@ -1,132 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录 - Paper Burner X</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://gcore.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body class="bg-gray-100 min-h-screen flex items-center justify-center">
<script>
// 防止登录页 redirect 参数递归叠加/循环
(function () {
try {
var url = new URL(window.location.href);
var red = url.searchParams.get('redirect');
if (!red) return;
// 多层解码,防止 %25 级联编码导致的嵌套绕过
function deepDecode(s, limit) {
var i = 0, prev = s;
while (i++ < (limit || 8)) {
try {
var next = decodeURIComponent(prev);
if (next === prev) break;
prev = next;
} catch { break; }
}
return prev;
}
var decoded = deepDecode(red, 8);
// 规则:
// 1) 任意层包含 login.html?redirect= 视为递归 → 归一 '/'
// 2) 过长(>512视为异常 → 归一 '/'
var shouldClamp = (red.length > 512) || (decoded.length > 512) || (decoded.indexOf('/login.html?redirect=') !== -1);
if (shouldClamp) {
url.searchParams.set('redirect', '/');
history.replaceState(null, '', url.toString());
return;
}
// 仅允许同源,且目标不能是登录页自身
var target;
try { target = new URL(decoded, window.location.origin); } catch { target = null; }
if (!target || target.origin !== window.location.origin || target.pathname.endsWith('/login.html')) {
url.searchParams.set('redirect', '/');
history.replaceState(null, '', url.toString());
}
} catch {}
})();
</script>
<div class="bg-white p-8 rounded-lg shadow w-full max-w-md">
<h1 class="text-2xl font-bold mb-6 text-center">登录到 Paper Burner X</h1>
<form id="loginForm" class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700">邮箱</label>
<input id="email" type="email" required class="mt-1 block w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="you@example.com" />
</div>
<div>
<label class="block text-sm font-medium text-gray-700">密码</label>
<input id="password" type="password" required class="mt-1 block w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="••••••••" />
</div>
<button type="submit" class="w-full bg-blue-600 text-white py-2 rounded-md hover:bg-blue-700">登录</button>
<p id="error" class="text-red-600 text-sm hidden"></p>
</form>
<div class="text-sm text-gray-500 mt-4">
<p>默认管理员:<code>admin@paperburner.local / admin123456</code></p>
<p class="mt-1">管理员入口:<a href="/admin" class="text-blue-600 hover:underline">/admin</a></p>
</div>
</div>
<script>
const API_BASE = window.location.origin + '/api';
function getSafeRedirect() {
try {
const p = new URLSearchParams(window.location.search);
const raw = p.get('redirect');
if (!raw) return '/';
// 多层解码,长度限制
function deepDecode(s, limit) {
let i = 0, prev = s;
while (i++ < (limit || 8)) {
try {
const next = decodeURIComponent(prev);
if (next === prev) break;
prev = next;
} catch { break; }
}
return prev;
}
const decoded = deepDecode(raw, 8);
if (decoded.length > 512 || decoded.indexOf('/login.html?redirect=') !== -1) return '/';
const u = new URL(decoded, window.location.origin);
// 仅允许同源回跳,且不指向 login.html 防止循环
if (u.origin !== window.location.origin) return '/';
if (u.pathname.endsWith('/login.html')) return '/';
u.searchParams.delete('redirect');
return u.toString();
} catch { return '/'; }
}
function getRedirect() {
try {
const p = new URLSearchParams(window.location.search);
return p.get('redirect') || '/';
} catch { return '/'; }
}
document.getElementById('loginForm').addEventListener('submit', async (e) => {
e.preventDefault();
const email = document.getElementById('email').value.trim();
const password = document.getElementById('password').value;
const err = document.getElementById('error');
err.classList.add('hidden');
err.textContent = '';
try {
const res = await axios.post(`${API_BASE}/auth/login`, { email, password });
if (res.data && res.data.token) {
// 与前端适配器一致的存储键
localStorage.setItem('auth_token', res.data.token);
window.location.href = getSafeRedirect();
} else {
throw new Error('登录响应异常');
}
} catch (e2) {
err.textContent = e2?.response?.data?.error || e2.message || '登录失败';
err.classList.remove('hidden');
}
});
</script>
</body>
</html>

View File

@ -9,9 +9,6 @@ async function main() {
const admin = await request(app).get('/admin'); const admin = await request(app).get('/admin');
console.log('ADMIN', admin.status, admin.headers['content-type']); console.log('ADMIN', admin.status, admin.headers['content-type']);
const login = await request(app).get('/login.html');
console.log('LOGIN', login.status, login.headers['content-type']);
if (health.status !== 200) { if (health.status !== 200) {
process.exitCode = 1; process.exitCode = 1;
} }

View File

@ -344,11 +344,6 @@ app.use('/api/prompt-pool', promptPoolRoutes);
// ==================== 前端路由SPA ==================== // ==================== 前端路由SPA ====================
// 登录页需显式返回 login.html避免被通配符 * 误回退到 index.html
app.get('/login.html', (req, res) => {
res.sendFile(join(rootPath, 'login.html'));
});
// 管理员面板 // 管理员面板
app.get('/admin*', (req, res) => { app.get('/admin*', (req, res) => {
res.sendFile(join(rootPath, 'admin/index.html')); res.sendFile(join(rootPath, 'admin/index.html'));

View File

@ -11,8 +11,6 @@
<script src="https://gcore.jsdelivr.net/npm/iconify-icon@2.0.0/dist/iconify-icon.min.js"></script> <script src="https://gcore.jsdelivr.net/npm/iconify-icon@2.0.0/dist/iconify-icon.min.js"></script>
<!-- GitHub Stars 统一获取模块 --> <!-- GitHub Stars 统一获取模块 -->
<script src="../../js/utils/github-stars.js"></script> <script src="../../js/utils/github-stars.js"></script>
<!-- 后端模式未登录门禁(在有后端部署时,直接要求登录) -->
<script src="/js/boot/backend-gate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
<style> <style>
/* 谷歌字体 Inter (更专业、现代) */ /* 谷歌字体 Inter (更专业、现代) */