Openstatus
www.openstatus.dev
1import type { DefaultSession } from "next-auth";
2import NextAuth, { AuthError } from "next-auth";
3
4import { db, eq } from "@openstatus/db";
5import { viewer } from "@openstatus/db/src/schema";
6
7import { getValidCustomDomain } from "@/lib/domain";
8import { getQueryClient, trpc } from "@/lib/trpc/server";
9import { headers } from "next/headers";
10import { adapter } from "./adapter";
11import { ResendProvider } from "./providers";
12
13export type { DefaultSession };
14
15export const { handlers, signIn, signOut, auth } = NextAuth({
16 debug: process.env.NODE_ENV === "development",
17 adapter,
18 providers: [ResendProvider],
19 callbacks: {
20 async signIn(params) {
21 const _headers = await headers();
22 const host = _headers.get("host");
23
24 if (!host) throw new AuthError("No host found");
25
26 const protocol = _headers.get("x-forwarded-proto") || "https";
27 const req = new Request(`${protocol}://${host}`, {
28 headers: new Headers(_headers),
29 });
30 const { prefix } = getValidCustomDomain(req);
31
32 if (!prefix || !params.user.email) return false;
33
34 const queryClient = getQueryClient();
35 // NOTE: throws an error if the email domain is not allowed
36 const query = await queryClient.fetchQuery(
37 trpc.statusPage.validateEmailDomain.queryOptions({
38 slug: prefix,
39 email: params.user.email,
40 }),
41 );
42
43 if (!query) return false;
44
45 if (params.account?.provider === "resend") {
46 // if the user is new, the id is the verification_token and not the viewer id, so we cannot update the viewer
47 if (Number.isNaN(Number(params.user.id))) return true;
48 await db
49 .update(viewer)
50 .set({ updatedAt: new Date() })
51 .where(eq(viewer.id, Number(params.user.id)))
52 .run();
53
54 return true;
55 }
56
57 return false;
58 },
59 redirect: async (params) => {
60 return params.url;
61 },
62 async session(params) {
63 return params.session;
64 },
65 },
66});