From 2adb07518c57bbf561a28960e0ec899107534095 Mon Sep 17 00:00:00 2001 From: Matt Edholm Date: Sat, 9 May 2026 15:25:54 -0400 Subject: [PATCH] feat(account): change-password endpoint + Settings modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PATCH /api/user/password — verifies the current password, enforces 8-char minimum on the new one, and rehashes via the configured password hasher. Returns 204 on success, 422 with an `error` body on every validation failure (wrong current, too-short new, missing fields). Settings adds a "Change password" link under the Account section that opens a modal with current/new/confirm fields and posts to the new endpoint. Confirm-mismatch and submit-disabled wiring is client-side; backend errors surface inline. Tests: 4 new controller tests cover success, wrong-current, short-new, and missing-fields; success path also re-fetches the user and checks the hash actually changed. --- frontend/src/views/SettingsView.vue | 195 ++++++++++++++++++ ...eView-BVevNXVc.js => HomeView-mbzgzaYa.js} | 2 +- ...ew-BmJwebDM.js => LibraryView-BpZzSEhN.js} | 2 +- public/build/assets/SettingsView-B7QpEYTf.css | 1 + public/build/assets/SettingsView-CbrGcLxA.js | 1 + public/build/assets/SettingsView-DWXOcfNX.js | 1 - public/build/assets/SettingsView-DmUQdUmU.css | 1 - ...iew-C0dEQPr3.js => UploadView-C19nnAIR.js} | 2 +- .../{index-DdJ5jHP4.js => index-BA_yAOa7.js} | 4 +- public/build/index.html | 2 +- src/Controller/UserApiController.php | 30 +++ .../Controller/UserApiControllerTest.php | 62 ++++++ 12 files changed, 295 insertions(+), 8 deletions(-) rename public/build/assets/{HomeView-BVevNXVc.js => HomeView-mbzgzaYa.js} (99%) rename public/build/assets/{LibraryView-BmJwebDM.js => LibraryView-BpZzSEhN.js} (99%) create mode 100644 public/build/assets/SettingsView-B7QpEYTf.css create mode 100644 public/build/assets/SettingsView-CbrGcLxA.js delete mode 100644 public/build/assets/SettingsView-DWXOcfNX.js delete mode 100644 public/build/assets/SettingsView-DmUQdUmU.css rename public/build/assets/{UploadView-C0dEQPr3.js => UploadView-C19nnAIR.js} (99%) rename public/build/assets/{index-DdJ5jHP4.js => index-BA_yAOa7.js} (99%) diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue index 4f4231d..4f1f95f 100644 --- a/frontend/src/views/SettingsView.vue +++ b/frontend/src/views/SettingsView.vue @@ -55,9 +55,73 @@ Signed in as {{ auth.user?.email }} + Sign out + +
(null) +const pwSuccess = ref(false) + +const pwConfirmMismatch = computed(() => + pwConfirm.value.length > 0 && pwConfirm.value !== pwNew.value, +) + +function resetPasswordForm() { + pwCurrent.value = '' + pwNew.value = '' + pwConfirm.value = '' + pwError.value = null + pwSuccess.value = false + pwSubmitting.value = false +} + +function closePasswordModal() { + passwordModalOpen.value = false + resetPasswordForm() +} + +async function submitPasswordChange() { + if (pwConfirmMismatch.value) return + pwError.value = null + pwSuccess.value = false + pwSubmitting.value = true + try { + const res = await fetch('/api/user/password', { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + currentPassword: pwCurrent.value, + newPassword: pwNew.value, + }), + }) + if (res.status === 204) { + pwSuccess.value = true + pwCurrent.value = '' + pwNew.value = '' + pwConfirm.value = '' + // Auto-close after a moment so the user sees the confirmation. + setTimeout(closePasswordModal, 1500) + return + } + const body = await res.json().catch(() => ({})) + pwError.value = body?.error ?? 'Could not update password.' + } catch { + pwError.value = 'Network error. Try again.' + } finally { + pwSubmitting.value = false + } +}