From 138193ce13bd882f215309e4e3caf019099868d3 Mon Sep 17 00:00:00 2001 From: Zaine Arch Date: Sat, 9 May 2026 00:36:21 +0100 Subject: [PATCH] hehe --- assets/scripts/api-auth.js | 48 ---------- assets/scripts/auth.js | 102 ++++++++++++++++++++++ assets/scripts/comments.js | 2 +- assets/scripts/competency-status-board.js | 2 + assets/scripts/wird-tracker.js | 2 + assets/styles/misc.css | 26 ++++++ build-site.el | 2 +- home/guide/setup.org | 2 +- home/login.org | 21 +++++ posts/posts-list.org | 2 +- recently-updated.org | 4 +- sitemap.org | 5 +- 12 files changed, 162 insertions(+), 56 deletions(-) delete mode 100644 assets/scripts/api-auth.js create mode 100644 assets/scripts/auth.js create mode 100644 home/login.org diff --git a/assets/scripts/api-auth.js b/assets/scripts/api-auth.js deleted file mode 100644 index 6c87ef5..0000000 --- a/assets/scripts/api-auth.js +++ /dev/null @@ -1,48 +0,0 @@ -(function () { - "use strict"; - - const STORAGE_KEY = "orgBackendApiKey"; - const HEADER = "X-Org-Api-Key"; - - function getStoredKey() { - return window.localStorage.getItem(STORAGE_KEY) || ""; - } - - function requestKey() { - const key = window.prompt("API key"); - if (key && key.trim()) { - window.localStorage.setItem(STORAGE_KEY, key.trim()); - return key.trim(); - } - return ""; - } - - function authHeaders(existingHeaders = {}, options = {}) { - const headers = new Headers(existingHeaders); - let key = getStoredKey(); - if (!key && options.prompt !== false) { - key = requestKey(); - } - if (key) headers.set(HEADER, key); - return headers; - } - - async function authFetch(input, options = {}) { - const headers = authHeaders(options.headers, options); - const response = await fetch(input, { ...options, headers }); - if (response.status !== 401) return response; - - window.localStorage.removeItem(STORAGE_KEY); - if (options.retry === false) return response; - - const retryHeaders = authHeaders(options.headers, options); - if (!retryHeaders.has(HEADER)) return response; - return fetch(input, { ...options, headers: retryHeaders, retry: false }); - } - - window.orgAuth = { - fetch: authFetch, - headers: authHeaders, - clear: () => window.localStorage.removeItem(STORAGE_KEY), - }; -})(); diff --git a/assets/scripts/auth.js b/assets/scripts/auth.js new file mode 100644 index 0000000..5300186 --- /dev/null +++ b/assets/scripts/auth.js @@ -0,0 +1,102 @@ +(function () { + "use strict"; + + const TOKEN_KEY = "orgWebJwt"; + const LOGIN_PATH = "/home/login.html"; + + function token() { + return window.localStorage.getItem(TOKEN_KEY) || ""; + } + + function isLoginPage() { + return window.location.pathname === LOGIN_PATH; + } + + function loginUrl() { + const next = window.location.pathname + window.location.search + window.location.hash; + return `${LOGIN_PATH}?next=${encodeURIComponent(next)}`; + } + + function redirectToLogin() { + if (!isLoginPage()) { + window.location.assign(loginUrl()); + } + } + + function authHeaders(existingHeaders = {}) { + const headers = new Headers(existingHeaders); + const jwt = token(); + if (jwt) headers.set("Authorization", `Bearer ${jwt}`); + return headers; + } + + async function authFetch(input, options = {}) { + const response = await fetch(input, { + ...options, + headers: authHeaders(options.headers), + }); + + if (response.status === 401) { + window.localStorage.removeItem(TOKEN_KEY); + redirectToLogin(); + } + + return response; + } + + async function login(username, password) { + const response = await fetch("/api/auth/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username, password }), + }); + + const data = await response.json().catch(() => ({})); + if (!response.ok) { + throw new Error(data.error || "Login failed"); + } + + window.localStorage.setItem(TOKEN_KEY, data.token); + return data.token; + } + + function initLoginForm() { + const form = document.getElementById("login-form"); + if (!form) return; + + const message = document.getElementById("login-message"); + form.addEventListener("submit", async (event) => { + event.preventDefault(); + const submit = form.querySelector("button[type='submit']"); + if (submit) submit.disabled = true; + if (message) message.textContent = ""; + + try { + await login(form.username.value.trim(), form.password.value); + const params = new URLSearchParams(window.location.search); + window.location.assign(params.get("next") || "/"); + } catch (error) { + if (message) message.textContent = error.message; + } finally { + if (submit) submit.disabled = false; + } + }); + } + + window.orgAuth = { + fetch: authFetch, + headers: authHeaders, + token, + logout: () => { + window.localStorage.removeItem(TOKEN_KEY); + redirectToLogin(); + }, + }; + + document.addEventListener("DOMContentLoaded", () => { + initLoginForm(); + if (!isLoginPage() && !token()) { + redirectToLogin(); + } + }); +})(); diff --git a/assets/scripts/comments.js b/assets/scripts/comments.js index c6ef45e..7dcd221 100755 --- a/assets/scripts/comments.js +++ b/assets/scripts/comments.js @@ -121,7 +121,7 @@ function renderComments(comments) { * ----------------------------- */ async function fetchComments() { - const res = await fetch(`/api/comments/${pageSlug}`); + const res = await window.orgAuth.fetch(`/api/comments/${pageSlug}`); if (!res.ok) { console.error("Failed to fetch comments"); return []; diff --git a/assets/scripts/competency-status-board.js b/assets/scripts/competency-status-board.js index cec3183..bd11f82 100755 --- a/assets/scripts/competency-status-board.js +++ b/assets/scripts/competency-status-board.js @@ -228,6 +228,8 @@ function renderBoard(items) { } async function loadBoard() { + if (!document.getElementById("kanban-board")) return; + const res = await window.orgAuth.fetch(`/api/competencies/items?group=${currentLevel}`); const items = await res.json(); diff --git a/assets/scripts/wird-tracker.js b/assets/scripts/wird-tracker.js index b2deb73..8f4123e 100755 --- a/assets/scripts/wird-tracker.js +++ b/assets/scripts/wird-tracker.js @@ -328,6 +328,8 @@ // ── Boot ───────────────────────────────────────────────── document.addEventListener("DOMContentLoaded", async () => { + if (!document.getElementById("wird-app")) return; + setTodayLabel(); await Promise.all([loadAll(), loadMotalah()]); renderToday(); diff --git a/assets/styles/misc.css b/assets/styles/misc.css index 4d1e050..e4e5ae0 100644 --- a/assets/styles/misc.css +++ b/assets/styles/misc.css @@ -427,3 +427,29 @@ h2[id], h3[id], h4[id] { scroll-margin-top: 6.5rem; } background-color: var(--active-toc); /* border: 1px solid var(--border, #ccc); */ } +.auth-page { + min-height: 70vh; + display: grid; + place-items: center; + padding: 3rem 1rem; +} + +.auth-form { + width: min(100%, 360px); + display: grid; + gap: 0.8rem; +} + +.auth-form h1 { + margin: 0 0 0.5rem; +} + +.auth-form input { + width: 100%; + box-sizing: border-box; +} + +.auth-message { + min-height: 1.4em; + color: #a33; +} diff --git a/build-site.el b/build-site.el index 91f2a19..2ed577b 100755 --- a/build-site.el +++ b/build-site.el @@ -182,7 +182,7 @@ FMT / ARGS are passed to `format'." (format "" f)) '("script.js" "lunr.js" - "api-auth.js" + "auth.js" "competency-status-board.js" "notes.js" "comments.js" diff --git a/home/guide/setup.org b/home/guide/setup.org index 3653323..87344fa 100755 --- a/home/guide/setup.org +++ b/home/guide/setup.org @@ -74,7 +74,7 @@ The script is as follows, at the start I have some metadata relating to the file - + diff --git a/home/login.org b/home/login.org new file mode 100644 index 0000000..7833bd3 --- /dev/null +++ b/home/login.org @@ -0,0 +1,21 @@ +#+TITLE: Login +#+OPTIONS: toc:nil num:nil +#+NO_SIDENOTES: t +#+COMMENTS: nil +#+SLUG: login + +#+BEGIN_EXPORT html +
+
+

Login

+ + + + + + + + +
+
+#+END_EXPORT diff --git a/posts/posts-list.org b/posts/posts-list.org index 1ddbc63..c72bb91 100644 --- a/posts/posts-list.org +++ b/posts/posts-list.org @@ -4,7 +4,7 @@ See the categories: @@html:Categories@@ * Posts: -- [[file:career/career-list.org][Career List]] @@html:09-05-2026 00:25@@ +- [[file:career/career-list.org][Career List]] @@html:09-05-2026 00:35@@ - [[file:posts-list.sync-conflict-20260417-233432-VT6366A.org][Posts List]] @@html:14-04-2026 16:36@@ - [[file:career/ha-dr.org][High Availability, Disaster Recovery and Business Continuity]] @@html:11-03-2026 17:18@@ @@html:@@ @@html:@@ - [[file:career/javascript.org][Understands the Javascript language]] @@html:11-03-2026 16:52@@ @@html:@@ @@html:@@ diff --git a/recently-updated.org b/recently-updated.org index a6b6aa7..d7e0fb6 100644 --- a/recently-updated.org +++ b/recently-updated.org @@ -2,7 +2,8 @@ #+OPTIONS: toc:nil num:nil * Recently Updated (top 26 files) -- [[file:home/guide/setup.org][Setup]] @@html:2026-05-09 00:20@@ +- [[file:home/login.org][Login]] @@html:2026-05-09 00:33@@ +- [[file:home/guide/setup.org][Setup]] @@html:2026-05-09 00:33@@ - [[file:blogs/2026/05-may/ai-datacamp-08-05.org][AI Datacamp]] @@html:2026-05-08 12:49@@ - [[file:blogs/2026/05-may/using-codex-07-05-26.org][Using Codex]] @@html:2026-05-07 16:12@@ - [[file:blogs/2026/05-may/03-05-week-review.org][[03-05-2026] - Weekly Review]] @@html:2026-05-03 12:00@@ @@ -27,4 +28,3 @@ - [[file:blogs/2026/04-april/joining-new-team-meeting-02-04-2026.org][New team meeting]] @@html:2026-04-02 16:03@@ - [[file:blogs/2026/03-march/intellectually-challenging-myself-30-03-26.org][Intellectually challenging oneself]] @@html:2026-03-31 11:14@@ - [[file:blogs/2026/03-march/cooking-dinner-29-03-26.org][Cooking Dinner (num)]] @@html:2026-03-30 11:49@@ -- [[file:blogs/2026/03-march/29-03-week-review.org][[29-03-2026] - Weekly Review]] @@html:2026-03-29 12:00@@ diff --git a/sitemap.org b/sitemap.org index cd0e1b4..a84c6ca 100644 --- a/sitemap.org +++ b/sitemap.org @@ -11,12 +11,12 @@ - [[file:tags/notes.org][Tag: notes]] - [[file:tags/review.org][Tag: review]] - [[file:tags/website.org][Tag: website]] - - [[file:tags/life.org][Tag: life]] - [[file:tags/update.org][Tag: update]] - [[file:tags/insights.org][Tag: insights]] - - [[file:tags/emacs.org][Tag: emacs]] + - [[file:tags/life.org][Tag: life]] - [[file:tags/education.org][Tag: education]] - [[file:tags/reading.org][Tag: reading]] + - [[file:tags/emacs.org][Tag: emacs]] - [[file:tags/maths.org][Tag: maths]] - posts - [[file:posts/posts-intro.org][Posts Introduction]] @@ -60,6 +60,7 @@ - [[file:home/services.org][Service]] - [[file:home/status.org][Competency Status Board]] - [[file:home/wird-tracker.org][Wird Tracker]] + - [[file:home/login.org][Login]] - [[file:home/categories.org][Categories]] - guide - [[file:home/guide/wird-tracker-guide.org][Wird Tracker — Technical Guide]]