From e4c56442e7d2350691577e9ffe1b0f8efa1ba03e Mon Sep 17 00:00:00 2001 From: Benjamin Palko Date: Tue, 28 Jan 2025 08:20:40 -0500 Subject: [PATCH] merge loadUserEnv and validateSession --- src/hooks.server.ts | 4 +- src/lib/server/auth/index.ts | 79 ------------- src/lib/server/middleware/server/index.ts | 2 +- .../server/middleware/server/loadUserEnv.ts | 40 ------- .../middleware/server/validateSession.ts | 105 ++++++++++++++++++ src/routes/+page.server.ts | 3 - src/routes/app/+layout.server.ts | 14 ++- 7 files changed, 120 insertions(+), 127 deletions(-) delete mode 100644 src/lib/server/auth/index.ts delete mode 100644 src/lib/server/middleware/server/loadUserEnv.ts create mode 100644 src/lib/server/middleware/server/validateSession.ts delete mode 100644 src/routes/+page.server.ts diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 5d5908e..41fbe6e 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,5 +1,5 @@ -import { loadUserEnv } from '$lib/server/middleware'; +import { validateSession } from '$lib/server/middleware'; import { sequence } from '@sveltejs/kit/hooks'; import { withClerkHandler } from 'clerk-sveltekit/server'; -export const handle = sequence(withClerkHandler(), loadUserEnv()); +export const handle = sequence(withClerkHandler(), validateSession()); diff --git a/src/lib/server/auth/index.ts b/src/lib/server/auth/index.ts deleted file mode 100644 index 6e60334..0000000 --- a/src/lib/server/auth/index.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { redirect, type ServerLoadEvent } from '@sveltejs/kit'; -import { prisma } from '../prisma'; -import { createClerkClient } from '@clerk/backend'; -import { CLERK_SECRET_KEY } from '$env/static/private'; -import { clerkClient } from 'clerk-sveltekit/server'; -import { logger } from '$lib/server/logger'; - -const clerkSessionClient = createClerkClient({ - secretKey: CLERK_SECRET_KEY, -}); - -export async function validateSession({ locals }: ServerLoadEvent) { - if (!locals.auth.userId || !locals.auth.sessionId) { - return redirect(307, '/login'); - } - - if ((!locals.auth.orgId && locals.auth.sessionId) || !locals.auth.orgId) { - // Sign out the user if they are not associated with an organization - await clerkSessionClient.sessions.revokeSession(locals.auth.sessionId); - return redirect(307, '/login'); - } - - const clerkUser = await clerkClient.users.getUser(locals.auth.userId); - - const tenantClerkId = locals.auth.orgId; - - let tenant = await prisma.tenant.findUnique({ - where: { - clerkOrganizationId: tenantClerkId, - }, - }); - - if (!tenant) { - const organization = await clerkClient.organizations.getOrganization({ - organizationId: tenantClerkId, - }); - - tenant = await prisma.tenant.create({ - data: { - clerkOrganizationId: tenantClerkId, - name: organization.name, - slug: organization.slug ?? `tenant-${tenantClerkId}`, - }, - }); - } - - let user = await prisma.user.findFirst({ - where: { - clerkId: clerkUser.id, - tenantId: tenant.id, - }, - }); - - if (!user) { - if (clerkUser.emailAddresses.length === 0) { - logger.error('User has no email address'); - await clerkSessionClient.sessions.revokeSession(locals.auth.sessionId); - - return redirect(307, '/login'); - } - - user = await prisma.user.create({ - data: { - clerkId: clerkUser.id, - email: clerkUser.emailAddresses[0].emailAddress, - name: clerkUser.fullName ?? '', - tenantId: tenant.id, - }, - }); - - if (clerkUser.fullName === null) { - logger.error('User has no name'); - } - } - - return { - user: { name: user.name, hasImage: clerkUser.hasImage, imageUrl: clerkUser.imageUrl }, - }; -} diff --git a/src/lib/server/middleware/server/index.ts b/src/lib/server/middleware/server/index.ts index 2bcd448..04b65cb 100644 --- a/src/lib/server/middleware/server/index.ts +++ b/src/lib/server/middleware/server/index.ts @@ -1 +1 @@ -export * from './loadUserEnv'; +export * from './validateSession'; diff --git a/src/lib/server/middleware/server/loadUserEnv.ts b/src/lib/server/middleware/server/loadUserEnv.ts deleted file mode 100644 index cf20a81..0000000 --- a/src/lib/server/middleware/server/loadUserEnv.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { logger } from '$lib/server/logger'; -import { prisma } from '$lib/server/prisma'; -import type { Handle } from '@sveltejs/kit'; - -const publicRoutes = ['/login']; - -export function loadUserEnv(): Handle { - return async ({ event, resolve }) => { - if (event.url !== null && publicRoutes.includes(event.url.pathname)) { - return resolve(event); - } - try { - const tenant = await prisma.tenant.findUniqueOrThrow({ - where: { - clerkOrganizationId: event.locals.auth.orgId ?? undefined, - }, - }); - const user = await prisma.user.findUniqueOrThrow({ - where: { - clerkId_tenantId: { clerkId: event.locals.auth.userId!, tenantId: tenant.id }, - }, - }); - - event.locals.tenant = tenant; - event.locals.user = user; - } catch (error) { - if (error instanceof Error) { - logger.error(error); - } - return new Response(null, { - status: 307, - headers: { - location: '/login', - }, - }); - } - - return resolve(event); - }; -} diff --git a/src/lib/server/middleware/server/validateSession.ts b/src/lib/server/middleware/server/validateSession.ts new file mode 100644 index 0000000..8da3238 --- /dev/null +++ b/src/lib/server/middleware/server/validateSession.ts @@ -0,0 +1,105 @@ +import { logger } from '$lib/server/logger'; +import { prisma } from '$lib/server/prisma'; +import { type Handle } from '@sveltejs/kit'; +import { clerkClient } from 'clerk-sveltekit/server'; + +const publicRoutes = ['/login']; + +const loginRedirect = () => + new Response(null, { + status: 307, + headers: { + location: '/login', + }, + }); + +async function findOrCreateTenant(tenantClerkId: string) { + const tenant = await prisma.tenant.findUnique({ + where: { + clerkOrganizationId: tenantClerkId, + }, + }); + + if (tenant) { + return tenant; + } + + const organization = await clerkClient.organizations.getOrganization({ + organizationId: tenantClerkId, + }); + + return await prisma.tenant.create({ + data: { + clerkOrganizationId: tenantClerkId, + name: organization.name, + slug: organization.slug ?? `tenant-${tenantClerkId}`, + }, + }); +} + +export function validateSession(): Handle { + return async ({ event: { locals, url, ...rest }, resolve }) => { + // Public route? LET THEM PASS! + if (url !== null && publicRoutes.includes(url.pathname)) { + return resolve({ locals, url, ...rest }); + } + + // No session, redirect! + if (!locals.auth.sessionId) { + return loginRedirect(); + } + + // No user, revoke session and redirect! + if (!locals.auth.userId) { + await clerkClient.sessions.revokeSession(locals.auth.sessionId); + return loginRedirect(); + } + + // No org, signout and redirect! + if (!locals.auth.orgId) { + await clerkClient.sessions.revokeSession(locals.auth.sessionId); + return loginRedirect(); + } + + // Make sure that a tenant exists for the clerk org + const tenant = await findOrCreateTenant(locals.auth.orgId); + + // Make sure a user exists for the clerk user + const clerkUser = await clerkClient.users.getUser(locals.auth.userId); + + let user = await prisma.user.findFirst({ + where: { + clerkId: clerkUser.id, + tenantId: tenant.id, + }, + }); + + if (!user) { + if (clerkUser.emailAddresses.length === 0) { + logger.error('User has no email address'); + await clerkClient.sessions.revokeSession(locals.auth.sessionId); + + return loginRedirect(); + } + + user = await prisma.user.create({ + data: { + clerkId: clerkUser.id, + email: clerkUser.emailAddresses[0].emailAddress, + name: clerkUser.fullName ?? '', + tenantId: tenant.id, + }, + }); + + if (clerkUser.fullName === null) { + logger.warn(`User {${user.id}} has no name!`); + } + } + + // Load user and tenant into locals + locals.user = user; + locals.tenant = tenant; + + return resolve({ locals, url, ...rest }); + }; +} diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts deleted file mode 100644 index 0f94248..0000000 --- a/src/routes/+page.server.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { validateSession } from '$lib/server/auth'; - -export const load = async (event) => validateSession(event); diff --git a/src/routes/app/+layout.server.ts b/src/routes/app/+layout.server.ts index 0f94248..77f8762 100644 --- a/src/routes/app/+layout.server.ts +++ b/src/routes/app/+layout.server.ts @@ -1,3 +1,13 @@ -import { validateSession } from '$lib/server/auth'; +import { clerkClient } from 'clerk-sveltekit/server'; -export const load = async (event) => validateSession(event); +export const load = async ({ locals }) => { + const clerkUser = await clerkClient.users.getUser(locals.auth.userId!); + + return { + user: { + name: clerkUser.fullName || '', + hasImage: clerkUser.hasImage, + imageUrl: clerkUser.imageUrl, + }, + }; +}; -- 2.45.3