esifawafwu
This commit is contained in:
+77
-11
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user