FAQ: login con Basic Auth para el webhook

Como el webhook ahora pide Basic Auth, la página muestra un formulario de
acceso y manda Authorization: Basic en cada request (guarda en sessionStorage,
no embebe credenciales en el HTML). En 401 vuelve a pedir el login.
This commit is contained in:
webmanager committed 2026-11-07 17:37:04 +00:00
1 parent 13693530a9
commit 18638c68f5
1 file changed
+49 -1
+49 -1
View File
@@ -36,6 +36,12 @@
.msg{padding:12px 16px;border-radius:12px;margin-bottom:18px;font-size:.9rem;display:none}
.msg.show{display:block}
.msg.err{background:#fdf1ef;color:#8f2117;border:1px solid #f0c9c4}
.auth{background:#fff;border:1px solid var(--line);border-radius:14px;padding:18px 20px;display:flex;gap:12px;flex-wrap:wrap;align-items:flex-end}
.auth h2{font-family:Sora,sans-serif;font-size:.95rem;margin:0 0 12px;width:100%;color:var(--muted)}
.auth input{flex:1 1 160px;padding:11px 13px;border:1px solid var(--line);border-radius:10px;font:inherit;background:#fff}
.auth input:focus{outline:2px solid rgba(108,76,224,.22);border-color:var(--violet-soft)}
.auth button{font:inherit;font-weight:600;border-radius:10px;border:1px solid transparent;padding:11px 18px;cursor:pointer;background:var(--violet);color:#fff}
.auth .err{width:100%;margin:4px 0 0;font-size:.85rem;color:var(--py-red);font-weight:600;min-height:1em}
.area{margin:0 0 26px}
.area h2{font-family:Sora,sans-serif;font-size:.8rem;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin:0 0 10px}
.card{background:#fff;border:1px solid var(--line);border-radius:14px;margin-bottom:10px;overflow:hidden;transition:border-color .18s,box-shadow .18s}
@@ -67,6 +73,14 @@
<main class="wrap">
<div class="msg" id="msg"></div>
<form class="auth" id="auth" hidden>
<h2 data-i18n="loginTitle">Acceso</h2>
<input id="au" data-i18n-placeholder="user" placeholder="Usuario" autocomplete="username" autocapitalize="none" />
<input id="ap" type="password" data-i18n-placeholder="pass" placeholder="Contraseña" autocomplete="current-password" />
<button type="submit" data-i18n="enter">Entrar</button>
<p class="err" id="auth-err"></p>
</form>
<div class="bar">
<input type="search" id="q" data-i18n-placeholder="search" placeholder="Buscar…" aria-label="Buscar" />
</div>
@@ -87,6 +101,8 @@ const I18N = {
group: 'General', errRead: 'No se pudo leer el webhook',
errData: 'El webhook respondió sin datos',
hint: 'En n8n, poné el nodo Webhook en "Respond: When Last Node Finishes".',
loginTitle: 'Acceso restringido', user: 'Usuario', pass: 'Contraseña', enter: 'Entrar',
badCred: 'Usuario o contraseña incorrectos.',
},
en: {
title: 'Frequently asked questions', subtitle: 'OCA Paraguay · internal use', badge: 'not indexed',
@@ -95,6 +111,8 @@ const I18N = {
group: 'General', errRead: 'Could not read the webhook',
errData: 'The webhook returned no data',
hint: 'In n8n, set the Webhook node to "Respond: When Last Node Finishes".',
loginTitle: 'Restricted access', user: 'User', pass: 'Password', enter: 'Sign in',
badCred: 'Wrong user or password.',
}
};
@@ -209,12 +227,32 @@ function render() {
});
}
const AUTH_KEY = 'ocaFaqAuth';
function sGet(k) { try { return sessionStorage.getItem(k); } catch (e) { return null; } }
function sSet(k, v) { try { sessionStorage.setItem(k, v); } catch (e) {} }
function sDel(k) { try { sessionStorage.removeItem(k); } catch (e) {} }
function showAuth(on, bad) {
el('auth').hidden = !on;
document.querySelector('.bar').hidden = on;
el('list').hidden = on;
el('auth-err').textContent = (on && bad) ? t('badCred') : '';
el('count').textContent = '';
if (on) setTimeout(() => el('au').focus(), 60);
}
async function load() {
const cred = sGet(AUTH_KEY);
if (!cred) { showAuth(true); return; }
try {
const res = await fetch(API, { headers: { Accept: 'application/json' } });
const res = await fetch(API, {
headers: { Accept: 'application/json', Authorization: 'Basic ' + cred },
});
if (res.status === 401) { sDel(AUTH_KEY); showAuth(true, true); return; }
if (!res.ok) throw new Error('HTTP ' + res.status);
const payload = await res.json();
state.items = normalize(payload);
showAuth(false);
hideMsg();
render();
if (!state.items.length && !Array.isArray(payload)) {
@@ -246,6 +284,16 @@ el('list').addEventListener('toggle', (ev) => {
el('q').addEventListener('input', render);
el('auth').addEventListener('submit', (ev) => {
ev.preventDefault();
const u = el('au').value.trim();
const p = el('ap').value;
if (!u) { el('au').focus(); return; }
try { sSet(AUTH_KEY, btoa(u + ':' + p)); } catch (e) { sSet(AUTH_KEY, ''); }
el('ap').value = '';
load();
});
applyLang();
load();
</script>