diff --git a/libs/api/src/trpc.ts b/libs/api/src/trpc.ts new file mode 100644 index 00000000..690e4e79 --- /dev/null +++ b/libs/api/src/trpc.ts @@ -0,0 +1,49 @@ +import { TRPCError, initTRPC } from '@trpc/server' +import superjson from 'superjson' +import { ZodError } from 'zod' + +import { auth } from '@cuhacking/auth' +import { db } from '@cuhacking/db' + +export async function createTRPCContext(opts: { headers: Headers }) { + const { session, user } = await auth() + + return { + db, + session, + user, + ...opts, + } +} + +const t = initTRPC.context().create({ + transformer: superjson, + errorFormatter({ shape, error }) { + return { + ...shape, + data: { + ...shape.data, + zodError: + error.cause instanceof ZodError ? error.cause.flatten() : null, + }, + } + }, +}) +export const createCallerFactory = t.createCallerFactory + +export const createRouter = t.router + +export const publicProcedure = t.procedure + +export const protectedProcedure = t.procedure.use(({ ctx, next }) => { + if (!ctx.session || !ctx.user) { + throw new TRPCError({ code: 'UNAUTHORIZED' }) + } + + return next({ + ctx: { + session: { ...ctx.session }, + user: { ...ctx.user }, + }, + }) +}) diff --git a/libs/auth/src/actions/logout.ts b/libs/auth/src/actions/logout.ts new file mode 100644 index 00000000..0a173813 --- /dev/null +++ b/libs/auth/src/actions/logout.ts @@ -0,0 +1,32 @@ +'use server' + +import { cookies } from 'next/headers' +import { redirect } from 'next/navigation' + +import { auth } from '../auth' +import { lucia } from '../lucia' + +/** + * This function logs out the user. + * @returns The response. + */ +export async function logout() { + const { session } = await auth() + if (!session) { + return { + error: 'Unauthorized', + } + } + + await lucia.invalidateSession(session.id) + + const sessionCookie = lucia.createBlankSessionCookie() + + cookies().set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes, + ) + + return redirect('/login') +} diff --git a/libs/auth/src/auth.ts b/libs/auth/src/auth.ts new file mode 100644 index 00000000..8561ae96 --- /dev/null +++ b/libs/auth/src/auth.ts @@ -0,0 +1,40 @@ +import { cache } from 'react' +import { cookies } from 'next/headers' + +import type { Session, User } from 'lucia' + +import { lucia } from './lucia' + +export async function uncachedAuth(): Promise< + { user: User, session: Session } | { user: null, session: null } +> { + const sessionId = cookies().get(lucia.sessionCookieName)?.value ?? null + if (!sessionId) { + return { user: null, session: null } + } + const result = await lucia.validateSession(sessionId) + try { + if (result.session?.fresh) { + const sessionCookie = lucia.createSessionCookie(result.session.id) + cookies().set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes, + ) + } + if (!result.session) { + const sessionCookie = lucia.createBlankSessionCookie() + cookies().set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes, + ) + } + } + catch { + console.error('Failed to set session cookie') + } + return result +} + +export const auth = cache(uncachedAuth) diff --git a/libs/auth/src/index.ts b/libs/auth/src/index.ts index 6f06caaa..6fe12b00 100644 --- a/libs/auth/src/index.ts +++ b/libs/auth/src/index.ts @@ -1 +1,2 @@ -export * from './lib/auth' +export * from './lucia' +export * from './auth' diff --git a/libs/auth/src/lucia.ts b/libs/auth/src/lucia.ts new file mode 100644 index 00000000..7c289637 --- /dev/null +++ b/libs/auth/src/lucia.ts @@ -0,0 +1,33 @@ +import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle' +import { Lucia } from 'lucia' + +import { db, session, user } from '@cuhacking/db' +import { envWebsiteServer } from '@cuhacking/env' + +const adapter = new DrizzlePostgreSQLAdapter(db, session, user) + +export const lucia = new Lucia(adapter, { + sessionCookie: { + attributes: { + secure: envWebsiteServer.NODE_ENV === 'production', + }, + }, + getUserAttributes: (attributes: any) => { + return { + name: attributes.name, + email: attributes.email, + avatarUrl: attributes.avatarUrl, + } + }, +}) + +declare module 'lucia' { + interface Register { + Lucia: typeof lucia + DatabaseUserAttributes: { + name?: string + email: string + avatarUrl?: string + } + } +}