diff --git a/.env b/.env
index a706777..617c806 100644
--- a/.env
+++ b/.env
@@ -4,4 +4,8 @@ TWILIO_AUTH_TOKEN=
TWILIO_PHONE_NUMBER=
# PRISMA
-DATABASE_URL="postgres://hestia:test123@localhost:5432/hestia"
\ No newline at end of file
+DATABASE_URL="postgres://hestia:test123@localhost:5432/hestia"
+
+# CLERK
+PUBLIC_CLERK_PUBLISHABLE_KEY=secret_do_not_commit_or_change_this_create_.env.local_instead
+CLERK_SECRET_KEY=secret_do_not_commit_or_change_this_create_.env.local_instead
diff --git a/.gitignore b/.gitignore
index 8c331d8..66c56cc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ node_modules
# OS
.DS_Store
Thumbs.db
+.idea
# Vite
vite.config.js.timestamp-*
diff --git a/bun.lockb b/bun.lockb
index 4d06590..509b39c 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index 307fe62..b9dbb60 100644
--- a/package.json
+++ b/package.json
@@ -3,10 +3,10 @@
"version": "0.0.1",
"type": "module",
"scripts": {
- "dev": "bun validate-env && bun database:up && bun prisma:push && vite dev",
+ "dev": "bun validate-env && bun database:up && bun prisma:generate && vite dev",
"build": "vite build",
"build-storybook": "storybook build",
- "database:up": "docker compose -p hestia -f devops/docker-compose.dev.yml up -d && docker compose -p hestia -f devops/docker-compose.dev.yml -f devops/docker-compose.wait.yml run --rm wait -c hestia-database:5432",
+ "database:up": "docker compose -p hestia -f devops/docker-compose.dev.yml up -d && docker compose -p hestia -f devops/docker-compose.dev.yml -f devops/docker-compose.wait.yml run --rm wait -c hestia-database:5432 && bun prisma:push",
"database:down": "docker compose -p hestia -f devops/docker-compose.dev.yml down",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
@@ -65,19 +65,19 @@
"vitest": "^2.0.4"
},
"dependencies": {
+ "@clerk/backend": "1.21.4",
+ "@clerk/themes": "^2.2.3",
"@flaticon/flaticon-uicons": "^3.3.1",
"@inlang/paraglide-sveltekit": "^0.15.0",
- "@lucia-auth/adapter-prisma": "^4.0.1",
"@pothos/core": "^4.3.0",
"@pothos/plugin-prisma": "^4.4.0",
"@prisma/client": "6.0.1",
"@tailwindcss/typography": "^0.5.15",
+ "clerk-sveltekit": "https://pkg.pr.new/wobsoriano/clerk-sveltekit@ca15d4e",
"dayjs": "^1.11.13",
"eslint_d": "^14.3.0",
"graphql": "^16.9.0",
"graphql-yoga": "^5.10.4",
- "lucia": "^3.2.2",
- "oslo": "^1.2.1",
"pino": "^9.5.0",
"pino-pretty": "^13.0.0",
"tailwind-merge": "^2.5.5",
diff --git a/prisma/migrations/20250105021234_add_clerk_auth/migration.sql b/prisma/migrations/20250105021234_add_clerk_auth/migration.sql
new file mode 100644
index 0000000..a37b472
--- /dev/null
+++ b/prisma/migrations/20250105021234_add_clerk_auth/migration.sql
@@ -0,0 +1,24 @@
+/*
+ Warnings:
+
+ - You are about to drop the column `password` on the `User` table. All the data in the column will be lost.
+ - You are about to drop the `Session` table. If the table is not empty, all the data it contains will be lost.
+ - A unique constraint covering the columns `[clerkId]` on the table `User` will be added. If there are existing duplicate values, this will fail.
+ - Added the required column `clerkId` to the `User` table without a default value. This is not possible if the table is not empty.
+
+*/
+-- DropForeignKey
+ALTER TABLE "Session" DROP CONSTRAINT "Session_userId_fkey";
+
+-- DropIndex
+DROP INDEX "User_email_key";
+
+-- AlterTable
+ALTER TABLE "User" DROP COLUMN "password",
+ADD COLUMN "clerkId" TEXT NOT NULL;
+
+-- DropTable
+DROP TABLE "Session";
+
+-- CreateIndex
+CREATE UNIQUE INDEX "User_clerkId_key" ON "User"("clerkId");
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index a23c87a..090ea2e 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -15,24 +15,12 @@ datasource db {
}
model User {
- id String @id @default(uuid())
+ id String @id @default(uuid())
+ clerkId String @unique
- email String? @unique
- name String
- password String
- posts Post[]
- sessions Session[]
-
- createdAt DateTime @default(now())
- updatedAt DateTime @default(now()) @updatedAt
-}
-
-model Session {
- id String @id @default(uuid())
-
- expiresAt DateTime
- user User @relation(references: [id], fields: [userId])
- userId String
+ email String?
+ name String
+ posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
diff --git a/src/app.d.ts b/src/app.d.ts
index c8414b8..086138d 100644
--- a/src/app.d.ts
+++ b/src/app.d.ts
@@ -5,8 +5,11 @@ declare global {
namespace App {
// interface Error {}
interface Locals {
- user: import('lucia').User | null;
- session: import('lucia').Session | null;
+ auth: {
+ userId?: string;
+ orgId?: string | null;
+ sessionId?: string;
+ };
}
// interface PageData {}
// interface PageState {}
diff --git a/src/hooks.server.ts b/src/hooks.server.ts
new file mode 100644
index 0000000..e182571
--- /dev/null
+++ b/src/hooks.server.ts
@@ -0,0 +1,3 @@
+import { withClerkHandler } from 'clerk-sveltekit/server';
+
+export const handle = withClerkHandler();
diff --git a/src/lib/components/Navigation/Navbar/Navbar.svelte b/src/lib/components/Navigation/Navbar/Navbar.svelte
index bc396ca..ce41227 100644
--- a/src/lib/components/Navigation/Navbar/Navbar.svelte
+++ b/src/lib/components/Navigation/Navbar/Navbar.svelte
@@ -1,5 +1,6 @@
- {@render children()}
+
+ {@render children()}
+
diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts
index 5d81608..0f94248 100644
--- a/src/routes/+page.server.ts
+++ b/src/routes/+page.server.ts
@@ -1,5 +1,3 @@
import { validateSession } from '$lib/server/auth';
-export async function load(event) {
- await validateSession(event);
-}
+export const load = async (event) => validateSession(event);
diff --git a/src/routes/app/+layout.server.ts b/src/routes/app/+layout.server.ts
index 894e1f4..0f94248 100644
--- a/src/routes/app/+layout.server.ts
+++ b/src/routes/app/+layout.server.ts
@@ -1,10 +1,3 @@
import { validateSession } from '$lib/server/auth';
-export async function load(event) {
- const {
- user: { password: _, ...rest },
- } = await validateSession(event);
- return {
- user: rest,
- };
-}
+export const load = async (event) => validateSession(event);
diff --git a/src/routes/app/+layout.svelte b/src/routes/app/+layout.svelte
index 94df874..a01f36b 100644
--- a/src/routes/app/+layout.svelte
+++ b/src/routes/app/+layout.svelte
@@ -2,6 +2,8 @@
import { Navbar } from '$lib/components/Navigation';
import type { User } from '@prisma/client';
import type { Snippet } from 'svelte';
+ import { onMount } from 'svelte';
+ import 'clerk-sveltekit/client';
type Props = {
children: Snippet;
@@ -11,6 +13,35 @@
};
let { children, data }: Props = $props();
+
+ let clerk;
+
+ onMount(async () => {
+ clerk = window.Clerk;
+
+ if (clerk) {
+ if (!clerk.user || clerk.user?.organizationMemberships.length === 0) {
+ console.debug('No organizations found');
+ await clerk.signOut();
+ return;
+ }
+
+ /**
+ * Set the active organization to the first one in the list, this is temporary until we let the user choose the organization
+ */
+ try {
+ // Set the active organization to the first one in the list
+ await clerk.setActive({
+ organization: clerk.user?.organizationMemberships[0].organization.id,
+ });
+ console.debug(
+ `Active organization set to ${clerk.user?.organizationMemberships[0].organization.id}`
+ );
+ } catch (error) {
+ console.error('Error setting active organization:', error);
+ }
+ }
+ });
diff --git a/src/routes/login/+page.server.ts b/src/routes/login/+page.server.ts
deleted file mode 100644
index 620225f..0000000
--- a/src/routes/login/+page.server.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { messages } from '$lib/i18n';
-import { logger } from '$lib/server/logger';
-import { auth } from '$lib/server/lucia.js';
-import { prisma } from '$lib/server/prisma';
-import { fail, redirect, type Actions } from '@sveltejs/kit';
-import { Argon2id } from 'oslo/password';
-import { string } from 'zod';
-
-export const actions = {
- login: async (event) => {
- const form = await event.request.formData();
- const email = form.get('email');
- if (typeof email !== 'string') {
- return fail(400, { email: { error: messages.login_error_email_required() } });
- }
-
- const password = form.get('password') as string;
- if (!password) {
- return fail(400, { password: { error: messages.login_error_password_required() } });
- }
-
- const user = await prisma.user.findUnique({
- where: {
- email: email,
- },
- });
- if (!user) {
- logger.error(`User not found! ${email}`);
- return fail(404, {
- email: { value: email, error: messages.login_error_user_not_found() },
- });
- }
-
- const validPassword = await new Argon2id().verify(user.password, password);
- if (!validPassword) {
- return fail(400, {
- password: { error: messages.login_error_password_incorrect() },
- });
- }
- const session = await auth.createSession(user.id, []);
- const sessionCookie = auth.createSessionCookie(session.id);
- event.cookies.set(sessionCookie.name, sessionCookie.value, {
- path: '/',
- maxAge: 120,
- });
- redirect(302, '/');
- },
-
- register: async (event) => {
- const form = await event.request.formData();
- if (!form.has('email')) {
- return fail(400, { email: { error: messages.login_error_email_required() } });
- }
- const { success, data: email, error } = string().email().safeParse(form.get('email'));
- if (!success) {
- logger.error(error);
- return fail(400, {
- email: { value: email, error: messages.login_error_email_incorrect() },
- });
- }
-
- const password = form.get('password');
- if (typeof password !== 'string') {
- return fail(400, { password: { error: messages.login_error_password_required() } });
- }
- const name = form.get('name');
- if (typeof name !== 'string') {
- return fail(400, { name: { error: messages.login_error_name_required() } });
- }
-
- const usersWithEmail = await prisma.user.count({ where: { email: email } });
- if (usersWithEmail !== 0) {
- return fail(409, {
- email: { value: email, error: messages.login_error_email_inuse() },
- });
- }
-
- const hashedPassword = await new Argon2id().hash(password);
- const user = await prisma.user.create({
- data: {
- email: form.get('email') as string,
- name: form.get('name') as string,
- password: hashedPassword,
- },
- });
-
- const session = await auth.createSession(user.id.toString(), {});
- const sessionCookie = auth.createSessionCookie(session.id);
- event.cookies.set(sessionCookie.name, sessionCookie.value, {
- path: '/',
- maxAge: 120,
- });
-
- redirect(303, '/');
- },
-} satisfies Actions;
diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte
index 8c8ee0c..c73f28d 100644
--- a/src/routes/login/+page.svelte
+++ b/src/routes/login/+page.svelte
@@ -1,77 +1,23 @@
-{#snippet userIcon()}
-
-{/snippet}
-
-{#snippet passwordIcon()}
-
-{/snippet}
-
-{#snippet nameIcon()}
-
-{/snippet}
-
-{#snippet alert(message: string)}
-
-
- {message}
-
-{/snippet}
-
-{#snippet Form(variant: 'login' | 'register')}
-
-{/snippet}
-
-
-
- {#if form}
- {@render alert(Object.values(form)[0].error)}
- {/if}
-
-
- {@render Form(tab === 0 ? 'login' : 'register')}
+
+