Repository navigation
Expand file tree
/
Copy pathauth.ts
More file actions
85 lines (80 loc) · 2.82 KB
/
Copy pathauth.ts
File metadata and controls
85 lines (80 loc) · 2.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import NextAuth from "next-auth";
import { DrizzleAdapter } from "@auth/drizzle-adapter";
import { skipCSRFCheck } from "@auth/core";
import { Resend } from "resend";
import { getDb } from "./db/client";
import { users, accounts, sessions, verificationTokens } from "./db/tables";
const SESSION_MAX_AGE = 30 * 24 * 60 * 60; // 30 days in seconds
const TOKEN_MAX_AGE = 15 * 60; // 15 minutes in seconds
const resend = new Resend(process.env.RESEND_API_KEY ?? "re_placeholder");
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: DrizzleAdapter(getDb(), {
usersTable: users as any,
accountsTable: accounts as any,
sessionsTable: sessions as any,
verificationTokensTable: verificationTokens as any,
}),
providers: [
{
id: "email",
type: "email",
name: "Email",
from: process.env.EMAIL_FROM ?? "ArbWatch <noreply@arbwatch.test>",
maxAge: TOKEN_MAX_AGE,
async sendVerificationRequest({ identifier, url, provider }) {
const emailPayload = {
from: provider.from ?? "ArbWatch <noreply@arbwatch.test>",
to: identifier,
subject: "Sign in to ArbWatch",
html: `<p>Click the link below to sign in to ArbWatch:</p><p><a href="${url}">${url}</a></p><p>This link expires in 15 minutes.</p>`,
text: `Sign in to ArbWatch: ${url}\n\nThis link expires in 15 minutes.`,
};
// Populate the global inbox for Playwright / dev server tests
if (process.env.NODE_ENV === "test" || process.env.NODE_ENV === "development") {
if (typeof global !== "undefined") {
if (!(global as any).__testResendInbox) {
(global as any).__testResendInbox = [];
}
(global as any).__testResendInbox.push(emailPayload);
}
}
await resend.emails.send(emailPayload);
},
},
],
pages: {
signIn: "/signin",
verifyRequest: "/check-email",
error: "/signin",
newUser: "/onboarding/key",
},
session: {
strategy: "database",
maxAge: SESSION_MAX_AGE,
},
cookies: {
sessionToken: {
name:
process.env.NODE_ENV === "production"
? "__Secure-next-auth.session-token"
: "next-auth.session-token",
options: {
httpOnly: true,
sameSite: "lax" as const,
path: "/",
secure: process.env.NODE_ENV === "production",
maxAge: SESSION_MAX_AGE,
},
},
},
// Skip CSRF validation only when SKIP_CSRF_CHECK=true is explicitly set.
// This var must never appear in staging or production environments.
...(process.env.SKIP_CSRF_CHECK === "true" ? { skipCSRFCheck } : {}),
callbacks: {
async redirect({ url, baseUrl }) {
if (url.startsWith(baseUrl)) return url;
if (url.startsWith("/")) return `${baseUrl}${url}`;
return baseUrl;
},
},
});