SSO and turn gate
This commit is contained in:
+32
-55
@@ -1,6 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
const TOKEN_KEY = "thebutton-token";
|
||||
const $ = (id) => document.getElementById(id);
|
||||
|
||||
const callbackParams = new URLSearchParams(window.location.hash.slice(1));
|
||||
@@ -11,10 +10,10 @@ if (callbackError) {
|
||||
|
||||
let game = null; // latest /api/state payload
|
||||
let clockOffset = 0; // serverTime - clientTime
|
||||
let token = localStorage.getItem(TOKEN_KEY);
|
||||
let oidcMode = false;
|
||||
let oidcLoginUrl = "";
|
||||
let oidcRegisterUrl = "";
|
||||
let token = false;
|
||||
let oidcLoginUrl = "/auth/oidc/login";
|
||||
let oidcRegisterUrl = "https://lists.eom.dev/accounts/register/";
|
||||
localStorage.removeItem("thebutton-token");
|
||||
let statsLoaded = false;
|
||||
let subscribed = false;
|
||||
let currentShareText = "";
|
||||
@@ -198,7 +197,6 @@ function syncPresence() {
|
||||
if (token && sharePresence) $("presence-error").textContent = "Couldn't update online status. Retrying automatically.";
|
||||
if (error.status === 401) {
|
||||
token = null;
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
lastPresenceUpdate = null;
|
||||
updateButton();
|
||||
}
|
||||
@@ -218,13 +216,10 @@ window.addEventListener("storage", (event) => {
|
||||
alertObservation = null;
|
||||
renderAlertSettings();
|
||||
}
|
||||
if (event.key === PRESENCE_KEY || event.key === TOKEN_KEY) {
|
||||
if (event.key === PRESENCE_KEY) {
|
||||
sharePresence = localStorage.getItem(PRESENCE_KEY) !== "false";
|
||||
if (event.key === TOKEN_KEY) isAdmin = false;
|
||||
token = localStorage.getItem(TOKEN_KEY);
|
||||
updateButton();
|
||||
syncPresence();
|
||||
if (event.key === TOKEN_KEY && token) loadMe();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -247,19 +242,16 @@ function renderSections() {
|
||||
const id = button.dataset.section;
|
||||
button.hidden = !available[id];
|
||||
button.setAttribute("aria-expanded", String(selectedSection === id));
|
||||
if (id === "account-section") button.textContent = token ? "Your score" : oidcMode ? "Sign in" : "Log in / Join";
|
||||
if (id === "account-section") button.textContent = token ? "Your score" : "Sign in";
|
||||
});
|
||||
document.querySelectorAll("[data-panel]").forEach((panel) => {
|
||||
panel.hidden = panel.id !== selectedSection;
|
||||
});
|
||||
$("mailing-list").hidden = !token || subscribed;
|
||||
$("register-section").hidden = !!token || oidcMode;
|
||||
$("oidc-login-section").hidden = !!token || !oidcMode;
|
||||
if (oidcMode) {
|
||||
$("oidc-login-link").href = oidcLoginUrl;
|
||||
$("oidc-register-link").href = oidcRegisterUrl || "#";
|
||||
$("oidc-register-link").hidden = !oidcRegisterUrl;
|
||||
}
|
||||
$("oidc-login-section").hidden = !!token;
|
||||
$("oidc-login-link").href = oidcLoginUrl;
|
||||
$("oidc-register-link").href = oidcRegisterUrl || "#";
|
||||
$("oidc-register-link").hidden = !oidcRegisterUrl;
|
||||
$("chat-form").hidden = !token;
|
||||
$("chat-login").hidden = !!token;
|
||||
$("section-hint").hidden = selectedSection !== null;
|
||||
@@ -282,7 +274,6 @@ $("section-nav").addEventListener("click", (event) => {
|
||||
|
||||
async function api(method, path, body) {
|
||||
const headers = { "Content-Type": "application/json" };
|
||||
if (token) headers.Authorization = `Bearer ${token}`;
|
||||
const res = await fetch(path, { method, headers, body: body ? JSON.stringify(body) : undefined });
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) throw Object.assign(new Error(data.error || res.statusText), { status: res.status });
|
||||
@@ -449,7 +440,18 @@ async function refreshState() {
|
||||
}
|
||||
|
||||
function updateButton() {
|
||||
$("the-button").disabled = !token || !game || game.ended;
|
||||
const eligibility = game?.press_eligibility;
|
||||
$("the-button").disabled = !token || !game || game.ended || eligibility?.eligible !== true;
|
||||
$("press-eligibility").hidden = !token;
|
||||
if (token) {
|
||||
$("press-eligibility").textContent = !eligibility
|
||||
? "Checking when you can press..."
|
||||
: !eligibility.has_pressed
|
||||
? "Your first press this season is ready."
|
||||
: eligibility.eligible
|
||||
? "Your turn is ready: two different players have pressed since your last turn."
|
||||
: `Waiting for ${eligibility.other_players_remaining} more different ${eligibility.other_players_remaining === 1 ? "player" : "players"} to press.`;
|
||||
}
|
||||
$("me-section").hidden = !token;
|
||||
$("my-subscribed").hidden = !token || !subscribed;
|
||||
renderSections();
|
||||
@@ -512,11 +514,11 @@ function renderAchievements(achievements) {
|
||||
}
|
||||
|
||||
async function loadMe() {
|
||||
if (!token) return;
|
||||
const requestedToken = token;
|
||||
try {
|
||||
const me = await api("GET", "/api/me");
|
||||
if (token !== requestedToken) return;
|
||||
if (token !== requestedToken && token) return;
|
||||
token = true;
|
||||
isAdmin = me.is_admin === true;
|
||||
$("my-name").textContent = me.name;
|
||||
$("my-points").textContent = me.total_points.toLocaleString();
|
||||
@@ -549,6 +551,7 @@ async function loadMe() {
|
||||
}
|
||||
subscribed = me.subscribed;
|
||||
updateButton();
|
||||
syncPresence();
|
||||
fillTable($("my-zones"), ["Zone", "Points each", "Your presses"], zoneRows(me.zone_counts));
|
||||
fillTable(
|
||||
$("my-history"),
|
||||
@@ -561,7 +564,6 @@ async function loadMe() {
|
||||
if (e.status === 401) {
|
||||
token = null;
|
||||
isAdmin = false;
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
updateButton();
|
||||
}
|
||||
}
|
||||
@@ -879,7 +881,6 @@ $("chat-form").addEventListener("submit", async (event) => {
|
||||
$("chat-error").textContent = error.message;
|
||||
if (error.status === 401) {
|
||||
token = null;
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
updateButton();
|
||||
}
|
||||
} finally {
|
||||
@@ -934,6 +935,7 @@ $("the-button").addEventListener("click", async () => {
|
||||
try {
|
||||
const r = await api("POST", "/api/press");
|
||||
game.deadline = r.deadline;
|
||||
game.press_eligibility = r.press_eligibility;
|
||||
currentShareText = `I pushed #TheButton at ${formatPrecise(r.press.remaining_ms)}.`;
|
||||
$("press-result").textContent =
|
||||
`You pressed in the ${r.press.zone} zone with ${formatPrecise(r.press.remaining_ms)} remaining: +${r.press.points} points`;
|
||||
@@ -954,15 +956,10 @@ $("mastodon-instance").addEventListener("input", updateShareLinks);
|
||||
async function loadAuthConfig() {
|
||||
try {
|
||||
const response = await fetch("/api/config", { cache: "no-store" });
|
||||
if (!response.ok) return;
|
||||
if (!response.ok) throw new Error("Identity configuration is unavailable");
|
||||
const config = await response.json();
|
||||
oidcMode = config.auth_mode === "oidc";
|
||||
if (oidcMode) {
|
||||
token = null;
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
}
|
||||
oidcLoginUrl = config.login_url || "";
|
||||
oidcRegisterUrl = config.register_url || "";
|
||||
oidcLoginUrl = config.login_url || "/auth/oidc/login";
|
||||
oidcRegisterUrl = config.register_url || "https://lists.eom.dev/accounts/register/";
|
||||
renderSections();
|
||||
if (callbackError) {
|
||||
const errorMessage = callbackError === "access_denied"
|
||||
@@ -973,36 +970,16 @@ async function loadAuthConfig() {
|
||||
$("oidc-error").textContent = errorMessage;
|
||||
}
|
||||
} catch {
|
||||
// Keep the local login UI available if the server has no OIDC config endpoint.
|
||||
// Never fall back to local credentials when Dex is unavailable.
|
||||
renderSections();
|
||||
}
|
||||
}
|
||||
|
||||
$("register-form").addEventListener("submit", async (ev) => {
|
||||
ev.preventDefault();
|
||||
$("register-error").textContent = "";
|
||||
const action = ev.submitter?.value === "register" ? "register" : "login";
|
||||
try {
|
||||
const r = await api("POST", `/api/${action}`, {
|
||||
name: $("name-input").value,
|
||||
password: $("password-input").value,
|
||||
});
|
||||
token = r.token;
|
||||
localStorage.setItem(TOKEN_KEY, token);
|
||||
syncPresence();
|
||||
$("password-input").value = "";
|
||||
updateButton();
|
||||
await loadMe();
|
||||
await loadLeaderboard();
|
||||
} catch (e) {
|
||||
$("register-error").textContent = e.message;
|
||||
}
|
||||
});
|
||||
|
||||
$("logout-button").addEventListener("click", async () => {
|
||||
await api("POST", "/api/logout").catch(() => {});
|
||||
token = null;
|
||||
subscribed = false;
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
isAdmin = false;
|
||||
$("me-section").hidden = true;
|
||||
$("press-result").textContent = "";
|
||||
$("subscribe-result").textContent = "";
|
||||
|
||||
Reference in New Issue
Block a user