diff --git a/src/server.js b/src/server.js
index e9b3ff5..0146f83 100644
--- a/src/server.js
+++ b/src/server.js
@@ -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(`
+
+
+
+
+ 节目热度采集 - 访问验证
+
+
+
+
+
+`);
+}
+
+function escapeHtmlText(value) {
+ return String(value ?? "")
+ .replace(/&/g, "&")
+ .replace(//g, ">");
+}
+
+function escapeHtmlAttribute(value) {
+ return escapeHtmlText(value)
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+}
+
function contentType(filePath) {
const ext = path.extname(filePath).toLowerCase();
return {
diff --git a/test/access-password.test.js b/test/access-password.test.js
index 6faf490..f0b64ff 100644
--- a/test/access-password.test.js
+++ b/test/access-password.test.js
@@ -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"/);