parent
8270c53509
commit
0d21abd3a0
7 changed files with 120 additions and 127 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
import { loadUserEnv } from '$lib/server/middleware';
|
import { validateSession } from '$lib/server/middleware';
|
||||||
import { sequence } from '@sveltejs/kit/hooks';
|
import { sequence } from '@sveltejs/kit/hooks';
|
||||||
import { withClerkHandler } from 'clerk-sveltekit/server';
|
import { withClerkHandler } from 'clerk-sveltekit/server';
|
||||||
|
|
||||||
export const handle = sequence(withClerkHandler(), loadUserEnv());
|
export const handle = sequence(withClerkHandler(), validateSession());
|
||||||
|
|
|
||||||
|
|
@ -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 },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
export * from './loadUserEnv';
|
export * from './validateSession';
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
105
src/lib/server/middleware/server/validateSession.ts
Normal file
105
src/lib/server/middleware/server/validateSession.ts
Normal file
|
|
@ -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 });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
import { validateSession } from '$lib/server/auth';
|
|
||||||
|
|
||||||
export const load = async (event) => validateSession(event);
|
|
||||||
|
|
@ -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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue