Openstatus
www.openstatus.dev
1import type { DefaultSession } from "next-auth";
2import NextAuth from "next-auth";
3
4import { Events, setupAnalytics } from "@openstatus/analytics";
5import { db, eq } from "@openstatus/db";
6import { user } from "@openstatus/db/src/schema";
7
8import { WelcomeEmail, sendEmail } from "@openstatus/emails";
9import { headers } from "next/headers";
10import { adapter } from "./adapter";
11import { GitHubProvider, GoogleProvider, ResendProvider } from "./providers";
12
13export type { DefaultSession };
14
15export const { handlers, signIn, signOut, auth } = NextAuth({
16 // debug: true,
17 adapter,
18 providers:
19 process.env.NODE_ENV === "development" || process.env.SELF_HOST === "true"
20 ? [GitHubProvider, GoogleProvider, ResendProvider]
21 : [GitHubProvider, GoogleProvider],
22 callbacks: {
23 async signIn(params) {
24 // We keep updating the user info when we loggin in
25
26 if (params.account?.provider === "google") {
27 if (!params.profile) return true;
28 if (Number.isNaN(Number(params.user.id))) return true;
29
30 await db
31 .update(user)
32 .set({
33 firstName: params.profile.given_name,
34 lastName: params.profile.family_name || "",
35 photoUrl: params.profile.picture,
36 // keep the name in sync
37 name: `${params.profile.given_name} ${
38 params.profile.family_name || ""
39 }`.trim(),
40 updatedAt: new Date(),
41 })
42 .where(eq(user.id, Number(params.user.id)))
43 .run();
44 }
45 if (params.account?.provider === "github") {
46 if (!params.profile) return true;
47 if (Number.isNaN(Number(params.user.id))) return true;
48
49 await db
50 .update(user)
51 .set({
52 name: params.profile.name,
53 photoUrl: String(params.profile.avatar_url),
54 updatedAt: new Date(),
55 })
56 .where(eq(user.id, Number(params.user.id)))
57 .run();
58 }
59
60 // REMINDER: only used in dev mode
61 if (params.account?.provider === "resend") {
62 if (Number.isNaN(Number(params.user.id))) return true;
63 await db
64 .update(user)
65 .set({ updatedAt: new Date() })
66 .where(eq(user.id, Number(params.user.id)))
67 .run();
68 }
69
70 return true;
71 },
72 async session(params) {
73 return params.session;
74 },
75 },
76 events: {
77 // That should probably done in the callback method instead
78 async createUser(params) {
79 if (!params.user.id || !params.user.email) {
80 throw new Error("User id & email is required");
81 }
82
83 // this means the user has already been created with clerk
84 if (params.user.tenantId) return;
85
86 await sendEmail({
87 from: "Thibault from OpenStatus <thibault@openstatus.dev>",
88 subject: "Welcome to OpenStatus.",
89 to: [params.user.email],
90 react: WelcomeEmail(),
91 });
92
93 const analytics = await setupAnalytics({
94 userId: `usr_${params.user.id}`,
95 email: params.user.email,
96 location: (await headers()).get("x-forwarded-for") ?? undefined,
97 userAgent: (await headers()).get("user-agent") ?? undefined,
98 });
99
100 await analytics.track(Events.CreateUser);
101 },
102
103 async signIn(params) {
104 if (params.isNewUser) return;
105 if (!params.user.id || !params.user.email) return;
106
107 const analytics = await setupAnalytics({
108 userId: `usr_${params.user.id}`,
109 email: params.user.email,
110 location: (await headers()).get("x-forwarded-for") ?? undefined,
111 userAgent: (await headers()).get("user-agent") ?? undefined,
112 });
113
114 await analytics.track(Events.SignInUser);
115 },
116 },
117 pages: {
118 signIn: "/login",
119 newUser: "/onboarding",
120 },
121 // basePath: "/api/auth", // default is `/api/auth`
122 // secret: process.env.AUTH_SECRET, // default is `AUTH_SECRET`
123 debug: process.env.NODE_ENV === "development",
124});