esifawafwu

This commit is contained in:
2026-09-28 19:03:31 +02:00
parent 5eb33f97c8
commit dde0a2d271
62 changed files with 3760 additions and 1865 deletions
+77 -11
View File
@@ -369,6 +369,55 @@
return finishData;
}
// ── Add a passkey to the current anonymous identity ───────────────────
async addPasskey() {
if (!this.supported) throw new Error('WebAuthn / Passkeys are not supported in this browser.');
const csrfToken = (window.f0ckSession && window.f0ckSession.csrf_token)
|| document.querySelector('meta[name="csrf-token"]')?.content || '';
const beginRes = await fetch('/api/v2/anon/passkey/add/begin', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrfToken },
body: JSON.stringify({})
});
const beginData = await beginRes.json();
if (!beginData.success) throw new Error(beginData.msg || 'Server error');
const rawChallenge = beginData.options.challenge;
const opts = beginData.options;
const cred = await navigator.credentials.create({ publicKey: {
rp: opts.rp,
user: {
id: b64urlToArr(opts.user.id),
name: opts.user.name,
displayName: opts.user.displayName
},
challenge: b64urlToArr(rawChallenge),
pubKeyCredParams: opts.pubKeyCredParams,
timeout: opts.timeout || 60000,
excludeCredentials: (opts.excludeCredentials || []).map(c => ({ type: c.type, id: b64urlToArr(c.id) })),
authenticatorSelection: opts.authenticatorSelection,
attestation: opts.attestation || 'none'
}});
const finishRes = await fetch('/api/v2/anon/passkey/add/finish', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrfToken },
body: JSON.stringify({
challenge: rawChallenge,
credentialId: arrToB64url(cred.rawId),
clientDataJSON: arrToB64url(cred.response.clientDataJSON),
attestationObject: arrToB64url(cred.response.attestationObject),
hw_fingerprint: await getHardwareFingerprint()
})
});
const finishData = await finishRes.json();
if (!finishData.success) throw new Error(finishData.msg || 'Registration failed');
return finishData;
}
// ── Helpers ───────────────────────────────────────────────────────────────
_hasLocalPasskey() {
@@ -490,12 +539,26 @@
const res = await fetch('/api/v2/anon/identity');
const data = await res.json();
const fpEl = document.getElementById('anon-pk-fp-display');
const credEl = document.getElementById('anon-pk-cred-display');
if (fpEl) fpEl.textContent = data.fingerprint ? data.fingerprint.slice(7, 15) : 'none';
if (credEl) credEl.textContent = data.credential_id || '—';
this._renderPasskeyCount(data.passkey_count, data.passkey_max);
} catch (e) {}
}
_renderPasskeyCount(count, max) {
const countEl = document.getElementById('anon-pk-count');
const maxEl = document.getElementById('anon-pk-max');
const addBtn = document.getElementById('anon-pk-add-btn');
if (countEl && typeof count === 'number') countEl.textContent = count;
if (maxEl && typeof max === 'number') maxEl.textContent = max;
if (addBtn && typeof count === 'number' && typeof max === 'number') {
const full = count >= max;
addBtn.disabled = full;
addBtn.innerHTML = full
? '<i class="fa-solid fa-lock"></i> Limit reached'
: '<i class="fa-solid fa-plus"></i> Add passkey';
}
}
// ── Init ──────────────────────────────────────────────────────────────────
async init() {
@@ -568,24 +631,27 @@
const modalOverlay = document.getElementById('anon-passkey-modal');
if (modalOverlay) modalOverlay.addEventListener('click', e => { if (e.target === modalOverlay) this.closeModal(); });
// Add-passkey button inside modal (for anonymous users to add a second passkey)
// Add-passkey button inside modal — attaches a new passkey to this identity (max enforced server-side)
const addBtn = document.getElementById('anon-pk-add-btn');
if (addBtn) {
addBtn.addEventListener('click', async () => {
const errEl = document.getElementById('anon-pk-error');
if (errEl) errEl.style.display = 'none';
addBtn.disabled = true;
try {
await this.register();
if (typeof window.showToastNotification === 'function') window.showToastNotification('New passkey registered!');
this._refreshModalContent();
const data = await this.addPasskey();
if (typeof window.showToastNotification === 'function') window.showToastNotification('Passkey added');
this._renderPasskeyCount(data.passkey_count, data.passkey_max);
} catch (err) {
console.warn('[ANON_PASSKEY] Modal add passkey error:', err);
if (err && err.name === 'NotAllowedError') {
alert('Passkey creation was cancelled or blocked. If Bitwarden or another password manager is locked, please unlock it.');
} else {
alert('Failed to add passkey: ' + ((err && err.message) || 'Unknown error'));
if (errEl) {
errEl.style.display = '';
errEl.textContent = (err && err.name === 'NotAllowedError')
? 'Cancelled or blocked. Unlock your password manager and try again.'
: ((err && err.message) || 'Failed to add passkey.');
}
} finally {
addBtn.disabled = false;
this._refreshModalContent();
}
});
}