diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx new file mode 100644 index 0000000..9e74013 --- /dev/null +++ b/src/app/(auth)/login/page.tsx @@ -0,0 +1,89 @@ +import { auth, signIn } from "@/lib/auth"; +import { redirect } from "next/navigation"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; + +export default async function LoginPage() { + const session = await auth(); + + if (session) { + redirect("/dashboard"); + } + + return ( +
+ + + + HP CG Production Tracker + + Sign in to manage your production pipeline + + +
{ + "use server"; + await signIn("google", { redirectTo: "/dashboard" }); + }} + > + +
+
{ + "use server"; + await signIn("microsoft-entra-id", { redirectTo: "/dashboard" }); + }} + > + +
+
+
+
+ ); +} + +function GoogleIcon() { + return ( + + + + + + + ); +} + +function MicrosoftIcon() { + return ( + + + + + + + ); +} diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..c55a45e --- /dev/null +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,3 @@ +import { handlers } from "@/lib/auth"; + +export const { GET, POST } = handlers; diff --git a/src/lib/auth.ts b/src/lib/auth.ts new file mode 100644 index 0000000..aeef523 --- /dev/null +++ b/src/lib/auth.ts @@ -0,0 +1,44 @@ +import NextAuth from "next-auth"; +import Google from "next-auth/providers/google"; +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: [ + Google({ + clientId: process.env.AUTH_GOOGLE_ID, + clientSecret: process.env.AUTH_GOOGLE_SECRET, + }), + 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`, + }), + ], + session: { + strategy: "database", + }, + 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", + }, +}); diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 0000000..e4dd454 --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,36 @@ +import { NextResponse } from "next/server"; +import type { NextRequest } from "next/server"; + +export function middleware(request: NextRequest) { + const { pathname } = request.nextUrl; + const isAuthPage = pathname.startsWith("/login"); + const isApiAuth = pathname.startsWith("/api/auth"); + + // Always allow auth API routes + if (isApiAuth) { + return NextResponse.next(); + } + + // Check for session cookie (Auth.js database sessions) + const sessionToken = + request.cookies.get("authjs.session-token")?.value || + request.cookies.get("__Secure-authjs.session-token")?.value; + + const isLoggedIn = !!sessionToken; + + // Redirect logged-in users away from login page + if (isAuthPage && isLoggedIn) { + return NextResponse.redirect(new URL("/dashboard", request.url)); + } + + // Redirect unauthenticated users to login + if (!isAuthPage && !isLoggedIn) { + return NextResponse.redirect(new URL("/login", request.url)); + } + + return NextResponse.next(); +} + +export const config = { + matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"], +}; diff --git a/src/types/next-auth.d.ts b/src/types/next-auth.d.ts new file mode 100644 index 0000000..17d06af --- /dev/null +++ b/src/types/next-auth.d.ts @@ -0,0 +1,14 @@ +import type { Role } from "@/generated/prisma/client"; + +declare module "next-auth" { + interface Session { + user: { + id: string; + name?: string | null; + email?: string | null; + image?: string | null; + role: Role; + organizationId: string | null; + }; + } +}