BUG Fix broken login (#77)

merge loadUserEnv and validateSession
This commit is contained in:
Baobeld 2025-02-03 17:02:10 -05:00 committed by GitHub
parent 8270c53509
commit 0d21abd3a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 120 additions and 127 deletions

View file

@ -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());

View file

@ -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 },
};
}

View file

@ -1 +1 @@
export * from './loadUserEnv';
export * from './validateSession';

View file

@ -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);
};
}

View 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 });
};
}

View file

@ -1,3 +0,0 @@
import { validateSession } from '$lib/server/auth';
export const load = async (event) => validateSession(event);

View file

@ -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,
},
};
};