feat(auth): allow naming the initial passkey during registration
- Add "Passkey Name" field to the registration form - Pass passkey_name through start/finish WebAuthn ceremony - Use user-provided name instead of hardcoded "default"
This commit is contained in:
parent
16a4704fcf
commit
6a0d94739b
3 changed files with 26 additions and 4 deletions
|
|
@ -47,6 +47,7 @@ struct CliCallbackTemplate {
|
|||
pub struct RegisterStartRequest {
|
||||
pub token: String,
|
||||
pub display_name: String,
|
||||
pub passkey_name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
|
@ -58,6 +59,7 @@ pub struct ChallengeResponse<T: Serialize> {
|
|||
#[derive(Deserialize)]
|
||||
pub struct RegisterFinishRequest {
|
||||
pub challenge_id: String,
|
||||
pub passkey_name: String,
|
||||
pub credential: RegisterPublicKeyCredential,
|
||||
}
|
||||
|
||||
|
|
@ -211,7 +213,7 @@ pub(crate) async fn register_finish(
|
|||
// Store the credential
|
||||
let credential_json =
|
||||
serde_json::to_string(&passkey).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let new_credential = NewPasskeyCredential::new(user_id, credential_json, "default".to_string());
|
||||
let new_credential = NewPasskeyCredential::new(user_id, credential_json, payload.passkey_name);
|
||||
state
|
||||
.passkey_repo
|
||||
.insert(new_credential)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,17 @@
|
|||
/>
|
||||
</label>
|
||||
|
||||
<label class="flex flex-col gap-1 text-sm">
|
||||
<span class="text-stone-700">Passkey Name</span>
|
||||
<input
|
||||
type="text"
|
||||
id="passkey-name"
|
||||
required
|
||||
class="input-field"
|
||||
placeholder="e.g. MacBook Touch ID, iPhone"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<button
|
||||
id="register-button"
|
||||
type="button"
|
||||
|
|
@ -63,6 +74,7 @@
|
|||
const token = "{{ token }}";
|
||||
const button = document.getElementById("register-button");
|
||||
const displayNameInput = document.getElementById("display-name");
|
||||
const passkeyNameInput = document.getElementById("passkey-name");
|
||||
const errorDiv = document.getElementById("register-error");
|
||||
const loadingDiv = document.getElementById("register-loading");
|
||||
const unsupportedDiv = document.getElementById("register-unsupported");
|
||||
|
|
@ -84,12 +96,19 @@
|
|||
return;
|
||||
}
|
||||
|
||||
const passkeyName = passkeyNameInput.value.trim();
|
||||
if (!passkeyName) {
|
||||
errorDiv.textContent = "Please enter a name for this passkey.";
|
||||
errorDiv.classList.remove("hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
errorDiv.classList.add("hidden");
|
||||
loadingDiv.classList.remove("hidden");
|
||||
button.disabled = true;
|
||||
|
||||
try {
|
||||
await startPasskeyRegistration(token, displayName);
|
||||
await startPasskeyRegistration(token, displayName, passkeyName);
|
||||
formDiv.classList.add("hidden");
|
||||
successDiv.classList.remove("hidden");
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -73,12 +73,12 @@ function serializeAuthenticationCredential(credential) {
|
|||
}
|
||||
|
||||
// Start passkey registration ceremony
|
||||
async function startPasskeyRegistration(token, displayName) {
|
||||
async function startPasskeyRegistration(token, displayName, passkeyName) {
|
||||
// 1. Get challenge from server
|
||||
const startResponse = await fetch("/api/v1/webauthn/register/start", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ token, display_name: displayName }),
|
||||
body: JSON.stringify({ token, display_name: displayName, passkey_name: passkeyName }),
|
||||
});
|
||||
|
||||
if (!startResponse.ok) {
|
||||
|
|
@ -100,6 +100,7 @@ async function startPasskeyRegistration(token, displayName) {
|
|||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
challenge_id,
|
||||
passkey_name: passkeyName,
|
||||
credential: serializeRegistrationCredential(credential),
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue