diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..244db184 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +.DS_Store +Thumbs.db + +node_modules +/.svelte-kit +/build +/.output + +.env +.env.* +!.env.example +!.env.test + +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/src/lib/sso-store.ts b/src/lib/sso-store.ts new file mode 100644 index 00000000..50a3122c --- /dev/null +++ b/src/lib/sso-store.ts @@ -0,0 +1,32 @@ +/** + * In-memory store for one-time SSO tokens. + * Token → { auth, expiresAt }. Exchanged once then deleted. + */ +const TTL_MS = 2 * 60 * 1000; // 2 minutes + +type AuthPayload = { token: string; model: Record | null }; + +const store = new Map(); + +function prune(): void { + const now = Date.now(); + for (const [key, entry] of store.entries()) { + if (entry.expiresAt <= now) store.delete(key); + } +} + +export function set(token: string, auth: AuthPayload): void { + prune(); + store.set(token, { auth, expiresAt: Date.now() + TTL_MS }); +} + +export function get(token: string): AuthPayload | null { + const entry = store.get(token); + if (!entry) return null; + if (entry.expiresAt <= Date.now()) { + store.delete(token); + return null; + } + store.delete(token); // one-time use + return entry.auth; +} diff --git a/src/routes/[site]/+page.server.ts b/src/routes/[site]/+page.server.ts index 12f93d7b..5cf82333 100644 --- a/src/routes/[site]/+page.server.ts +++ b/src/routes/[site]/+page.server.ts @@ -1,7 +1,9 @@ import { sites, type SiteSlug } from '$lib/sites'; import { error } from '@sveltejs/kit'; +import * as ssoStore from '$lib/sso-store'; +import { randomUUID } from 'crypto'; -export function load({ params }) { +export function load({ params, locals }) { const slug = params.site?.toLowerCase() as SiteSlug | undefined; const site = slug && slug in sites ? sites[slug as SiteSlug] : null; @@ -9,9 +11,22 @@ export function load({ params }) { throw error(404, `Unknown site: ${params.site}`); } + let ssoToken: string | undefined; + if (locals.user && locals.pb?.authStore?.token) { + try { + const token = locals.pb.authStore.token; + const model = locals.pb.authStore.record as Record | null; + ssoToken = randomUUID(); + ssoStore.set(ssoToken, { token, model }); + } catch { + // no sso if export fails + } + } + return { slug: slug as SiteSlug, url: site.url, - title: site.title + title: site.title, + ssoToken }; } diff --git a/src/routes/[site]/+page.svelte b/src/routes/[site]/+page.svelte index bf6e0856..c71926f9 100644 --- a/src/routes/[site]/+page.svelte +++ b/src/routes/[site]/+page.svelte @@ -2,8 +2,14 @@ import { Button } from '$lib/components/ui/button'; let { data }: { - data: { slug: string; url: string; title: string }; + data: { slug: string; url: string; title: string; ssoToken?: string }; } = $props(); + + const iframeSrc = $derived( + data.ssoToken + ? `${data.url}${data.url.includes('?') ? '&' : '?'}sso=${data.ssoToken}` + : data.url + ); @@ -14,7 +20,7 @@