diff --git a/AGENT_SETUP.md b/AGENT_SETUP.md index 30c47b8..19280fe 100644 --- a/AGENT_SETUP.md +++ b/AGENT_SETUP.md @@ -828,7 +828,7 @@ import { permissions } from '../settings'; // Validate collection format and check against allowed list from settings const collectionSchema = v.pipe( v.string(), - v.regex(/^[a-zA-Z][a-zA-Z0-9-]*\.[a-zA-Z][a-zA-Z0-9-]*\.[a-zA-Z][a-zA-Z0-9-]*$/), + v.regex(/^[a-zA-Z][a-zA-Z0-9-]*(\.[a-zA-Z][a-zA-Z0-9-]*){2,}$/), v.check( (c) => permissions.collections.some((allowed) => c === allowed || allowed.startsWith(c + '?')), 'Collection not in allowed list' diff --git a/src/lib/atproto/auth.svelte.ts b/src/lib/atproto/auth.svelte.ts index 58de137..c5a0db8 100644 --- a/src/lib/atproto/auth.svelte.ts +++ b/src/lib/atproto/auth.svelte.ts @@ -1,6 +1,7 @@ import { AppBskyActorDefs } from '@atcute/bluesky'; import type { ActorIdentifier, Did } from '@atcute/lexicons'; import { page } from '$app/state'; +import { REDIRECT_TO_LAST_PAGE_ON_LOGIN } from './settings'; export const user = { get profile() { @@ -29,6 +30,9 @@ export async function login(handle: string) { const { oauthLogin } = await import('./server/oauth.remote'); const { url } = await oauthLogin({ handle }); + if (REDIRECT_TO_LAST_PAGE_ON_LOGIN) { + document.cookie = `oauth_return_to=${encodeURIComponent(window.location.pathname + window.location.search)};path=/;max-age=600;samesite=lax`; + } window.location.assign(url); // Wait for navigation (prevents UI flash) @@ -42,6 +46,9 @@ export async function login(handle: string) { export async function signup() { const { oauthLogin } = await import('./server/oauth.remote'); const { url } = await oauthLogin({ signup: true }); + if (REDIRECT_TO_LAST_PAGE_ON_LOGIN) { + document.cookie = `oauth_return_to=${encodeURIComponent(window.location.pathname + window.location.search)};path=/;max-age=600;samesite=lax`; + } window.location.assign(url); await new Promise((_resolve, reject) => { diff --git a/src/lib/atproto/server/repo.remote.ts b/src/lib/atproto/server/repo.remote.ts index 6af60d8..3ee8d23 100644 --- a/src/lib/atproto/server/repo.remote.ts +++ b/src/lib/atproto/server/repo.remote.ts @@ -6,7 +6,7 @@ import { permissions } from '../settings'; // Validate collection format and check against allowed list from settings const collectionSchema = v.pipe( v.string(), - v.regex(/^[a-zA-Z][a-zA-Z0-9-]*\.[a-zA-Z][a-zA-Z0-9-]*\.[a-zA-Z][a-zA-Z0-9-]*$/), + v.regex(/^[a-zA-Z][a-zA-Z0-9-]*(\.[a-zA-Z][a-zA-Z0-9-]*){2,}$/), v.check( (c) => permissions.collections.some((allowed) => c === allowed || allowed.startsWith(c + '?')), 'Collection not in allowed list' diff --git a/src/lib/atproto/settings.ts b/src/lib/atproto/settings.ts index 5ad4549..d7dd3bd 100644 --- a/src/lib/atproto/settings.ts +++ b/src/lib/atproto/settings.ts @@ -30,4 +30,7 @@ export const signUpPDS = dev ? devPDS : prodPDS; // where to redirect after oauth login/signup export const REDIRECT_PATH = '/oauth/callback'; +// redirect the user back to the page they were on before login +export const REDIRECT_TO_LAST_PAGE_ON_LOGIN = true; + export const DOH_RESOLVER = 'https://mozilla.cloudflare-dns.com/dns-query'; diff --git a/src/routes/(oauth)/oauth/callback/+server.ts b/src/routes/(oauth)/oauth/callback/+server.ts index d55f1a9..e2e64d6 100644 --- a/src/routes/(oauth)/oauth/callback/+server.ts +++ b/src/routes/(oauth)/oauth/callback/+server.ts @@ -24,5 +24,14 @@ export const GET: RequestHandler = async ({ url, platform, cookies }) => { redirect(303, '/?error=auth_failed'); } + const returnTo = cookies.get('oauth_return_to'); + if (returnTo) { + cookies.delete('oauth_return_to', { path: '/' }); + const decoded = decodeURIComponent(returnTo); + if (decoded.startsWith('/') && !decoded.startsWith('//')) { + redirect(303, decoded); + } + } + redirect(303, '/'); };