From baa2d4b5210b7b3c61c7fdb01fc80556b61289ce Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Nov 2026 21:05:40 -0500 Subject: [PATCH] Send the admin login straight to Dex A public custom script submits the OIDC form on /admin/login and CSS hides the password form. /admin/login?local=1 (or a failed password attempt) shows it for break-glass use; a 15s guard prevents loops. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- files/public.css | 3 +++ files/public.js | 20 ++++++++++++++++++++ templates/configure.sql.j2 | 1 + 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 files/public.js diff --git a/README.md b/README.md index 4d87d9e..28dee93 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Deploys [listmonk](https://listmonk.app) v6.2.0, the eom.dev newsletter, to Kube It fits in with the rest of eom.dev: -- **Login** goes through Dex (`listmonk` static client) with the same "eom.dev account" button as the other services. Only e-mail addresses listed in `listmonk_oidc_admins` can sign in, so being a member is not enough to get into the admin UI. A password login for the `admin` user is kept as a fallback. +- **Login** goes through Dex (`listmonk` static client) with the same "eom.dev account" button as the other services. Only e-mail addresses listed in `listmonk_oidc_admins` can sign in, so being a member is not enough to get into the admin UI. The admin login page sends you straight to Dex. For the `admin` password login, kept as a fallback for when Dex is down, use `/admin/login?local=1`. - **Mail** goes out through `postfix.eom.dev:587` as the LDAP account `news`, the same way Gitea, Nextcloud and PeerTube send mail. - **Look**: the public pages (subscribe, archive, preferences, unsubscribe) use the Catppuccin Mocha/Latte palette from `dex.eom.dev`, along with the eom.dev logo and favicon. - **Cluster conventions**: `ca-issuer` TLS, `nfs-client` volumes, and anti-affinity that keeps the pods off the ingress-nginx node. diff --git a/files/public.css b/files/public.css index b5838dc..7b56094 100644 --- a/files/public.css +++ b/files/public.css @@ -71,3 +71,6 @@ input::placeholder { color: var(--muted); } .button.button-outline:hover { background: var(--surface); color: var(--accent-hover); } .error { color: var(--error); } .pg-page.pg-selected { background: var(--accent); color: var(--on-accent); } +/* Admin login is Dex only; ?local=1 (public.js) reveals the password form. */ +.login form[action="/admin/login"], .login .center.small { display: none; } +.eom-local-login .login form[action="/admin/login"], .eom-local-login .login .center.small { display: block; } diff --git a/files/public.js b/files/public.js new file mode 100644 index 0000000..cbc6c8b --- /dev/null +++ b/files/public.js @@ -0,0 +1,20 @@ +// eom.dev: the admin login goes straight to Dex. +// Break-glass password login: /admin/login?local=1 +(function () { + if (window.location.pathname !== "/admin/login") return; + var local = new URLSearchParams(window.location.search).has("local"); + var go = function () { + var root = document.documentElement; + // A failed password attempt re-renders /admin/login without ?local=1. + if (local || document.querySelector(".login .error")) { root.classList.add("eom-local-login"); return; } + var form = document.querySelector('form[action="/auth/oidc"]'); + if (!form) return; + // Don't loop if Dex sent us straight back (e.g. login refused). + var key = "eom-oidc-redirect", now = Date.now(), last = 0; + try { last = Number(sessionStorage.getItem(key)) || 0; sessionStorage.setItem(key, String(now)); } catch (e) {} + if (now - last < 15000) return; + form.submit(); + }; + if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", go); + else go(); +})(); diff --git a/templates/configure.sql.j2 b/templates/configure.sql.j2 index 0bc1cfc..2664b6d 100644 --- a/templates/configure.sql.j2 +++ b/templates/configure.sql.j2 @@ -52,6 +52,7 @@ }], 'appearance.public.custom_css': lookup('ansible.builtin.file', role_path ~ '/files/public.css'), 'appearance.admin.custom_css': lookup('ansible.builtin.file', role_path ~ '/files/admin.css'), + 'appearance.public.custom_js': lookup('ansible.builtin.file', role_path ~ '/files/public.js'), } | combine(listmonk_settings_extra) -%} \set ON_ERROR_STOP on BEGIN;