-
Notifications
You must be signed in to change notification settings - Fork 0
Mask email addresses in admin user list #79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -297,6 +297,7 @@ <h6 class="alert-heading">Health Details</h6> | |
| const API = ''; | ||
| let editingUserId = null; // when set, the Create button acts as Update | ||
| let editingUserName = null; // for heading text | ||
| let pendingEditId = null; // guards against stale out-of-order fetch responses | ||
|
|
||
| async function authFetch(path, opts = {}) { | ||
| opts.credentials = 'include'; | ||
|
|
@@ -376,7 +377,10 @@ <h6 class="alert-heading">Health Details</h6> | |
| tr.innerHTML = ` | ||
| <td>${u.id}</td> | ||
| <td class="user-username cell-like" data-id="${u.id}">${u.username}</td> | ||
| <td class="user-email cell-like" data-id="${u.id}">${u.email}</td> | ||
| <td class="user-email cell-like" data-id="${u.id}"> | ||
| <span id="email-display-${u.id}">${u.email}</span> | ||
| <button class="btn btn-link p-0 ms-1 reveal-email" data-id="${u.id}" style="font-size:0.72rem;vertical-align:baseline;opacity:0.6">reveal</button> | ||
| </td> | ||
| <td class="user-role cell-like" data-id="${u.id}">${u.role}</td> | ||
| <td class="d-flex gap-2"> | ||
| <button class="btn btn-sm btn-primary edit-in-form" data-id="${u.id}">Edit</button> | ||
|
|
@@ -392,14 +396,20 @@ <h6 class="alert-heading">Health Details</h6> | |
| } | ||
|
|
||
| function attachHandlers(cachedUsers) { | ||
| // Edit → copy row values into the bottom form and highlight row | ||
| // Edit → fetch full user detail (unmasked email) then populate form | ||
| document.querySelectorAll('.edit-in-form').forEach(btn => { | ||
| btn.onclick = () => { | ||
| btn.onclick = async () => { | ||
| const id = +btn.dataset.id; | ||
| const u = cachedUsers.find(x => x.id === id); | ||
| if (!u) return; | ||
| pendingEditId = id; | ||
| let u; | ||
| try { | ||
| u = await authFetch(`/users/${id}`, { method: 'GET' }); | ||
| } catch (err) { | ||
| if (pendingEditId === id) alert('Could not load user: ' + err.message); | ||
| return; | ||
| } | ||
| if (pendingEditId !== id) return; // superseded by a later edit click | ||
|
|
||
| // copy values to form (ONLY editable place) | ||
| document.getElementById('newUsername').value = u.username; | ||
| document.getElementById('newEmail').value = u.email; | ||
| document.getElementById('newRole').value = u.role; | ||
|
|
@@ -410,12 +420,25 @@ <h6 class="alert-heading">Health Details</h6> | |
| setFormMode('update'); | ||
| highlightRow(id); | ||
|
|
||
| // bring form into view | ||
| document.getElementById('createForm') | ||
| .scrollIntoView({behavior:'smooth', block:'center'}); | ||
| }; | ||
| }); | ||
|
|
||
| // Reveal → fetch full user detail and replace masked email in table | ||
| document.querySelectorAll('.reveal-email').forEach(btn => { | ||
| btn.onclick = async () => { | ||
| const id = +btn.dataset.id; | ||
| try { | ||
| const u = await authFetch(`/users/${id}`, { method: 'GET' }); | ||
| document.getElementById(`email-display-${id}`).textContent = u.email; | ||
| btn.remove(); | ||
| } catch (err) { | ||
| alert('Could not reveal email: ' + err.message); | ||
| } | ||
| }; | ||
| }); | ||
|
|
||
| // Delete | ||
| document.querySelectorAll('.delete-btn').forEach(btn => { | ||
| btn.onclick = async () => { | ||
|
|
@@ -519,30 +542,19 @@ <h6 class="alert-heading">Health Details</h6> | |
|
|
||
| try { | ||
| const users = await authFetch('/users', { method: 'GET' }); | ||
| const lowerU = username.toLowerCase(), lowerE = email.toLowerCase(); | ||
| const lowerU = username.toLowerCase(); | ||
|
|
||
| // exclude record being edited from duplicate checks | ||
| // username duplicate check (emails are masked in list, so email check is server-side) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Keep a case-insensitive duplicate check rather than relying solely on Useful? React with 👍 / 👎. |
||
| const usernameTaken = users.some(u => | ||
| (editingUserId ? u.id !== editingUserId : true) && | ||
| (u.username||'').toLowerCase() === lowerU | ||
| ); | ||
| const emailTaken = users.some(u => | ||
| (editingUserId ? u.id !== editingUserId : true) && | ||
| (u.email||'').toLowerCase() === lowerE | ||
| ); | ||
|
|
||
| let blocked = false; | ||
| if (usernameTaken){ | ||
| usernameError.textContent = 'Username already exists.'; | ||
| usernameError.classList.remove('d-none'); | ||
| blocked = true; | ||
| } | ||
| if (emailTaken){ | ||
| emailError.textContent = 'Email already in use.'; | ||
| emailError.classList.remove('d-none'); | ||
| blocked = true; | ||
| return; | ||
| } | ||
| if (blocked) return; | ||
|
|
||
| if (editingUserId){ // update existing user | ||
| const payload = { username, email, role }; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prevent an earlier edit request from overwriting a later selection. If an admin clicks Edit for user A and then user B before A's request completes, and B's response arrives first, A's later response repopulates the form and resets
editingUserIdto A; the next submission can therefore modify the wrong account. Track the latest requested ID, disable competing edits, or abort superseded requests before applying the response.Useful? React with 👍 / 👎.