-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathauth.js
More file actions
153 lines (145 loc) · 4.69 KB
/
Copy pathauth.js
File metadata and controls
153 lines (145 loc) · 4.69 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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import { Pool } from "pg";
import { betterAuth } from "better-auth";
import { admin, magicLink, jwt } from "better-auth/plugins";
import { oauthProvider } from "@better-auth/oauth-provider";
import appConfig from "./config.js";
import { getGithubUserInfo } from "./auth/github-provider.js";
import { getGithubAccountId } from "./auth/id-token-claims.js";
import { devMagicLinks } from "./dev/magic-links.js";
import { buildMagicLinkPayload } from "./app/utils/magic-link-email.js";
// PostgreSQL connection pool for CI/production and local dev
// SSL only for non-local connections (Heroku requires it; local/CI does not)
const isLocal =
appConfig.database_url.includes("@localhost") ||
appConfig.database_url.includes("@127.0.0.1");
const db = new Pool({
connectionString: appConfig.database_url,
max: 10,
...(isLocal ? {} : { ssl: { rejectUnauthorized: false } }),
});
db.on("error", (err) => {
console.error("Unexpected database error", err);
process.exit(-1);
});
export { db };
export const auth = betterAuth({
database: db,
baseURL: appConfig.base_url,
logger: {
disabled: false,
level: "debug",
},
socialProviders: {
github: {
clientId: appConfig.social.github.id,
clientSecret: appConfig.social.github.secret,
// Resolve the account email from /user/emails (primary + verified) so
// returning GitHub users match their existing planner accounts.
getUserInfo: getGithubUserInfo,
// Re-derive the stored email from the provider on every sign-in so users
// who linked BEFORE getUserInfo shipped (with their old public-email
// address) self-heal to the primary instead of staying bound to a
// duplicate planner account.
overrideUserInfoOnSignIn: true,
},
},
telemetry: {
enabled: false,
},
// ponytail: the database strategy's extra signed state cookie check is
// redundant — the state is already validated against the `verification`
// table. Cloudflare strips `__Secure-` prefix cookies on ingress, so the
// cookie fails on the GitHub OAuth callback redirect. skipStateCookieCheck
// skips this check. Better Auth's own oauth-proxy plugin does the same.
account: {
skipStateCookieCheck: true,
},
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days
updateAge: 60 * 60 * 24, // 1 day (update session every day)
},
plugins: [
jwt({
jwks: {
keyPairConfig: { alg: "RS256" },
},
jwt: {
issuer: appConfig.base_url,
audience: "planner",
expirationTime: "15m",
definePayload: (session) => ({
email: session.user.email,
name: session.user.name,
}),
},
}),
oauthProvider({
loginPage: "/login",
scopes: ["openid", "profile", "email"],
accessTokenExpiresIn: 900, // 15 minutes
validAudiences: ["planner"],
allowDynamicClientRegistration: false,
customIdTokenClaims: async ({ user }) => {
const githubId = await getGithubAccountId(db, user.id);
return githubId ? { github_id: String(githubId) } : {};
},
}),
magicLink({
sendMagicLink: async ({ email, url }) => {
if (process.env.NODE_ENV !== "production") {
console.log(`Magic Link for ${email}: ${url}`);
devMagicLinks.push({
email,
url,
createdAt: new Date().toISOString(),
});
return;
}
const apiKey = process.env.SENDGRID_API_KEY;
const fromEmail =
process.env.MAGIC_LINK_FROM_EMAIL || "auth-noreply@codebar.io";
if (!apiKey) {
console.error(
"SENDGRID_API_KEY is not set in production — magic links will not be sent",
);
return;
}
try {
const res = await fetch("https://api.sendgrid.com/v3/mail/send", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify(
buildMagicLinkPayload({
email,
url,
fromEmail,
subject: "Sign in to codebar",
}),
),
});
if (!res.ok) {
console.error(`SendGrid error: ${res.status} ${await res.text()}`);
}
} catch (err) {
console.error("Failed to send magic link:", err);
}
},
}),
admin({
// adminUserIds: [],
}),
],
// advanced: {
// crossSubDomainCookies: {
// enabled: true,
// domain: '.codebar.io',
// },
// },
trustedOrigins: [
appConfig.base_url,
...(!appConfig.isProduction ? ["http://127.0.0.1:3001"] : []),
],
});