aboutsummaryrefslogtreecommitdiffstats
path: root/src/routes
diff options
context:
space:
mode:
Diffstat (limited to 'src/routes')
-rw-r--r--src/routes/+error.svelte6
-rw-r--r--src/routes/+layout.server.ts5
-rw-r--r--src/routes/+layout.svelte23
-rw-r--r--src/routes/+page.svelte26
-rw-r--r--src/routes/login/+server.ts55
-rw-r--r--src/routes/login/callback/+server.ts80
6 files changed, 195 insertions, 0 deletions
diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte
new file mode 100644
index 0000000..a19e467
--- /dev/null
+++ b/src/routes/+error.svelte
@@ -0,0 +1,6 @@
+<script lang="ts">
+ import { page } from '$app/state';
+</script>
+
+<h2 class="text-2xl">HTTP {page.status}</h2>
+<p>{page.error?.message}</p>
diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts
new file mode 100644
index 0000000..afdac71
--- /dev/null
+++ b/src/routes/+layout.server.ts
@@ -0,0 +1,5 @@
+export const load = async ({ locals }) => {
+ return {
+ // session: await locals.auth(),
+ };
+};
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
new file mode 100644
index 0000000..1980c3a
--- /dev/null
+++ b/src/routes/+layout.svelte
@@ -0,0 +1,23 @@
+<script lang="ts">
+ import '../app.css';
+ import favicon from '$lib/assets/favicon.svg';
+ import { page } from '$app/state';
+
+ let { children } = $props();
+</script>
+
+<svelte:head>
+ <link rel="icon" href={favicon} />
+</svelte:head>
+
+<nav class="header">
+ <h1 class="text-4xl">crunched</h1>
+ <p>
+ <a href="/">home</a> - {#if page.data.session}<a href="/vms">vms</a
+ >{:else}<a href="/login?scope=profile%20vm-own-read">login</a>{/if}
+ </p>
+ <div class="my-2">
+ <hr />
+ </div>
+</nav>
+{@render children?.()}
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
new file mode 100644
index 0000000..2634622
--- /dev/null
+++ b/src/routes/+page.svelte
@@ -0,0 +1,26 @@
+<script>
+ import { page } from '$app/state';
+ let s = $derived(page.data.session);
+</script>
+
+{@debug s}
+
+<h1>SvelteKit Auth Example</h1>
+<div>
+ {#if page.data.session}
+ {#if page.data.session.user?.image}
+ <img
+ src={page.data.session.user.image}
+ class="avatar"
+ alt="User Avatar"
+ />
+ {/if}
+ <span>
+ <small>Signed in as</small><br />
+ <strong>{page.data.session.user?.name ?? 'User'}</strong>
+ </span>
+ <!-- <div slot="submitButton" class="buttonPrimary">Sign out</div> -->
+ {:else}
+ <span class="notSignedInText">You are not signed in</span>
+ {/if}
+</div>
diff --git a/src/routes/login/+server.ts b/src/routes/login/+server.ts
new file mode 100644
index 0000000..4a032d4
--- /dev/null
+++ b/src/routes/login/+server.ts
@@ -0,0 +1,55 @@
+import { getAuthorizeUrl } from '$lib/auth.server.js';
+import { error, redirect } from '@sveltejs/kit';
+
+export const GET = async (event) => {
+ let target = event.url.searchParams.get('next') ?? '/';
+ let desiredScopes =
+ event.url.searchParams.get('scope') ?? 'profile vm-own-read';
+ if (new URL(target, event.url.href).host !== event.url.host) target = '/';
+ const existingScopes = (event.cookies.get('oid__scopes') ?? '').split(' ');
+ const authed = await event.locals.auth();
+ const missingScopes = !!desiredScopes
+ .split(' ')
+ .find((v) => !existingScopes.includes(v));
+ if (
+ // if we're not authenticated
+ !authed ||
+ // or we're missing scopes
+ missingScopes
+ ) {
+ const { nonce, redirectTo } = await getAuthorizeUrl(
+ event.url.href,
+ desiredScopes.split(' ')
+ );
+ if (nonce) {
+ let existingNonces = [];
+ try {
+ const n = JSON.parse(event.cookies.get('pending-auth-nonces') ?? '[]');
+ if (Array.isArray(n) && n.length && typeof n[0] === 'string')
+ existingNonces = n;
+ } catch (error) {
+ // revoke all existing nonces
+ }
+ event.cookies.set(
+ 'pending-auth-nonces',
+ JSON.stringify([...existingNonces, nonce]),
+ {
+ path: '/',
+ httpOnly: true,
+ secure: true,
+ sameSite: true,
+ }
+ );
+ } else
+ event.cookies.delete('pending-auth-nonces', {
+ path: '/',
+ });
+ event.cookies.delete('next', {
+ path: target,
+ });
+ throw redirect(303, redirectTo);
+ } else {
+ throw redirect(303, target);
+ }
+};
+export const POST = GET;
diff --git a/src/routes/login/callback/+server.ts b/src/routes/login/callback/+server.ts
new file mode 100644
index 0000000..32b1647
--- /dev/null
+++ b/src/routes/login/callback/+server.ts
@@ -0,0 +1,80 @@
+import * as auth from '$lib/auth.server.js';
+import { error, json, redirect } from '@sveltejs/kit';
+import * as client from 'openid-client';
+
+// Pre-checker for nonce, not the primary implementation
+const handleNonce = (nonce: string | null, nonceCookie: string | undefined) => {
+ if (nonce) {
+ try {
+ const n = JSON.parse(nonceCookie ?? '[]');
+ if (Array.isArray(n) && n.length && typeof n[0] === 'string') {
+ if (!n.includes(nonce)) throw error(400, 'Nonce not in array');
+ else return n.filter((v) => v !== nonce);
+ } else throw error(400, 'Nonce provided, but nonce cookie not found');
+ } catch (e) {
+ throw error(400, `Failed parsing nonce: ${e}`);
+ }
+ } else if (nonceCookie) throw error(400, 'Missing Nonce');
+};
+export const GET = async (event) => {
+ const sp = event.url.searchParams;
+ const params = {
+ sessionState: sp.get('session_state'),
+ iss: sp.get('iss'),
+ code: sp.get('code'),
+ nonce: sp.get('nonce'),
+ };
+ if (!params.sessionState || !params.iss || !params.code)
+ throw error(400, 'Missing one of session_state, iss, code');
+
+ const remainingNonces = handleNonce(
+ params.nonce,
+ event.cookies.get('pending-auth-nonces')
+ );
+
+ try {
+ const tk = await auth.authorizeNewSession(
+ new URL(event.url.href),
+ params.nonce ?? undefined
+ );
+
+ for (const [k, v] of Object.entries({
+ oid__access_token: tk.access_token,
+ oid__token_type: tk.token_type,
+ oid__expires_at: '' + (Date.now() + (tk.expiresIn() ?? 0) * 1000),
+ oid__refresh_token: tk.refresh_token,
+ oid__sub: tk.claims()!.sub,
+ 'pending-auth-nonces': JSON.stringify(remainingNonces),
+ }))
+ if (v)
+ event.cookies.set(k, v, {
+ path: '/',
+ secure: true,
+ httpOnly: true,
+ sameSite: true,
+ });
+ if (tk.scope)
+ event.cookies.set('oid__scopes', tk.scope, {
+ path: '/',
+ secure: true,
+ httpOnly: true,
+ sameSite: true,
+ });
+
+ console.warn(
+ 'New Session:',
+ await client.fetchUserInfo(
+ await auth.getConfig(),
+ tk.access_token,
+ tk.claims()!.sub
+ )
+ );
+
+ return json({
+ sub: tk.claims()!.sub,
+ at: tk.access_token,
+ });
+ } catch (e) {
+ throw redirect(307, '/login');
+ }
+};