Add access token fallback for cloud login
This commit is contained in:
parent
7bf5bcae43
commit
e1a80f5527
@ -609,6 +609,7 @@ dutyRunNow?.addEventListener("click", () => {
|
|||||||
runDutyNow();
|
runDutyNow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
consumeRedirectedAccessToken();
|
||||||
initializeApp();
|
initializeApp();
|
||||||
document.addEventListener("hotness:programs-changed", refreshPrograms);
|
document.addEventListener("hotness:programs-changed", refreshPrograms);
|
||||||
|
|
||||||
@ -617,6 +618,17 @@ async function initializeApp() {
|
|||||||
startApp();
|
startApp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function consumeRedirectedAccessToken() {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const token = params.get("access_token");
|
||||||
|
if (!token) return;
|
||||||
|
localStorage.setItem(HOTNESS_AUTH_TOKEN_KEY, token);
|
||||||
|
params.delete("access_token");
|
||||||
|
const search = params.toString();
|
||||||
|
const cleanUrl = `${window.location.pathname}${search ? `?${search}` : ""}${window.location.hash}`;
|
||||||
|
history.replaceState(null, "", cleanUrl || "/");
|
||||||
|
}
|
||||||
|
|
||||||
function startApp() {
|
function startApp() {
|
||||||
if (appStarted) return;
|
if (appStarted) return;
|
||||||
appStarted = true;
|
appStarted = true;
|
||||||
|
|||||||
@ -175,6 +175,7 @@ window.addEventListener("appinstalled", () => {
|
|||||||
updateInstallPrompt("installed");
|
updateInstallPrompt("installed");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
consumeRedirectedAccessToken();
|
||||||
initializeApp();
|
initializeApp();
|
||||||
|
|
||||||
async function initializeApp() {
|
async function initializeApp() {
|
||||||
@ -182,6 +183,17 @@ async function initializeApp() {
|
|||||||
startApp();
|
startApp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function consumeRedirectedAccessToken() {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const token = params.get("access_token");
|
||||||
|
if (!token) return;
|
||||||
|
localStorage.setItem(HOTNESS_AUTH_TOKEN_KEY, token);
|
||||||
|
params.delete("access_token");
|
||||||
|
const search = params.toString();
|
||||||
|
const cleanUrl = `${window.location.pathname}${search ? `?${search}` : ""}${window.location.hash}`;
|
||||||
|
history.replaceState(null, "", cleanUrl || "/");
|
||||||
|
}
|
||||||
|
|
||||||
async function startApp() {
|
async function startApp() {
|
||||||
if (appStarted) return;
|
if (appStarted) return;
|
||||||
appStarted = true;
|
appStarted = true;
|
||||||
|
|||||||
@ -80,7 +80,7 @@ const server = http.createServer(async (request, response) => {
|
|||||||
const body = await readFormBody(request);
|
const body = await readFormBody(request);
|
||||||
if (!ACCESS_PASSWORD || timingSafeEqualText(String(body.password || ""), ACCESS_PASSWORD)) {
|
if (!ACCESS_PASSWORD || timingSafeEqualText(String(body.password || ""), ACCESS_PASSWORD)) {
|
||||||
if (ACCESS_PASSWORD) response.setHeader("set-cookie", cookieHeader(ACCESS_TOKEN));
|
if (ACCESS_PASSWORD) response.setHeader("set-cookie", cookieHeader(ACCESS_TOKEN));
|
||||||
return sendRedirect(response, body.next || "/");
|
return sendRedirect(response, buildAuthRedirectLocation(body.next || "/", ACCESS_TOKEN));
|
||||||
}
|
}
|
||||||
return sendRedirect(response, "/?auth_error=1");
|
return sendRedirect(response, "/?auth_error=1");
|
||||||
}
|
}
|
||||||
@ -909,6 +909,14 @@ function cookieHeader(token) {
|
|||||||
return `hotness_auth=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${60 * 60 * 24 * 30}`;
|
return `hotness_auth=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${60 * 60 * 24 * 30}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildAuthRedirectLocation(location, token) {
|
||||||
|
const safeLocation = String(location || "/").startsWith("/") ? String(location || "/") : "/";
|
||||||
|
if (!token) return safeLocation;
|
||||||
|
const redirectUrl = new URL(safeLocation, "http://video-hotness.local");
|
||||||
|
redirectUrl.searchParams.set("access_token", token);
|
||||||
|
return `${redirectUrl.pathname}${redirectUrl.search}${redirectUrl.hash}`;
|
||||||
|
}
|
||||||
|
|
||||||
function timingSafeEqualText(left, right) {
|
function timingSafeEqualText(left, right) {
|
||||||
const leftBuffer = Buffer.from(String(left || ""));
|
const leftBuffer = Buffer.from(String(left || ""));
|
||||||
const rightBuffer = Buffer.from(String(right || ""));
|
const rightBuffer = Buffer.from(String(right || ""));
|
||||||
|
|||||||
@ -42,6 +42,14 @@ test("desktop login form is not blocked by JavaScript", () => {
|
|||||||
assert.doesNotMatch(desktopJs, /async function submitAccessPassword/);
|
assert.doesNotMatch(desktopJs, /async function submitAccessPassword/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("desktop can finish login from a redirected access token", () => {
|
||||||
|
assert.match(server, /buildAuthRedirectLocation/);
|
||||||
|
assert.match(server, /access_token/);
|
||||||
|
assert.match(desktopJs, /consumeRedirectedAccessToken/);
|
||||||
|
assert.match(desktopJs, /URLSearchParams/);
|
||||||
|
assert.match(desktopJs, /history\.replaceState/);
|
||||||
|
});
|
||||||
|
|
||||||
test("mobile page has the same password gate for cloud use", () => {
|
test("mobile page has the same password gate for cloud use", () => {
|
||||||
assert.match(mobileHtml, /id="auth-gate"/);
|
assert.match(mobileHtml, /id="auth-gate"/);
|
||||||
assert.match(mobileHtml, /action="\/auth\/login"/);
|
assert.match(mobileHtml, /action="\/auth\/login"/);
|
||||||
@ -61,6 +69,12 @@ test("mobile login form is not blocked by JavaScript", () => {
|
|||||||
assert.doesNotMatch(mobileJs, /async function submitAccessPassword/);
|
assert.doesNotMatch(mobileJs, /async function submitAccessPassword/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("mobile can finish login from a redirected access token", () => {
|
||||||
|
assert.match(mobileJs, /consumeRedirectedAccessToken/);
|
||||||
|
assert.match(mobileJs, /URLSearchParams/);
|
||||||
|
assert.match(mobileJs, /history\.replaceState/);
|
||||||
|
});
|
||||||
|
|
||||||
test("ranking radar requests respect the shared cloud login token", () => {
|
test("ranking radar requests respect the shared cloud login token", () => {
|
||||||
assert.match(rankingsJs, /HOTNESS_AUTH_TOKEN_KEY/);
|
assert.match(rankingsJs, /HOTNESS_AUTH_TOKEN_KEY/);
|
||||||
assert.match(rankingsJs, /authHeaders/);
|
assert.match(rankingsJs, /authHeaders/);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user