final flows including emails for passwords

This commit is contained in:
JCEEE
2026-09-01 11:58:43 +01:00
parent f491dbfbf1
commit 40d7bf7398
22 changed files with 1977 additions and 859 deletions
+62
View File
@@ -0,0 +1,62 @@
import { json } from '@sveltejs/kit';
import { RESEND_API } from '$app/env/private';
import { createSuperClient } from '$lib/server/pocketbase';
import { Resend } from 'resend';
export async function POST({ request, url }) {
const fd = await request.formData();
const email = (fd.get('email') || '').toString().trim().toLowerCase();
if (!email) {
return json({ ok: true }); // Generic response to avoid enumeration
}
try {
const pb = await createSuperClient();
// Find user by email
let user;
try {
user = await pb.collection('users').getFirstListItem(`email = '${email}'`);
} catch {
// User not found — still return success
return json({ ok: true });
}
// Only allow password reset for parents (they have emails)
if (user.role !== 'parent') {
return json({ ok: true });
}
// Generate token and expiry (1 hour)
const token = globalThis.crypto.randomUUID();
const expiry = new Date(Date.now() + 60 * 60 * 1000).toISOString();
// Store token on user record
await pb.collection('users').update(user.id, {
passwordResetToken: token,
passwordResetExpiry: expiry
});
// Send email via Resend
const resend = new Resend(String(RESEND_API));
const resetUrl = `${url.origin}/login/verify/${token}`;
await resend.emails.send({
from: 'no-reply@famchamp.ai',
to: email,
subject: 'Reset your FamChore password',
html: `
<p>You requested a password reset for your FamChore account.</p>
<p><a href="${resetUrl}">Click here to reset your password</a></p>
<p>This link expires in 1 hour.</p>
<p>If you didn't request this, you can ignore this email.</p>
`
});
} catch {
// Swallow all errors to avoid information leakage
}
// Always return success
return json({ ok: true });
}