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:
1 parent
13693530a9
commit
18638c68f5
1 file changed
+49
-1
+49
-1
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user