fix auth: register redirect, password reset flow, GHL contact ID, session handling

- Fix register redirect bug where successful signup returned 400 instead
  of redirecting to /account/setup (use isRedirect() to re-throw redirects)
- Add error display to register page and fix Svelte 5 syntax (on:click)
- Add await to Google sign-in on login page and register page
- Fix logout to await signOut and redirect to /
- Implement full password reset flow:
  - Add sendResetPassword callback to auth.ts config
  - Create forgot-password page with email submission
  - Create reset-password page with one-time token validation
  - Guard reset page: redirect to / if no valid token in URL
- Add ghlContactId field to Profile model for future Go High Level integration
- Add Prisma migration for ghl_contact_id column
- Update register test to expect redirect instead of broken 400
This commit is contained in:
NeilClack
2026-07-07 08:30:27 -05:00
parent f566866a23
commit 4d4b2a1497
12 changed files with 271 additions and 71 deletions
+59
View File
@@ -0,0 +1,59 @@
import type { PageServerLoad, Actions } from './$types';
import { auth } from '$lib/auth';
import { prisma } from '$lib/db';
import { fail, redirect } from '@sveltejs/kit';
export const load: PageServerLoad = async ({ url }) => {
const token = url.searchParams.get('token');
if (!token) {
throw redirect(302, '/');
}
const verification = await prisma.verification.findFirst({
where: {
value: token,
expiresAt: { gt: new Date() }
}
});
if (!verification) {
throw redirect(302, '/');
}
};
export const actions: Actions = {
default: async ({ request, url }) => {
const token = url.searchParams.get('token');
if (!token) {
return fail(400, { error: 'Invalid or missing reset token.' });
}
const data = await request.formData();
const password = data.get('password') as string;
const password2 = data.get('password2') as string;
if (!password || password.length < 8) {
return fail(400, { error: 'Password must be at least 8 characters.' });
}
if (password !== password2) {
return fail(400, { error: 'Passwords do not match.' });
}
try {
await auth.api.resetPassword({
body: {
newPassword: password,
token
}
});
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to reset password.';
return fail(400, { error: message });
}
throw redirect(303, '/login');
}
};
+40
View File
@@ -0,0 +1,40 @@
<script lang="ts">
import type { PageProps } from './$types';
let { form }: PageProps = $props();
</script>
<div class="mx-auto mt-12 max-w-md">
<h1 class="mb-6 text-2xl font-semibold text-gray-900">Reset Password</h1>
{#if form?.error}
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 p-3 text-red-700">
{form.error}
</div>
{/if}
<form method="POST" class="flex flex-col gap-4">
<input
type="password"
name="password"
placeholder="New Password"
required
minlength="8"
class="rounded-lg border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500"
/>
<input
type="password"
name="password2"
placeholder="Confirm New Password"
required
minlength="8"
class="rounded-lg border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500"
/>
<button
type="submit"
class="rounded-lg bg-blue-600 px-4 py-2 font-medium text-white transition-colors hover:bg-blue-700"
>
Reset Password
</button>
</form>
</div>