crates/nornir-identity-web/assets/login-helpers.js
// nornir-identity-web login-helpers.js (A34 M-7): ONE copy of the login page
// helpers for every console (Gunnar's and Holger's). A console serves this file
// (or splices it into its page) before its own login script; the page keeps
// only its own words and wiring. The page provides `show(text, cls)`, and may
// define `onInviteDone()` to forget its own invite state.
// What a person is told when a passkey cannot be used and another may be.
const ORPHAN_HINT = 'Sign in with your other passkey, or ask for a new invite.';
// What a spent invite says, when the server gave no sentence of its own.
const INVITE_SPENT = 'This invite has already been used. If you registered, sign in.';
// Where a login may send the person: ONE rule, the same everywhere (and in Rust,
// `nornir_identity_web::login_js::safe_local_path`). A single leading slash, no
// authority, and no character the URL parser strips before it decides what the
// value even is: `//host` and `/\host` are off-site in every engine, and a value
// carrying a tab, a newline or a NUL is one whose meaning is settled after those
// are removed, so this page would not be looking at the string the browser
// follows. Anything else answers ''. The escaped character class on purpose: a
// literal control byte would make this file binary to grep and diff.
function safeLocalPath(raw) {
if (typeof raw !== 'string' || raw === '') return '';
if (!raw.startsWith('/') || raw.startsWith('//') || raw.startsWith('/\\')) return '';
if (/[\u0000-\u0020\u007F]/.test(raw)) return '';
return raw;
}
// Where to land after logging in: the page's `?next=` through `safeLocalPath`,
// else `home` (a console names its own; '/' when none is given).
function nextDestination(home) {
const fallback = (typeof home === 'string' && safeLocalPath(home)) || '/';
try {
return safeLocalPath(new URLSearchParams(location.search).get('next')) || fallback;
} catch (e) { return fallback; }
}
// The invite is done with (registered, or the server says it is spent): the
// register panel goes, "Log in" is the way on, and the page forgets its invite.
function inviteDone(text, cls) {
if (typeof onInviteDone === 'function') onInviteDone();
const reg = document.getElementById('reg');
if (reg) { reg.open = false; reg.hidden = true; }
const login = document.getElementById('login');
if (login) { login.hidden = false; login.classList.add('primary'); }
show(text, cls);
}