fix password resets

This commit is contained in:
Samidy 2026-04-13 22:51:05 +03:00
parent 4497863667
commit d462542bbd
4 changed files with 194 additions and 6 deletions

View file

@ -5930,6 +5930,111 @@
</div>
</div>
</div>
<div id="page-reset-password" class="page">
<div
class="reset-password-container"
style="text-align: center; padding: var(--space-8); max-width: 400px; margin: 0 auto"
>
<h2 class="section-title" style="margin-bottom: var(--space-10)">Reset your Password</h2>
<div
id="reset-password-error"
class="message error-msg"
style="
display: none;
padding: var(--space-3) 0;
border-radius: var(--radius-md);
font-size: var(--text-sm);
margin-bottom: var(--space-4);
background: transparent;
color: #ff6b6b;
text-align: left;
"
></div>
<div
id="reset-password-success"
class="message success-msg"
style="
display: none;
padding: var(--space-3) 0;
border-radius: var(--radius-md);
font-size: var(--text-sm);
margin-bottom: var(--space-4);
background: transparent;
color: var(--foreground);
text-align: left;
"
></div>
<form id="reset-password-form">
<div style="margin-bottom: var(--space-4)">
<input
type="password"
id="reset-password-input"
placeholder="New Password"
required
minlength="8"
style="
width: 100%;
padding: var(--space-3) var(--space-4);
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--card);
color: var(--foreground);
font-size: 0.925rem;
outline: none;
transition: border-color 0.15s;
"
/>
</div>
<div style="margin-bottom: var(--space-6)">
<input
type="password"
id="reset-password-confirm"
placeholder="Confirm New Password"
required
minlength="8"
style="
width: 100%;
padding: var(--space-3) var(--space-4);
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--card);
color: var(--foreground);
font-size: 0.925rem;
outline: none;
transition: border-color 0.15s;
"
/>
</div>
<button
type="submit"
id="reset-password-submit-btn"
class="btn-primary"
style="width: 100%; justify-content: center"
>
<span id="reset-password-btn-text">Reset Password</span>
<div
id="reset-password-btn-spinner"
class="animate-spin"
style="display: none; width: 18px; height: 18px"
>
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
style="width: 100%; height: 100%"
>
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
</svg>
</div>
</button>
</form>
</div>
</div>
</main>
<footer class="now-playing-bar">

View file

@ -15,12 +15,14 @@ export class AuthManager {
const isOAuthRedirect = params.get('oauth') === '1';
if (userId && secret && userId !== 'null' && secret !== 'null') {
try {
await auth.createSession(userId, secret);
window.history.replaceState({}, '', window.location.pathname);
} catch (error) {
console.warn('OAuth session handoff failed:', error.message);
window.history.replaceState({}, '', window.location.pathname);
if (window.location.pathname !== '/reset-password') {
try {
await auth.createSession(userId, secret);
window.history.replaceState({}, '', window.location.pathname);
} catch (error) {
console.warn('OAuth session handoff failed:', error.message);
window.history.replaceState({}, '', window.location.pathname);
}
}
} else if (isOAuthRedirect) {
await new Promise((resolve) => setTimeout(resolve, 500));
@ -137,6 +139,18 @@ export class AuthManager {
}
}
async resetPassword(userId, secret, password, confirmPassword) {
if (password !== confirmPassword) {
throw new Error('Passwords do not match');
}
try {
await auth.updateRecovery(userId, secret, password, password);
} catch (error) {
console.error('Password reset failed:', error);
throw error;
}
}
async signOut() {
try {
await auth.deleteSession('current');

View file

@ -114,6 +114,9 @@ export function createRouter(ui) {
case 'home':
await ui.renderHomePage();
break;
case 'reset-password':
await ui.renderResetPasswordPage();
break;
case 'donate':
ui.showPage('donate');
break;

View file

@ -2392,6 +2392,72 @@ export class UIRenderer {
}
}
async renderResetPasswordPage() {
await this.showPage('reset-password');
const form = document.getElementById('reset-password-form');
const errorEl = document.getElementById('reset-password-error');
const successEl = document.getElementById('reset-password-success');
const btn = document.getElementById('reset-password-submit-btn');
const btnText = document.getElementById('reset-password-btn-text');
const spinner = document.getElementById('reset-password-btn-spinner');
const passwordInput = document.getElementById('reset-password-input');
const confirmInput = document.getElementById('reset-password-confirm');
if (!form) return;
const params = new URLSearchParams(window.location.search);
const userId = params.get('userId');
const secret = params.get('secret');
if (!userId || !secret) {
errorEl.textContent = 'Invalid or missing password reset link.';
errorEl.style.display = 'block';
form.style.display = 'none';
return;
}
form.onsubmit = async (e) => {
e.preventDefault();
errorEl.style.display = 'none';
successEl.style.display = 'none';
const password = passwordInput.value;
const confirm = confirmInput.value;
if (password !== confirm) {
errorEl.textContent = 'Passwords do not match.';
errorEl.style.display = 'block';
return;
}
try {
btn.disabled = true;
btnText.style.display = 'none';
spinner.style.display = 'block';
await authManager.resetPassword(userId, secret, password, confirm);
successEl.textContent = 'Password reset successfully. Opening login...';
successEl.style.display = 'block';
form.style.display = 'none';
setTimeout(() => {
const authModal = document.getElementById('email-auth-modal');
if (authModal) {
authModal.classList.add('active');
}
}, 2000);
} catch (error) {
errorEl.textContent = error.message || 'Failed to reset password. Please try again.';
errorEl.style.display = 'block';
} finally {
btn.disabled = false;
btnText.style.display = 'inline';
spinner.style.display = 'none';
}
};
}
async renderPartyDetailPage(id) {
await this.showPage('party-detail');
await partyManager.joinParty(id);