diff --git a/.env.example b/.env.example
index 55924c1..59c7535 100644
--- a/.env.example
+++ b/.env.example
@@ -2,4 +2,4 @@ DATABASE_URL=local.db
AUTH_DOMAIN=auth.lab.cazzzer.com
AUTH_CLIENT_ID=
AUTH_CLIENT_SECRET=
-AUTH_REDIRECT_URI=
+AUTH_REDIRECT_URI=http://localhost:5173/auth/authentik/callback
diff --git a/bun.lockb b/bun.lockb
index 15cd4e3..9876da1 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index 114cfb3..8bc45b9 100644
--- a/package.json
+++ b/package.json
@@ -46,6 +46,7 @@
"dependencies": {
"@oslojs/crypto": "^1.0.1",
"@oslojs/encoding": "^1.1.0",
+ "arctic": "^2.2.1",
"better-sqlite3": "^11.1.2",
"drizzle-orm": "^0.33.0",
"lucide-svelte": "^0.454.0"
diff --git a/src/lib/components/app/auth-form/auth-form.svelte b/src/lib/components/app/auth-form/auth-form.svelte
index 1e4b3d3..e851b1a 100644
--- a/src/lib/components/app/auth-form/auth-form.svelte
+++ b/src/lib/components/app/auth-form/auth-form.svelte
@@ -6,8 +6,8 @@
let { class: className, ...rest }: {class: string | undefined | null, rest: { [p: string]: unknown }} = $props();
let isLoading = $state(false);
- async function onSubmit(event: Event) {
- event.preventDefault();
+ async function onSubmit() {
+ // event.preventDefault();
isLoading = true;
setTimeout(() => {
@@ -17,12 +17,14 @@
diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts
index 839907e..8a36ee4 100644
--- a/src/lib/server/auth.ts
+++ b/src/lib/server/auth.ts
@@ -3,6 +3,8 @@ import { sha256 } from '@oslojs/crypto/sha2';
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding';
import { db } from '$lib/server/db';
import * as table from '$lib/server/db/schema';
+import type { RequestEvent } from '@sveltejs/kit';
+import { dev } from '$app/environment';
const DAY_IN_MS = 1000 * 60 * 60 * 24;
@@ -25,6 +27,16 @@ export async function createSession(userId: string): Promise {
return session;
}
+export function setSessionTokenCookie(event: RequestEvent, sessionId: string, expiresAt: Date) {
+ event.cookies.set(sessionCookieName, sessionId, {
+ path: '/',
+ sameSite: 'lax',
+ httpOnly: true,
+ expires: expiresAt,
+ secure: !dev,
+ });
+}
+
export async function invalidateSession(sessionId: string): Promise {
await db.delete(table.session).where(eq(table.session.id, sessionId));
}
@@ -33,7 +45,7 @@ export async function validateSession(sessionId: string) {
const [result] = await db
.select({
// Adjust user table here to tweak returned data
- user: { id: table.user.id, username: table.user.username },
+ user: { id: table.user.id, username: table.user.username, name: table.user.name },
session: table.session
})
.from(table.session)
diff --git a/src/lib/server/oauth.ts b/src/lib/server/oauth.ts
new file mode 100644
index 0000000..9d11186
--- /dev/null
+++ b/src/lib/server/oauth.ts
@@ -0,0 +1,9 @@
+import { Authentik } from 'arctic';
+import * as env from '$env/static/private';
+
+export const authentik = new Authentik(
+ env.AUTH_DOMAIN,
+ env.AUTH_CLIENT_ID,
+ env.AUTH_CLIENT_SECRET,
+ env.AUTH_REDIRECT_URI
+);
diff --git a/src/routes/auth/authentik/+server.ts b/src/routes/auth/authentik/+server.ts
new file mode 100644
index 0000000..5810654
--- /dev/null
+++ b/src/routes/auth/authentik/+server.ts
@@ -0,0 +1,30 @@
+import { generateState, generateCodeVerifier } from "arctic";
+import { authentik } from "$lib/server/oauth";
+
+import type { RequestEvent } from "@sveltejs/kit";
+
+export async function GET(event: RequestEvent): Promise {
+ const state = generateState();
+ const codeVerifier = generateCodeVerifier();
+ const url = authentik.createAuthorizationURL(state, codeVerifier, ["openid", "profile"]);
+
+ event.cookies.set("authentik_oauth_state", state, {
+ path: "/",
+ httpOnly: true,
+ maxAge: 60 * 10, // 10 minutes
+ sameSite: "lax"
+ });
+ event.cookies.set("authentik_code_verifier", codeVerifier, {
+ path: "/",
+ httpOnly: true,
+ maxAge: 60 * 10, // 10 minutes
+ sameSite: "lax"
+ });
+
+ return new Response(null, {
+ status: 302,
+ headers: {
+ Location: url.toString()
+ }
+ });
+}
diff --git a/src/routes/auth/authentik/callback/+server.ts b/src/routes/auth/authentik/callback/+server.ts
new file mode 100644
index 0000000..791e8fa
--- /dev/null
+++ b/src/routes/auth/authentik/callback/+server.ts
@@ -0,0 +1,78 @@
+import { createSession, setSessionTokenCookie } from "$lib/server/auth";
+import { authentik } from "$lib/server/oauth";
+import { decodeIdToken } from "arctic";
+
+import type { RequestEvent } from "@sveltejs/kit";
+import type { OAuth2Tokens } from "arctic";
+import { db } from '$lib/server/db';
+import { eq } from 'drizzle-orm';
+
+import * as table from '$lib/server/db/schema';
+
+export async function GET(event: RequestEvent): Promise {
+ const code = event.url.searchParams.get("code");
+ const state = event.url.searchParams.get("state");
+ const storedState = event.cookies.get("authentik_oauth_state") ?? null;
+ const codeVerifier = event.cookies.get("authentik_code_verifier") ?? null;
+ if (code === null || state === null || storedState === null || codeVerifier === null) {
+ return new Response(null, {
+ status: 400
+ });
+ }
+ if (state !== storedState) {
+ return new Response(null, {
+ status: 400
+ });
+ }
+
+ let tokens: OAuth2Tokens;
+ try {
+ tokens = await authentik.validateAuthorizationCode(code, codeVerifier);
+ } catch (e) {
+ // Invalid code or client credentials
+ return new Response(null, {
+ status: 400
+ });
+ }
+ const claims = decodeIdToken(tokens.idToken());
+ console.log("claims", claims);
+ const userId: string = claims.sub;
+ const username: string = claims.preferred_username;
+
+ const [existingUser] = await db.select().from(table.user).where(eq(table.user.id, userId));
+
+ if (existingUser) {
+ const session = await createSession(existingUser.id);
+ setSessionTokenCookie(event, session.id, session.expiresAt);
+ return new Response(null, {
+ status: 302,
+ headers: {
+ Location: "/"
+ }
+ });
+ }
+
+ const user: table.User = {
+ id: userId,
+ username,
+ name: claims.name as string,
+ };
+
+ try {
+ await db.insert(table.user).values(user);
+ const session = await createSession(user.id);
+ setSessionTokenCookie(event, session.id, session.expiresAt);
+ } catch (e) {
+ console.error('failed to create user', e);
+ return new Response(null, {
+ status: 500
+ });
+ }
+
+ return new Response(null, {
+ status: 302,
+ headers: {
+ Location: "/"
+ }
+ });
+}