sourcebot/packages/web/src/auth.ts
Michael Sukkarieh 75d4189f25
enforce tenancy on search and repo listing endpoints (#181)
* enforce tenancy on search and repo listing

* remove orgId from request schemas
2025-01-28 10:39:59 -08:00

150 lines
3.9 KiB
TypeScript

import 'next-auth/jwt';
import NextAuth, { User as AuthJsUser, DefaultSession } from "next-auth"
import GitHub from "next-auth/providers/github"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { prisma } from "@/prisma";
import type { Provider } from "next-auth/providers"
import { AUTH_GITHUB_CLIENT_ID, AUTH_GITHUB_CLIENT_SECRET, AUTH_SECRET } from "./lib/environment";
import { User } from '@sourcebot/db';
import { notAuthenticated, notFound, unexpectedError } from "@/lib/serviceError";
import { getUser } from "./data/user";
declare module 'next-auth' {
interface Session {
user: {
id: string;
} & DefaultSession['user'];
}
}
declare module 'next-auth/jwt' {
interface JWT {
userId: string
}
}
const providers: Provider[] = [
GitHub({
clientId: AUTH_GITHUB_CLIENT_ID,
clientSecret: AUTH_GITHUB_CLIENT_SECRET,
}),
];
// @see: https://authjs.dev/guides/pages/signin
export const providerMap = providers
.map((provider) => {
if (typeof provider === "function") {
const providerData = provider()
return { id: providerData.id, name: providerData.name }
} else {
return { id: provider.id, name: provider.name }
}
})
.filter((provider) => provider.id !== "credentials");
const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
if (!user.id) {
throw new Error("User ID is required.");
}
const orgName = (() => {
if (user.name) {
return `${user.name}'s Org`;
} else {
return `Default Org`;
}
})();
await prisma.$transaction((async (tx) => {
const org = await tx.org.create({
data: {
name: orgName,
members: {
create: {
role: "OWNER",
user: {
connect: {
id: user.id,
}
}
}
}
}
});
await tx.user.update({
where: {
id: user.id,
},
data: {
activeOrgId: org.id,
}
});
}));
}
export const { handlers, signIn, signOut, auth } = NextAuth({
secret: AUTH_SECRET,
adapter: PrismaAdapter(prisma),
session: {
strategy: "jwt",
},
events: {
createUser: onCreateUser,
},
callbacks: {
async jwt({ token, user: _user }) {
const user = _user as User | undefined;
// @note: `user` will be available on signUp or signIn triggers.
// Cache the userId in the JWT for later use.
if (user) {
token.userId = user.id;
}
return token;
},
async session({ session, token }) {
// @WARNING: Anything stored in the session will be sent over
// to the client.
session.user = {
...session.user,
// Propogate the userId to the session.
id: token.userId,
}
return session;
}
},
providers: providers,
pages: {
signIn: "/login"
}
});
export const getCurrentUserOrg = async () => {
const session = await auth();
if (!session) {
return notAuthenticated();
}
const user = await getUser(session.user.id);
if (!user) {
return unexpectedError("User not found");
}
const orgId = user.activeOrgId;
if (!orgId) {
return unexpectedError("User has no active org");
}
const membership = await prisma.userToOrg.findUnique({
where: {
orgId_userId: {
userId: session.user.id,
orgId,
}
},
});
if (!membership) {
return notFound();
}
return orgId;
}