Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/lib/auth-redirect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ describe('safeRedirectPath', () => {
});

describe('readPostAuthRedirectUrl', () => {
it('prefers redirect_url and supports redirectUrl', () => {
it('prefers redirect_url and supports Clerk redirect params', () => {
expect(readPostAuthRedirectUrl(new URLSearchParams('redirect_url=/runs'))).toBe('/runs');
expect(readPostAuthRedirectUrl(new URLSearchParams('redirectUrl=/regression'))).toBe(
'/regression'
);
expect(readPostAuthRedirectUrl(new URLSearchParams('redirectAfterAuth=/runs/123'))).toBe(
'/runs/123'
);
});
Comment on lines +19 to 27

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The priority ordering isn't actually tested — each param is only exercised in isolation. If the ?? chain order was accidentally reversed so redirectAfterAuth beat redirect_url, no existing test would catch it. Adding a case where multiple params are present (e.g., redirect_url=/runs&redirectAfterAuth=/other) and asserting the expected winner would fully verify the precedence contract.

Fix in Cursor Fix in Codex


it('falls back when params are missing or unsafe', () => {
Expand Down
16 changes: 11 additions & 5 deletions src/lib/auth-redirect.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const defaultPostAuthPath = '/runs';
const defaultPostAuthPath: `/${string}` = '/runs';

export function safeRedirectPath(path: string | null | undefined, fallback = defaultPostAuthPath) {
export function safeRedirectPath(
path: string | null | undefined,
fallback: `/${string}` = defaultPostAuthPath
): `/${string}` {
if (
!path ||
!path.startsWith('/') ||
Expand All @@ -10,13 +13,16 @@ export function safeRedirectPath(path: string | null | undefined, fallback = def
) {
return fallback;
}
return path;
return path as `/${string}`;
}

export function readPostAuthRedirectUrl(
searchParams: URLSearchParams,
fallback = defaultPostAuthPath
fallback: `/${string}` = defaultPostAuthPath
) {
const raw = searchParams.get('redirect_url') ?? searchParams.get('redirectUrl');
const raw =
searchParams.get('redirect_url') ??
searchParams.get('redirectUrl') ??
searchParams.get('redirectAfterAuth');
return safeRedirectPath(raw, fallback);
}
10 changes: 10 additions & 0 deletions src/lib/components/post-auth-redirect.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script lang="ts">
import { base } from '$app/paths';
import { onMount } from 'svelte';

let { to }: { to: `/${string}` } = $props();

onMount(() => {
window.location.replace(`${base}${to}`);
});
</script>
2 changes: 1 addition & 1 deletion src/lib/server/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { error, redirect, type RequestEvent } from '@sveltejs/kit';

export { safeRedirectPath } from '$lib/auth-redirect';

export function readPostAuthRedirectUrl(event: RequestEvent, fallback?: string) {
export function readPostAuthRedirectUrl(event: RequestEvent, fallback?: `/${string}`) {
return readPostAuthRedirectUrlFromSearchParams(event.url.searchParams, fallback);
}

Expand Down
14 changes: 11 additions & 3 deletions src/routes/(marketing)/sign-in/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
<script lang="ts">
import { page } from '$app/state';
import AuthWidgetLoading from '$lib/components/auth-widget-loading.svelte';
import PostAuthRedirect from '$lib/components/post-auth-redirect.svelte';
import ClerkLoaded from 'clerk-sveltekit/client/ClerkLoaded.svelte';
import ClerkLoading from 'clerk-sveltekit/client/ClerkLoading.svelte';
import SignIn from 'clerk-sveltekit/client/SignIn.svelte';
import { safeRedirectPath } from '$lib/auth-redirect';

const redirectUrl = $derived(
safeRedirectPath(
page.url.searchParams.get('redirect_url') ?? page.url.searchParams.get('redirectUrl')
page.url.searchParams.get('redirect_url') ??
page.url.searchParams.get('redirectUrl') ??
page.url.searchParams.get('redirectAfterAuth')
)
);
Comment on lines 8 to 16

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The redirectUrl derivation manually reads all three params inline, duplicating the logic that readPostAuthRedirectUrl already encapsulates in src/lib/auth-redirect.ts. If a fourth Clerk redirect param is added in the future, this site will need to be updated separately. Importing readPostAuthRedirectUrl from $lib/auth-redirect (the non-server version that accepts URLSearchParams) avoids the duplication.

Suggested change
import { safeRedirectPath } from '$lib/auth-redirect';
const redirectUrl = $derived(
safeRedirectPath(
page.url.searchParams.get('redirect_url') ?? page.url.searchParams.get('redirectUrl')
page.url.searchParams.get('redirect_url') ??
page.url.searchParams.get('redirectUrl') ??
page.url.searchParams.get('redirectAfterAuth')
)
);
import { readPostAuthRedirectUrl } from '$lib/auth-redirect';
const redirectUrl = $derived(readPostAuthRedirectUrl(page.url.searchParams));

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Cursor Fix in Codex

</script>
Expand All @@ -26,8 +29,13 @@
<ClerkLoading>
<AuthWidgetLoading />
</ClerkLoading>
<ClerkLoaded>
<SignIn {redirectUrl} signUpUrl="/sign-up" />
<ClerkLoaded let:clerk>
{#if clerk?.user}
<PostAuthRedirect to={redirectUrl} />
<AuthWidgetLoading />
{:else}
<SignIn {redirectUrl} signUpUrl="/sign-up" />
{/if}
</ClerkLoaded>
</section>
</main>
Loading