Skip to content

Commit b895463

Browse files
feat: change-password form on /account.html (rung-1 → rung-2 climb) (#17)
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 / <new password>" 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
1 parent a4e4ab2 commit b895463

2 files changed

Lines changed: 93 additions & 2 deletions

File tree

account.html

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,38 @@ <h2>Pod</h2>
4141

4242
<h2>Explore</h2>
4343
<div class="actions">
44-
<a class="btn btn-secondary" id="profile-link" href="#">Profile</a>
4544
<a class="btn btn-secondary" href="./public/">/public/</a>
45+
<a class="btn btn-secondary" id="profile-link" href="#">profile</a>
4646
<a class="btn btn-secondary" href="./private/">/private/</a>
4747
<a class="btn btn-secondary" href="./inbox/">/inbox/</a>
4848
<a class="btn btn-secondary" href="./settings/">/settings/</a>
4949
</div>
5050

51+
<h2>Password</h2>
52+
<p id="pw-hint" style="margin:.25em 0 .5em;color:#666;font-size:.9em">Climb from rung 1 (default <code>me</code>) to rung 2 (your password).</p>
53+
<button class="btn btn-secondary" id="pw-toggle" type="button">Change password</button>
54+
<form id="pw-form" hidden style="margin:.75em 0 0;display:grid;gap:.5em;max-width:360px">
55+
<!-- Hidden username for password managers: present in the form but
56+
not rendered. Standard pattern used by GitHub, Google, etc.
57+
Browsers use this to associate the saved password with an
58+
account name in the credential store. -->
59+
<input type="text" id="pw-username" name="username" autocomplete="username" value="me" hidden>
60+
<label style="display:grid;gap:.2em;font-size:.9em;color:#555">Current password
61+
<input type="password" id="pw-current" autocomplete="current-password" required style="padding:.5em .8em;border:1px solid #ddd;border-radius:6px;font:inherit">
62+
</label>
63+
<label style="display:grid;gap:.2em;font-size:.9em;color:#555">New password
64+
<input type="password" id="pw-new" autocomplete="new-password" required minlength="4" style="padding:.5em .8em;border:1px solid #ddd;border-radius:6px;font:inherit">
65+
</label>
66+
<label style="display:grid;gap:.2em;font-size:.9em;color:#555">Confirm new password
67+
<input type="password" id="pw-confirm" autocomplete="new-password" required style="padding:.5em .8em;border:1px solid #ddd;border-radius:6px;font:inherit">
68+
</label>
69+
<div class="actions">
70+
<button class="btn" type="submit" id="pw-submit">Update password</button>
71+
<button class="btn btn-secondary" type="button" id="pw-cancel">Cancel</button>
72+
</div>
73+
<div id="pw-msg" style="display:none;padding:.6em .9em;border-radius:8px;font-size:.9em"></div>
74+
</form>
75+
5176
<h2>Session</h2>
5277
<div class="actions">
5378
<button class="btn btn-danger" id="signout-btn" type="button">Sign out</button>
@@ -110,6 +135,72 @@ <h2>Session</h2>
110135
fail('Sign-out failed: ' + (e.message || String(e)));
111136
}
112137
});
138+
139+
// Change-password flow. POSTs to JSS's PUT /idp/credentials with
140+
// {currentPassword, newPassword}. The endpoint requires the
141+
// session's DPoP-bound bearer token plus re-proof of the current
142+
// password — session.authFetch handles the auth headers, the form
143+
// collects the re-auth proof.
144+
const pwToggle = document.getElementById('pw-toggle');
145+
const pwForm = document.getElementById('pw-form');
146+
const pwCancel = document.getElementById('pw-cancel');
147+
const pwSubmit = document.getElementById('pw-submit');
148+
const pwMsg = document.getElementById('pw-msg');
149+
const pwCurrent = document.getElementById('pw-current');
150+
const pwNew = document.getElementById('pw-new');
151+
const pwConfirm = document.getElementById('pw-confirm');
152+
153+
function pwStatus(text, isError) {
154+
pwMsg.textContent = text;
155+
pwMsg.style.display = 'block';
156+
pwMsg.style.background = isError ? '#fff0f0' : '#e8f5e9';
157+
pwMsg.style.color = isError ? '#a00' : '#1b5e20';
158+
}
159+
function pwReset() {
160+
pwForm.reset();
161+
pwMsg.style.display = 'none';
162+
}
163+
164+
pwToggle.addEventListener('click', () => {
165+
pwForm.hidden = false;
166+
pwToggle.hidden = true;
167+
pwCurrent.focus();
168+
});
169+
pwCancel.addEventListener('click', () => {
170+
pwForm.hidden = true;
171+
pwToggle.hidden = false;
172+
pwReset();
173+
});
174+
pwForm.addEventListener('submit', async (ev) => {
175+
ev.preventDefault();
176+
if (pwNew.value !== pwConfirm.value) {
177+
pwStatus('New password and confirmation do not match.', true);
178+
return;
179+
}
180+
pwSubmit.disabled = true;
181+
pwStatus('Updating…');
182+
try {
183+
const res = await session.authFetch(new URL('./idp/credentials', window.location.href).href, {
184+
method: 'PUT',
185+
headers: { 'Content-Type': 'application/json' },
186+
body: JSON.stringify({ currentPassword: pwCurrent.value, newPassword: pwNew.value })
187+
});
188+
if (!res.ok) {
189+
let detail = '';
190+
try {
191+
const j = await res.json();
192+
detail = j.error_description || j.error || '';
193+
} catch {}
194+
throw new Error('HTTP ' + res.status + (detail ? ' — ' + detail : ''));
195+
}
196+
pwStatus('Password updated. You\'re on rung 2 now.');
197+
pwCurrent.value = pwNew.value = pwConfirm.value = '';
198+
} catch (e) {
199+
pwStatus('Change failed: ' + (e.message || String(e)), true);
200+
} finally {
201+
pwSubmit.disabled = false;
202+
}
203+
});
113204
} else {
114205
signedOutEl.hidden = false;
115206
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "jspod",
3-
"version": "0.0.18",
3+
"version": "0.0.19",
44
"description": "JavaScript Solid Pod - Just works, batteries included",
55
"type": "module",
66
"main": "index.js",

0 commit comments

Comments
 (0)