dow-prod-tracker/src/lib/auth.ts
Leivur Djurhuus 0eaf809bc6 Add SSO bridge: Microsoft Entra ID auth with seed user linking
Configure Microsoft Entra ID as the sole SSO provider with
allowDangerousEmailAccountLinking to link SSO accounts to existing
seeded user records by email match. Add signIn event for automatic
org assignment by domain. Guard DEV_BYPASS_AUTH against production
use. Add branded pending page for authenticated users without org
membership. Remove Google provider for initial rollout simplicity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 14:52:13 -05:00

69 lines
2 KiB
TypeScript

import NextAuth from "next-auth";
import MicrosoftEntraID from "next-auth/providers/microsoft-entra-id";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { prisma } from "@/lib/prisma";
import type { Role } from "@/generated/prisma/client";
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
MicrosoftEntraID({
clientId: process.env.AUTH_MICROSOFT_ENTRA_ID_ID,
clientSecret: process.env.AUTH_MICROSOFT_ENTRA_ID_SECRET,
issuer: `https://login.microsoftonline.com/${process.env.AUTH_MICROSOFT_ENTRA_ID_TENANT_ID}/v2.0`,
// Safe for Entra ID — Microsoft verifies organizational emails.
// Required to link SSO accounts to pre-seeded User records by email match.
allowDangerousEmailAccountLinking: true,
}),
],
session: {
strategy: "database",
},
events: {
async signIn({ user }) {
if (!user.id || !user.email) return;
// Auto-assign organization by email domain match
const dbUser = await prisma.user.findUnique({
where: { id: user.id },
select: { organizationId: true, email: true },
});
if (dbUser?.organizationId || !dbUser?.email) return;
const domain = dbUser.email.split("@")[1];
if (!domain) return;
const org = await prisma.organization.findFirst({
where: { domain },
});
if (org) {
await prisma.user.update({
where: { id: user.id },
data: { organizationId: org.id },
});
}
},
},
callbacks: {
async session({ session, user }) {
// Fetch user with role and org from database
const dbUser = await prisma.user.findUnique({
where: { id: user.id },
select: { role: true, organizationId: true },
});
if (dbUser) {
session.user.id = user.id;
session.user.role = dbUser.role;
session.user.organizationId = dbUser.organizationId;
}
return session;
},
},
pages: {
signIn: "/login",
},
});