Skip to content

Commit 6bf3bf4

Browse files
committed
Replace rename passkey JS prompt with Bootstrap modal dialog
1 parent 48c9311 commit 6bf3bf4

2 files changed

Lines changed: 115 additions & 37 deletions

File tree

src/main/resources/static/js/user/webauthn-manage.js

Lines changed: 91 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ function displayCredentials(container, credentials) {
6060
: '<span class="badge bg-warning text-dark">Device-bound</span>'}
6161
</div>
6262
<div class="flex-shrink-0">
63-
<button class="btn btn-sm btn-outline-secondary me-1" onclick="window.renamePasskey('${escapeHtml(cred.id)}')">
63+
<button class="btn btn-sm btn-outline-secondary me-1" onclick="window.renamePasskey('${escapeHtml(cred.id)}', '${escapeHtml(cred.label || '')}')">
6464
<i class="bi bi-pencil"></i> Rename
6565
</button>
6666
<button class="btn btn-sm btn-outline-danger" onclick="window.deletePasskey('${escapeHtml(cred.id)}')">
@@ -73,47 +73,101 @@ function displayCredentials(container, credentials) {
7373
}
7474

7575
/**
76-
* Rename a passkey.
76+
* Show the rename passkey modal.
7777
*/
78-
async function renamePasskey(credentialId) {
79-
const newLabel = prompt('Enter new name for this passkey (max 64 characters):');
80-
if (!newLabel) return;
81-
82-
if (newLabel.length > 64) {
83-
const globalMsg = document.getElementById('passkeyMessage');
84-
if (globalMsg) {
85-
showMessage(globalMsg, 'Passkey name is too long (max 64 characters).', 'alert-danger');
78+
function renamePasskey(credentialId, currentLabel) {
79+
const input = document.getElementById('renamePasskeyInput');
80+
const counter = document.getElementById('renamePasskeyCount');
81+
const errorEl = document.getElementById('renamePasskeyError');
82+
const confirmBtn = document.getElementById('confirmRenameButton');
83+
84+
// Pre-fill with current label
85+
input.value = currentLabel || '';
86+
counter.textContent = `${input.value.length} / 64`;
87+
errorEl.classList.add('d-none');
88+
input.classList.remove('is-invalid');
89+
90+
// Show modal
91+
const modal = new bootstrap.Modal(document.getElementById('renamePasskeyModal'));
92+
modal.show();
93+
94+
// Focus input when modal is shown
95+
document.getElementById('renamePasskeyModal').addEventListener('shown.bs.modal', () => {
96+
input.select();
97+
}, { once: true });
98+
99+
// Character counter
100+
const onInput = () => {
101+
counter.textContent = `${input.value.length} / 64`;
102+
if (input.value.trim()) {
103+
errorEl.classList.add('d-none');
104+
input.classList.remove('is-invalid');
86105
}
87-
return;
88-
}
89-
90-
const globalMessage = document.getElementById('passkeyMessage');
91-
92-
try {
93-
const response = await fetch(`/user/webauthn/credentials/${credentialId}/label`, {
94-
method: 'PUT',
95-
headers: {
96-
'Content-Type': 'application/json',
97-
[csrfHeader]: csrfToken
98-
},
99-
body: JSON.stringify({ label: newLabel })
100-
});
101-
102-
if (!response.ok) {
103-
const data = await response.json();
104-
throw new Error(data.message || 'Failed to rename passkey');
106+
};
107+
input.addEventListener('input', onInput);
108+
109+
// Submit on Enter key
110+
const onKeydown = (e) => {
111+
if (e.key === 'Enter') {
112+
e.preventDefault();
113+
confirmBtn.click();
105114
}
106-
107-
if (globalMessage) {
108-
showMessage(globalMessage, 'Passkey renamed successfully.', 'alert-success');
115+
};
116+
input.addEventListener('keydown', onKeydown);
117+
118+
// Handle confirm click
119+
const onConfirm = async () => {
120+
const newLabel = input.value.trim();
121+
if (!newLabel) {
122+
errorEl.textContent = 'Please enter a name.';
123+
errorEl.classList.remove('d-none');
124+
input.classList.add('is-invalid');
125+
return;
109126
}
110-
loadPasskeys();
111-
} catch (error) {
112-
console.error('Failed to rename passkey:', error);
113-
if (globalMessage) {
114-
showMessage(globalMessage, error.message, 'alert-danger');
127+
128+
confirmBtn.disabled = true;
129+
confirmBtn.textContent = 'Saving...';
130+
131+
const globalMessage = document.getElementById('passkeyMessage');
132+
133+
try {
134+
const response = await fetch(`/user/webauthn/credentials/${credentialId}/label`, {
135+
method: 'PUT',
136+
headers: {
137+
'Content-Type': 'application/json',
138+
[csrfHeader]: csrfToken
139+
},
140+
body: JSON.stringify({ label: newLabel })
141+
});
142+
143+
if (!response.ok) {
144+
const data = await response.json();
145+
throw new Error(data.message || 'Failed to rename passkey');
146+
}
147+
148+
modal.hide();
149+
if (globalMessage) {
150+
showMessage(globalMessage, 'Passkey renamed successfully.', 'alert-success');
151+
}
152+
loadPasskeys();
153+
} catch (error) {
154+
console.error('Failed to rename passkey:', error);
155+
errorEl.textContent = error.message;
156+
errorEl.classList.remove('d-none');
157+
input.classList.add('is-invalid');
158+
} finally {
159+
confirmBtn.disabled = false;
160+
confirmBtn.textContent = 'Save';
115161
}
116-
}
162+
};
163+
confirmBtn.addEventListener('click', onConfirm);
164+
165+
// Clean up listeners when modal is hidden
166+
document.getElementById('renamePasskeyModal').addEventListener('hidden.bs.modal', () => {
167+
input.removeEventListener('input', onInput);
168+
input.removeEventListener('keydown', onKeydown);
169+
confirmBtn.removeEventListener('click', onConfirm);
170+
}, { once: true });
117171
}
118172

119173
/**

src/main/resources/templates/user/update-user.html

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,30 @@ <h5 class="mb-0"><i class="bi bi-key me-2"></i>Passkeys</h5>
6868
</div>
6969
</div>
7070

71+
<!-- Rename Passkey Modal -->
72+
<div class="modal fade" id="renamePasskeyModal" tabindex="-1" aria-labelledby="renamePasskeyLabel" aria-hidden="true">
73+
<div class="modal-dialog modal-dialog-centered">
74+
<div class="modal-content">
75+
<div class="modal-header">
76+
<h5 class="modal-title" id="renamePasskeyLabel"><i class="bi bi-pencil me-2"></i>Rename Passkey</h5>
77+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
78+
</div>
79+
<div class="modal-body">
80+
<label for="renamePasskeyInput" class="form-label">Passkey name</label>
81+
<input type="text" id="renamePasskeyInput" class="form-control" maxlength="64" placeholder="e.g. MacBook Pro">
82+
<div class="d-flex justify-content-between mt-1">
83+
<div id="renamePasskeyError" class="form-text text-danger d-none"></div>
84+
<small id="renamePasskeyCount" class="form-text text-muted ms-auto">0 / 64</small>
85+
</div>
86+
</div>
87+
<div class="modal-footer">
88+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
89+
<button type="button" id="confirmRenameButton" class="btn btn-primary">Save</button>
90+
</div>
91+
</div>
92+
</div>
93+
</div>
94+
7195
<!-- Additional Options -->
7296
<div class="text-center mt-4">
7397
<a href="/user/update-password.html" class="btn btn-outline-secondary" th:utext="#{action.update-password}">Change Password</a>

0 commit comments

Comments
 (0)