Added Multiple choice when allowCredentials is empty

This commit is contained in:
2025-05-14 03:17:08 +09:00
parent 70fba2b72e
commit 8bdac9e648
2 changed files with 69 additions and 13 deletions

25
fido.py
View File

@@ -313,6 +313,7 @@ class VirtualFidoDevice:
allowed_credential = data.get("allowCredentials")
cred = None
if allowed_credential:
for credential in allowed_credential:
credential_id_b64 = credential["id"]
@@ -320,10 +321,22 @@ class VirtualFidoDevice:
cred = self.credentials[credential_id_b64]
break
else:
for credential_id_b64, my_credential in self.credentials.items():
if my_credential["rp_id"] == rp_id.decode():
cred = my_credential
break
match_creds = []
for cid, cr in self.credentials.items():
if cr["rp_id"] == rp_id.decode():
match_creds.append(cid)
if len(match_creds) == 1:
cred = self.credentials[cid]
else:
results = []
for cr in match_creds:
current = data.copy()
current["allowCredentials"] = [{"id": cr}]
results.append(self.get(current, origin))
return results
if not cred:
raise self.CredNotFoundError()
@@ -360,7 +373,9 @@ class VirtualFidoDevice:
"signature": self._b64url(signature),
"userHandle": cred["user_id"]
},
"type": "public-key"
"type": "public-key",
"username": cred["user_name"],
"created": cred["created"]
}
return response

View File

@@ -56,6 +56,43 @@ function myFetch(url, options = {}) {
});
}
function showCredentialSelectionPopup(credentials) {
return new Promise((resolve) => {
const popup = document.createElement("div");
popup.style.position = "fixed";
popup.style.top = "20px";
popup.style.right = "20px";
popup.style.backgroundColor = "#fff";
popup.style.color = "#000";
popup.style.border = "1px solid #bbb";
popup.style.borderRadius = "5px";
popup.style.padding = "15px";
popup.style.zIndex = "9999";
popup.style.maxWidth = "300px";
const title = document.createElement("h3");
title.textContent = "Select credential";
title.style.margin = "0 0 10px 0";
popup.appendChild(title);
credentials.forEach((cred, index) => {
const option = document.createElement("div");
option.style.padding = "8px 10px";
option.style.cursor = "pointer";
const createdDate = new Date(cred.created * 1000).toLocaleString();
option.innerHTML = `
<strong>${cred.username || 'Unknown user'}</strong>
<div style="font-size: 0.8em; color: #666;">Created: ${createdDate}</div>
`;
option.addEventListener("mouseover", () => { option.style.backgroundColor = "#f0f0f0"; });
option.addEventListener("mouseout", () => { option.style.backgroundColor = "transparent"; });
option.addEventListener("click", () => { document.body.removeChild(popup); resolve(cred); });
popup.appendChild(option);
});
document.body.appendChild(popup);
});
}
const origGet = navigator.credentials.get;
const origCreate = navigator.credentials.create;
@@ -79,19 +116,23 @@ navigator.credentials.get = async function(options) {
if (!response.ok) throw new Error(`server error: ${response.status}`)
const resp = await response.json()
console.log("server response:", resp)
let cred = resp;
if (Array.isArray(resp) && resp.length > 0) {
cred = await showCredentialSelectionPopup(resp);
}
const credential = {
id: resp.id,
type: resp.type,
rawId: b64ab(resp.rawId),
id: cred.id,
type: cred.type,
rawId: b64ab(cred.rawId),
response: {
authenticatorData: b64ab(resp.response.authenticatorData),
clientDataJSON: b64ab(resp.response.clientDataJSON),
signature: b64ab(resp.response.signature)
authenticatorData: b64ab(cred.response.authenticatorData),
clientDataJSON: b64ab(cred.response.clientDataJSON),
signature: b64ab(cred.response.signature)
},
getClientExtensionResults: () => { return {} }
}
if (resp.response.userHandle) {
credential.response.userHandle = b64ab(resp.response.userHandle);
if (cred.response.userHandle) {
credential.response.userHandle = b64ab(cred.response.userHandle);
}
console.log(credential)
return credential;