diff --git a/.env b/.env index ac58967..40c3228 100644 --- a/.env +++ b/.env @@ -1,2 +1,4 @@ VITE_APP_VERSION=1.0.0-alpha DATABASE_URL="postgres://hestia:test123@localhost:5432/hestia" +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 a50918c..8d79727 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index e026e0e..29c7074 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "type": "module", "scripts": { - "dev": "bun database:up && bun prisma:push && vite dev", + "dev": "bun database:up && bun prisma:generate && bun prisma:push && 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", @@ -62,17 +62,19 @@ "vitest": "^2.0.4" }, "dependencies": { + "@clerk/clerk-js": "^5.43.4", + "@clerk/express": "^1.3.31", + "@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", "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", diff --git a/prisma/migrations/20250101171120_update_user_table/migration.sql b/prisma/migrations/20250101171120_update_user_table/migration.sql new file mode 100644 index 0000000..1f6c706 --- /dev/null +++ b/prisma/migrations/20250101171120_update_user_table/migration.sql @@ -0,0 +1,18 @@ +/* + 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. + +*/ +-- DropForeignKey +ALTER TABLE "Session" DROP CONSTRAINT "Session_userId_fkey"; + +-- DropIndex +DROP INDEX "User_email_key"; + +-- AlterTable +ALTER TABLE "User" DROP COLUMN "password"; + +-- DropTable +DROP TABLE "Session"; diff --git a/prisma/migrations/20250101171527_add_clerk_id_to_user_table/migration.sql b/prisma/migrations/20250101171527_add_clerk_id_to_user_table/migration.sql new file mode 100644 index 0000000..2c52947 --- /dev/null +++ b/prisma/migrations/20250101171527_add_clerk_id_to_user_table/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - Added the required column `clerkId` to the `User` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "clerkId" TEXT NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a23c87a..d23b90b 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 - 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 7cce717..335032f 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -4,8 +4,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 4b989d8..cdd5b65 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..0a52b31 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,32 @@ }; 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; + } + + 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 e703154..0000000 --- a/src/routes/login/+page.server.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { messages } from '$lib/i18n'; -import { email_inuse } from '$lib/paraglide/messages'; -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.email_required() } }); - } - - const password = form.get('password') as string; - if (!password) { - return fail(400, { password: { error: messages.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.user_not_found() } }); - } - - const validPassword = await new Argon2id().verify(user.password, password); - if (!validPassword) { - return fail(400, { - password: { error: messages.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.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.email_incorrect() } }); - } - - const password = form.get('password'); - if (typeof password !== 'string') { - return fail(400, { password: { error: messages.password_required() } }); - } - const name = form.get('name'); - if (typeof name !== 'string') { - return fail(400, { name: { error: messages.name_required() } }); - } - - const usersWithEmail = await prisma.user.count({ where: { email: email } }); - if (usersWithEmail !== 0) { - return fail(409, { email: { value: email, 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 4978eaf..c73f28d 100644 --- a/src/routes/login/+page.svelte +++ b/src/routes/login/+page.svelte @@ -1,62 +1,23 @@ -{#snippet userIcon()} - -{/snippet} - -{#snippet passwordIcon()} - -{/snippet} - -{#snippet nameIcon()} - -{/snippet} - -{#snippet alert(message: string)} - -{/snippet} - -{#snippet Form(variant: 'login' | 'register')} -
-
- - - {#if variant === 'register'} - - {/if} -
-
-
-
-{/snippet} -
-
-
- {#if form} - {@render alert(Object.values(form)[0].error)} - {/if} - -
- {@render Form(tab === 0 ? 'login' : 'register')} +
+
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..4288c3f --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1,10 @@ +/// + +interface ImportMetaEnv { + readonly VITE_CLERK_PUBLISHABLE_KEY: string + // more env variables... +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} \ No newline at end of file