From 856c9f22804430aab96eeb501ce42b011bf95717 Mon Sep 17 00:00:00 2001 From: Melvin Carvalho Date: Sat, 16 May 2026 09:06:02 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20change-password=20form=20on=20/account.?= =?UTF-8?q?html=20(rung-1=20=E2=86=92=20rung-2=20climb)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a Password section to the account dashboard. Reveal-on-click form (current / new / confirm) that POSTs to JSS's PUT /idp/credentials via session.authFetch (DPoP-bound bearer). On success, shows "Password updated. You're on rung 2 now." Closes the climb path from issue #6's auth ladder. Form markup follows the documented standard for change-password forms (hidden username input with autocomplete="username", visible current-password + two new-password inputs). Chrome offers to save "me / " as a credential. Brave's password manager has stricter heuristics and may not prompt on http://localhost — known browser quirk, not a markup issue. Also tidies the Explore row on the dashboard: - swap /public/ and profile (now /public/ first) - lowercase "profile" (matches the URL-shaped sibling buttons) Bumps jspod to 0.0.19. Refs #1 #6 --- account.html | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++- package.json | 2 +- 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/account.html b/account.html index ef8858a..04f9e24 100644 --- a/account.html +++ b/account.html @@ -41,13 +41,38 @@

Pod

Explore

+

Password

+

Climb from rung 1 (default me) to rung 2 (your password).

+ + +

Session

@@ -110,6 +135,72 @@

Session

fail('Sign-out failed: ' + (e.message || String(e))); } }); + + // Change-password flow. POSTs to JSS's PUT /idp/credentials with + // {currentPassword, newPassword}. The endpoint requires the + // session's DPoP-bound bearer token plus re-proof of the current + // password — session.authFetch handles the auth headers, the form + // collects the re-auth proof. + const pwToggle = document.getElementById('pw-toggle'); + const pwForm = document.getElementById('pw-form'); + const pwCancel = document.getElementById('pw-cancel'); + const pwSubmit = document.getElementById('pw-submit'); + const pwMsg = document.getElementById('pw-msg'); + const pwCurrent = document.getElementById('pw-current'); + const pwNew = document.getElementById('pw-new'); + const pwConfirm = document.getElementById('pw-confirm'); + + function pwStatus(text, isError) { + pwMsg.textContent = text; + pwMsg.style.display = 'block'; + pwMsg.style.background = isError ? '#fff0f0' : '#e8f5e9'; + pwMsg.style.color = isError ? '#a00' : '#1b5e20'; + } + function pwReset() { + pwForm.reset(); + pwMsg.style.display = 'none'; + } + + pwToggle.addEventListener('click', () => { + pwForm.hidden = false; + pwToggle.hidden = true; + pwCurrent.focus(); + }); + pwCancel.addEventListener('click', () => { + pwForm.hidden = true; + pwToggle.hidden = false; + pwReset(); + }); + pwForm.addEventListener('submit', async (ev) => { + ev.preventDefault(); + if (pwNew.value !== pwConfirm.value) { + pwStatus('New password and confirmation do not match.', true); + return; + } + pwSubmit.disabled = true; + pwStatus('Updating…'); + try { + const res = await session.authFetch(new URL('./idp/credentials', window.location.href).href, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ currentPassword: pwCurrent.value, newPassword: pwNew.value }) + }); + if (!res.ok) { + let detail = ''; + try { + const j = await res.json(); + detail = j.error_description || j.error || ''; + } catch {} + throw new Error('HTTP ' + res.status + (detail ? ' — ' + detail : '')); + } + pwStatus('Password updated. You\'re on rung 2 now.'); + pwCurrent.value = pwNew.value = pwConfirm.value = ''; + } catch (e) { + pwStatus('Change failed: ' + (e.message || String(e)), true); + } finally { + pwSubmit.disabled = false; + } + }); } else { signedOutEl.hidden = false; } diff --git a/package.json b/package.json index 235d8a8..ff8a11a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jspod", - "version": "0.0.18", + "version": "0.0.19", "description": "JavaScript Solid Pod - Just works, batteries included", "type": "module", "main": "index.js",