Implement secure storage and TOTP authentication for BluditSecure

This commit is contained in:
interlope committed 2026-11-02 20:18:08 +01:00
commit 49b4308028
18 files changed
+1127

No files matched your search

+1
View File
@@ -0,0 +1 @@
:root{color-scheme:light dark;font:16px/1.5 system-ui,sans-serif;background:#111827;color:#f3f4f6}body{margin:0;padding:2rem 1rem}main{max-width:640px;margin:auto;background:#1f2937;border:1px solid #475569;border-radius:16px;padding:2rem}h1{font-size:1.7rem}h2{font-size:1.2rem;margin-top:2rem;border-top:1px solid #475569;padding-top:1rem}label{display:block;margin:.8rem 0}input:not([type=checkbox]),select{box-sizing:border-box;display:block;width:100%;padding:.65rem;background:#111827;color:#fff;border:1px solid #64748b;border-radius:6px}button{cursor:pointer;background:#93c5fd;color:#111827;border:0;border-radius:6px;padding:.65rem 1rem;margin:.4rem 0}button:disabled{opacity:.5}a{color:#93c5fd}code,pre{overflow-wrap:anywhere;white-space:pre-wrap}.secret{display:block;padding:1rem;border:1px dashed #94a3b8}*:focus-visible{outline:3px solid #fbbf24;outline-offset:3px}#status,[role=alert]{color:#fcd34d}
+43
View File
@@ -0,0 +1,43 @@
'use strict';
(() => {
const decode = value => Uint8Array.from(atob(value.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0));
const encode = value => btoa(String.fromCharCode(...new Uint8Array(value))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const status = document.getElementById('status');
for (const button of document.querySelectorAll('[data-key]')) {
button.addEventListener('click', async () => {
const box = button.closest('.webauthn');
const create = button.dataset.key === 'create';
const kind = create ? 'create' : 'get';
const request = async (phase, fields = {}) => {
const url = new URL(box.dataset.base);
url.searchParams.set('bs', `key-${kind}-${phase}`);
const response = await fetch(url, {method: 'POST', credentials: 'same-origin', body: new URLSearchParams({secureCsrf: box.dataset.csrf, ...fields})});
const result = await response.json();
if (!response.ok || result.error) throw new Error(result.error || 'Vérification impossible.');
return result;
};
button.disabled = true;
try {
if (!window.PublicKeyCredential || !navigator.credentials) throw new Error('Ce navigateur ne prend pas en charge les passkeys.');
const args = await request('options');
args.publicKey.challenge = decode(args.publicKey.challenge);
if (args.publicKey.user) args.publicKey.user.id = decode(args.publicKey.user.id);
for (const field of ['allowCredentials', 'excludeCredentials']) {
for (const cred of args.publicKey[field] || []) cred.id = decode(cred.id);
}
const credential = await navigator.credentials[kind](args);
const fields = {id: encode(credential.rawId), clientDataJSON: encode(credential.response.clientDataJSON)};
if (create) {
fields.attestationObject = encode(credential.response.attestationObject);
fields.label = box.querySelector('.key-label').value;
} else {
fields.authenticatorData = encode(credential.response.authenticatorData);
fields.signature = encode(credential.response.signature);
}
const result = await request('finish', fields);
window.location.assign(result.redirect);
} catch (error) { status.textContent = error.name === 'NotAllowedError' ? 'Vérification annulée ou délai dépassé.' : error.message; }
finally { button.disabled = false; }
});
}
})();