Serve password page before protected app
This commit is contained in:
parent
e1a80f5527
commit
a69804a163
@ -89,6 +89,10 @@ const server = http.createServer(async (request, response) => {
|
||||
return sendAuthRequired(response);
|
||||
}
|
||||
|
||||
if (request.method === "GET" && ACCESS_PASSWORD && isProtectedAppPage(url.pathname) && !isAuthorizedRequest(request, url)) {
|
||||
return sendLoginPage(response, url);
|
||||
}
|
||||
|
||||
if (url.pathname === "/api/desktop-instance" && request.method === "GET") {
|
||||
return sendJson(response, 200, {
|
||||
desktopRoot,
|
||||
@ -947,6 +951,60 @@ function sendRedirect(response, location) {
|
||||
response.end();
|
||||
}
|
||||
|
||||
function isProtectedAppPage(pathname) {
|
||||
return ["/", "/index.html", "/mobile.html"].includes(pathname);
|
||||
}
|
||||
|
||||
function sendLoginPage(response, url) {
|
||||
const isMobile = url.pathname === "/mobile.html";
|
||||
const next = `${url.pathname}${url.search}`;
|
||||
const error = url.searchParams.has("auth_error") ? "访问密码不正确,请重新输入。" : "";
|
||||
response.writeHead(200, {
|
||||
"content-type": "text/html; charset=utf-8",
|
||||
"cache-control": "no-store",
|
||||
});
|
||||
response.end(`<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>节目热度采集 - 访问验证</title>
|
||||
<style>
|
||||
body { margin: 0; min-height: 100vh; display: grid; place-items: center; font-family: Arial, "Microsoft YaHei", sans-serif; background: #f3f7f8; color: #18323a; }
|
||||
form { width: min(360px, calc(100vw - 32px)); background: white; border: 1px solid #cfe0e5; border-radius: 8px; padding: 24px; box-shadow: 0 12px 32px rgba(15, 45, 55, 0.12); }
|
||||
h1 { margin: 0 0 8px; font-size: 22px; }
|
||||
p { margin: 0 0 18px; color: #5a6f78; line-height: 1.5; }
|
||||
input[type="password"] { box-sizing: border-box; width: 100%; height: 42px; border: 1px solid #b9ccd2; border-radius: 6px; padding: 0 12px; font-size: 16px; }
|
||||
button { width: 100%; height: 42px; margin-top: 14px; border: 0; border-radius: 6px; background: #0f766e; color: white; font-size: 16px; font-weight: 700; cursor: pointer; }
|
||||
.error { min-height: 20px; margin-top: 12px; color: #b42318; font-size: 14px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/auth/login" method="post">
|
||||
<h1>输入访问密码</h1>
|
||||
<p>${isMobile ? "验证后进入手机录入版。" : "验证后进入节目热度采集系统。"}</p>
|
||||
<input type="hidden" name="next" value="${escapeHtmlAttribute(next)}">
|
||||
<input name="password" type="password" autocomplete="current-password" placeholder="访问密码" required autofocus>
|
||||
<button type="submit">进入系统</button>
|
||||
<div class="error">${escapeHtmlText(error)}</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>`);
|
||||
}
|
||||
|
||||
function escapeHtmlText(value) {
|
||||
return String(value ?? "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
}
|
||||
|
||||
function escapeHtmlAttribute(value) {
|
||||
return escapeHtmlText(value)
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function contentType(filePath) {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
return {
|
||||
|
||||
@ -16,6 +16,8 @@ test("server supports optional shared access password authentication", () => {
|
||||
assert.match(server, /\/api\/auth\/status/);
|
||||
assert.match(server, /\/api\/auth\/login/);
|
||||
assert.match(server, /\/auth\/login/);
|
||||
assert.match(server, /sendLoginPage/);
|
||||
assert.match(server, /isProtectedAppPage/);
|
||||
assert.match(server, /readFormBody/);
|
||||
assert.match(server, /sendRedirect/);
|
||||
assert.match(server, /isAuthorizedRequest/);
|
||||
@ -23,6 +25,14 @@ test("server supports optional shared access password authentication", () => {
|
||||
assert.match(server, /x-hotness-auth-token/i);
|
||||
});
|
||||
|
||||
test("server renders a standalone password page before protected app pages", () => {
|
||||
assert.match(server, /isProtectedAppPage\(url\.pathname\)/);
|
||||
assert.match(server, /!isAuthorizedRequest\(request, url\)/);
|
||||
assert.match(server, /sendLoginPage\(response, url\)/);
|
||||
assert.match(server, /name="next"/);
|
||||
assert.match(server, /输入访问密码/);
|
||||
});
|
||||
|
||||
test("desktop page has a password gate and sends auth token with API calls", () => {
|
||||
assert.match(desktopHtml, /id="auth-gate"/);
|
||||
assert.match(desktopHtml, /action="\/auth\/login"/);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user