@neondatabase/auth 0.1.0-beta.9 → 0.2.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -18
- package/dist/{adapter-core-Bw9mn_AS.d.mts → adapter-core-CnrOXh1T.d.mts} +246 -280
- package/dist/{adapter-core-C_NEMs0b.mjs → adapter-core-CtmnMMJ7.mjs} +392 -67
- package/dist/better-auth-react-adapter-DNi5PC5D.d.mts +2170 -0
- package/dist/{better-auth-react-adapter-BbM3jLLv.mjs → better-auth-react-adapter-Dv-o6A6O.mjs} +10 -8
- package/dist/{chunk-5DLVHPZS-Bxj7snpZ-DoVNlsyk.mjs → chunk-VCZJYX65-CLnrj1o7-D6ZQkcc_.mjs} +13 -3
- package/dist/constants-Cupc_bln.mjs +28 -0
- package/dist/index.d.mts +4 -98
- package/dist/index.mjs +2 -1
- package/dist/neon-auth-BEGCfAe6.d.mts +107 -0
- package/dist/{neon-auth-DdlToh7_.mjs → neon-auth-Cs2cWh1B.mjs} +7 -4
- package/dist/next/index.d.mts +61 -170
- package/dist/next/index.mjs +4 -311
- package/dist/next/server/index.d.mts +538 -0
- package/dist/next/server/index.mjs +1373 -0
- package/dist/react/adapters/index.d.mts +4 -4
- package/dist/react/adapters/index.mjs +2 -1
- package/dist/react/index.d.mts +5 -5
- package/dist/react/index.mjs +4 -3
- package/dist/react/ui/index.d.mts +1 -1
- package/dist/react/ui/index.mjs +2 -2
- package/dist/react/ui/server.mjs +1 -1
- package/dist/{supabase-adapter-CAqbpOC7.mjs → supabase-adapter-BlcGPyOf.mjs} +28 -45
- package/dist/supabase-adapter-DUqw2fw8.d.mts +2258 -0
- package/dist/types/index.d.mts +2 -7
- package/dist/ui/.safelist.html +3 -0
- package/dist/ui/css.css +2 -2
- package/dist/ui/tailwind.css +2 -1
- package/dist/ui/theme-inline.css +44 -0
- package/dist/ui/theme.css +103 -76
- package/dist/{ui-aMoA-9nq.mjs → ui-COLWzDsu.mjs} +6024 -3004
- package/dist/vanilla/adapters/index.d.mts +3 -3
- package/dist/vanilla/adapters/index.mjs +2 -1
- package/dist/vanilla/index.d.mts +3 -3
- package/dist/vanilla/index.mjs +2 -1
- package/llms.txt +330 -0
- package/package.json +17 -10
- package/dist/better-auth-react-adapter-JoscqoDc.d.mts +0 -722
- package/dist/better-auth-types-CE4hLv9E.d.mts +0 -9
- package/dist/supabase-adapter-Clxlqg1x.d.mts +0 -127
- /package/dist/{adapters-D0mxG3F-.mjs → adapters-B7YKkjaL.mjs} +0 -0
- /package/dist/{adapters-Df6Dd3KK.mjs → adapters-CivF9wql.mjs} +0 -0
- /package/dist/{index-ClXLQ1fw.d.mts → index-CPnFzULh.d.mts} +0 -0
- /package/dist/{index-BXlAjlSt.d.mts → index-CzsGMS7C.d.mts} +0 -0
- /package/dist/{index-DCQ5Y2ED.d.mts → index-OEBbnNdr.d.mts} +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { a as NEON_AUTH_POPUP_PARAM_NAME, c as SESSION_CACHE_TTL_MS, i as NEON_AUTH_POPUP_CALLBACK_ROUTE, o as NEON_AUTH_SESSION_VERIFIER_PARAM_NAME, r as NEON_AUTH_POPUP_CALLBACK_PARAM_NAME, s as OAUTH_POPUP_MESSAGE_TYPE, t as CLOCK_SKEW_BUFFER_MS } from "./constants-Cupc_bln.mjs";
|
|
1
2
|
import { getGlobalBroadcastChannel } from "better-auth/client";
|
|
2
|
-
import { adminClient,
|
|
3
|
+
import { adminClient, emailOTPClient, jwtClient, organizationClient } from "better-auth/client/plugins";
|
|
4
|
+
import z from "zod";
|
|
3
5
|
|
|
4
6
|
//#region src/core/in-flight-request-manager.ts
|
|
5
7
|
/**
|
|
@@ -99,25 +101,6 @@ var InFlightRequestManager = class {
|
|
|
99
101
|
}
|
|
100
102
|
};
|
|
101
103
|
|
|
102
|
-
//#endregion
|
|
103
|
-
//#region src/core/constants.ts
|
|
104
|
-
/**
|
|
105
|
-
* Session caching configuration constants
|
|
106
|
-
*
|
|
107
|
-
* Uses industry-standard 60s cache TTL (common across auth providers).
|
|
108
|
-
*
|
|
109
|
-
* Note: Token refresh detection is now automatic via Better Auth's
|
|
110
|
-
* fetchOptions.onSuccess callback. No polling is needed.
|
|
111
|
-
*/
|
|
112
|
-
/** Session cache TTL in milliseconds (60 seconds) */
|
|
113
|
-
const SESSION_CACHE_TTL_MS = 6e4;
|
|
114
|
-
/** Clock skew buffer for token expiration checks in milliseconds (10 seconds) */
|
|
115
|
-
const CLOCK_SKEW_BUFFER_MS = 1e4;
|
|
116
|
-
/** Default session expiry duration in milliseconds (1 hour) */
|
|
117
|
-
const DEFAULT_SESSION_EXPIRY_MS = 36e5;
|
|
118
|
-
/** Name of the session verifier parameter in the URL, used for the OAUTH flow */
|
|
119
|
-
const NEON_AUTH_SESSION_VERIFIER_PARAM_NAME = "neon_auth_session_verifier";
|
|
120
|
-
|
|
121
104
|
//#endregion
|
|
122
105
|
//#region src/utils/jwt.ts
|
|
123
106
|
/**
|
|
@@ -136,16 +119,68 @@ function getJwtExpiration(jwt) {
|
|
|
136
119
|
}
|
|
137
120
|
}
|
|
138
121
|
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/core/token-cache.ts
|
|
124
|
+
var TokenCache = class {
|
|
125
|
+
cache = null;
|
|
126
|
+
/**
|
|
127
|
+
* Get cached data if not expired.
|
|
128
|
+
* Returns null if cache is empty or expired.
|
|
129
|
+
*/
|
|
130
|
+
get() {
|
|
131
|
+
if (!this.cache) return null;
|
|
132
|
+
if (Date.now() > this.cache.expiresAt) {
|
|
133
|
+
this.cache = null;
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return this.cache.data;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Set cached data with TTL.
|
|
140
|
+
* If jwt is provided, TTL is calculated from its expiration.
|
|
141
|
+
* Otherwise, uses default SESSION_CACHE_TTL_MS.
|
|
142
|
+
*/
|
|
143
|
+
set(data, jwt) {
|
|
144
|
+
const ttl = this.calculateTTL(jwt);
|
|
145
|
+
this.cache = {
|
|
146
|
+
data,
|
|
147
|
+
expiresAt: Date.now() + ttl
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Clear the cache.
|
|
152
|
+
*/
|
|
153
|
+
clear() {
|
|
154
|
+
this.cache = null;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Check if cache has valid (non-expired) data.
|
|
158
|
+
*/
|
|
159
|
+
has() {
|
|
160
|
+
return this.get() !== null;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Calculate cache TTL from JWT expiration.
|
|
164
|
+
* Falls back to default TTL if JWT is invalid or missing.
|
|
165
|
+
*/
|
|
166
|
+
calculateTTL(jwt) {
|
|
167
|
+
if (!jwt) return SESSION_CACHE_TTL_MS;
|
|
168
|
+
const exp = getJwtExpiration(jwt);
|
|
169
|
+
if (!exp) return SESSION_CACHE_TTL_MS;
|
|
170
|
+
const now = Date.now();
|
|
171
|
+
const ttl = exp * 1e3 - now - CLOCK_SKEW_BUFFER_MS;
|
|
172
|
+
return Math.max(ttl, 1e3);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
139
176
|
//#endregion
|
|
140
177
|
//#region src/core/session-cache-manager.ts
|
|
141
178
|
/**
|
|
142
179
|
* Manages in-memory session cache with TTL expiration.
|
|
143
180
|
*
|
|
144
|
-
*
|
|
145
|
-
* - Stores sessions in Better Auth native format
|
|
146
|
-
* - Automatic expiration based on JWT token expiration
|
|
181
|
+
* Built on TokenCache, adding session-specific features:
|
|
147
182
|
* - Invalidation flag for sign-out scenarios
|
|
148
|
-
* -
|
|
183
|
+
* - Token refresh detection via lastSessionData comparison
|
|
149
184
|
*
|
|
150
185
|
* Example:
|
|
151
186
|
* ```typescript
|
|
@@ -155,48 +190,40 @@ function getJwtExpiration(jwt) {
|
|
|
155
190
|
* ```
|
|
156
191
|
*/
|
|
157
192
|
var SessionCacheManager = class {
|
|
158
|
-
cache =
|
|
193
|
+
cache = new TokenCache();
|
|
159
194
|
lastSessionData = null;
|
|
195
|
+
invalidated = false;
|
|
160
196
|
/**
|
|
161
197
|
* Get cached session if valid and not expired.
|
|
162
198
|
* Returns null if cache is invalid, expired, or doesn't exist.
|
|
163
199
|
*/
|
|
164
200
|
getCachedSession() {
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
this.clearSessionCache();
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
return this.cache.data;
|
|
201
|
+
if (this.invalidated) return null;
|
|
202
|
+
return this.cache.get();
|
|
171
203
|
}
|
|
172
204
|
/**
|
|
173
|
-
* Set cached session with
|
|
174
|
-
* If TTL not provided, calculates from JWT expiration.
|
|
205
|
+
* Set cached session with JWT-based TTL.
|
|
175
206
|
* Skips caching if cache was invalidated (sign-out scenario).
|
|
176
207
|
*/
|
|
177
|
-
setCachedSession(data
|
|
178
|
-
if (this.
|
|
179
|
-
this.lastSessionData = this.cache
|
|
180
|
-
|
|
181
|
-
this.cache = {
|
|
182
|
-
data,
|
|
183
|
-
expiresAt: Date.now() + calculatedTtl,
|
|
184
|
-
invalidated: false
|
|
185
|
-
};
|
|
208
|
+
setCachedSession(data) {
|
|
209
|
+
if (this.invalidated) return;
|
|
210
|
+
this.lastSessionData = this.cache.get();
|
|
211
|
+
this.cache.set(data, data.session.token);
|
|
186
212
|
}
|
|
187
213
|
/**
|
|
188
214
|
* Invalidate cache (marks as invalid but doesn't clear).
|
|
189
215
|
* Useful for sign-out scenarios where in-flight requests should not cache.
|
|
190
216
|
*/
|
|
191
217
|
invalidateSessionCache() {
|
|
192
|
-
|
|
218
|
+
this.invalidated = true;
|
|
193
219
|
}
|
|
194
220
|
/**
|
|
195
221
|
* Clear cache completely.
|
|
196
222
|
*/
|
|
197
223
|
clearSessionCache() {
|
|
198
|
-
this.cache
|
|
224
|
+
this.cache.clear();
|
|
199
225
|
this.lastSessionData = null;
|
|
226
|
+
this.invalidated = false;
|
|
200
227
|
}
|
|
201
228
|
/**
|
|
202
229
|
* Check if token was refreshed by comparing tokens with previous session.
|
|
@@ -206,20 +233,101 @@ var SessionCacheManager = class {
|
|
|
206
233
|
if (!this.lastSessionData?.session?.token || !data?.session?.token) return false;
|
|
207
234
|
return this.lastSessionData.session.token !== data.session.token;
|
|
208
235
|
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region src/core/anonymous-token-cache-manager.ts
|
|
240
|
+
/**
|
|
241
|
+
* Manages in-memory anonymous token cache with TTL expiration.
|
|
242
|
+
*
|
|
243
|
+
* Stores the full anonymous token response (token + expires_at).
|
|
244
|
+
* Unlike SessionCacheManager, doesn't need:
|
|
245
|
+
* - Invalidation flag (no sign-out scenario for anonymous tokens)
|
|
246
|
+
* - Refresh detection (anonymous tokens are stateless)
|
|
247
|
+
*/
|
|
248
|
+
var AnonymousTokenCacheManager = class {
|
|
249
|
+
cache = new TokenCache();
|
|
250
|
+
/**
|
|
251
|
+
* Get cached anonymous token response if not expired.
|
|
252
|
+
* Returns null if cache is empty or expired.
|
|
253
|
+
*/
|
|
254
|
+
getCachedResponse() {
|
|
255
|
+
return this.cache.get();
|
|
256
|
+
}
|
|
209
257
|
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
258
|
+
* Set cached anonymous token response with JWT-based TTL.
|
|
259
|
+
* TTL is automatically calculated from the JWT expiration.
|
|
212
260
|
*/
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
261
|
+
setCachedResponse(data) {
|
|
262
|
+
this.cache.set(data, data.token);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Clear the cache.
|
|
266
|
+
*/
|
|
267
|
+
clearCache() {
|
|
268
|
+
this.cache.clear();
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Check if cache has a valid (non-expired) response.
|
|
272
|
+
*/
|
|
273
|
+
hasCachedResponse() {
|
|
274
|
+
return this.cache.has();
|
|
220
275
|
}
|
|
221
276
|
};
|
|
222
277
|
|
|
278
|
+
//#endregion
|
|
279
|
+
//#region src/core/oauth-popup.ts
|
|
280
|
+
/**
|
|
281
|
+
* Opens an OAuth popup window and waits for completion.
|
|
282
|
+
*
|
|
283
|
+
* This is used when the app is running inside an iframe, where OAuth
|
|
284
|
+
* redirect flows don't work due to X-Frame-Options/CSP restrictions.
|
|
285
|
+
* The popup completes the OAuth flow and sends a postMessage back
|
|
286
|
+
* with the session verifier needed to fetch the session.
|
|
287
|
+
*
|
|
288
|
+
* @param url - The OAuth authorization URL to open in the popup
|
|
289
|
+
* @returns Promise that resolves with the session verifier when OAuth completes
|
|
290
|
+
* @throws Error if popup is blocked, closed by user, or times out
|
|
291
|
+
*/
|
|
292
|
+
async function openOAuthPopup(url) {
|
|
293
|
+
const timeout = 12e4;
|
|
294
|
+
const pollInterval = 500;
|
|
295
|
+
return new Promise((resolve, reject) => {
|
|
296
|
+
const popup = globalThis.open(url, "neon_oauth_popup", "width=500,height=700,popup=yes");
|
|
297
|
+
if (!popup || popup.closed) {
|
|
298
|
+
reject(/* @__PURE__ */ new Error("Popup blocked. Please allow popups for this site."));
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const timeoutId = setTimeout(() => {
|
|
302
|
+
cleanup();
|
|
303
|
+
try {
|
|
304
|
+
popup.close();
|
|
305
|
+
} catch {}
|
|
306
|
+
reject(/* @__PURE__ */ new Error("OAuth popup timed out. Please try again."));
|
|
307
|
+
}, timeout);
|
|
308
|
+
const pollId = setInterval(() => {
|
|
309
|
+
try {
|
|
310
|
+
if (popup.closed) {
|
|
311
|
+
cleanup();
|
|
312
|
+
reject(/* @__PURE__ */ new Error("OAuth popup was closed. Please try again."));
|
|
313
|
+
}
|
|
314
|
+
} catch {}
|
|
315
|
+
}, pollInterval);
|
|
316
|
+
function cleanup() {
|
|
317
|
+
clearTimeout(timeoutId);
|
|
318
|
+
clearInterval(pollId);
|
|
319
|
+
globalThis.removeEventListener("message", handleMessage);
|
|
320
|
+
}
|
|
321
|
+
function handleMessage(event) {
|
|
322
|
+
if (event.origin !== globalThis.location.origin) return;
|
|
323
|
+
if (event.data?.type !== OAUTH_POPUP_MESSAGE_TYPE) return;
|
|
324
|
+
cleanup();
|
|
325
|
+
resolve({ verifier: event.data.verifier || null });
|
|
326
|
+
}
|
|
327
|
+
globalThis.addEventListener("message", handleMessage);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
223
331
|
//#endregion
|
|
224
332
|
//#region src/utils/browser.ts
|
|
225
333
|
/**
|
|
@@ -229,20 +337,91 @@ var SessionCacheManager = class {
|
|
|
229
337
|
const isBrowser = () => {
|
|
230
338
|
return globalThis.window !== void 0 && typeof document !== "undefined";
|
|
231
339
|
};
|
|
340
|
+
/**
|
|
341
|
+
* Checks if the code is running inside an iframe
|
|
342
|
+
* Used to detect embedded contexts where OAuth redirect won't work
|
|
343
|
+
* @returns true if in iframe, false otherwise
|
|
344
|
+
*/
|
|
345
|
+
const isIframe = () => {
|
|
346
|
+
if (!isBrowser()) return false;
|
|
347
|
+
try {
|
|
348
|
+
return globalThis.self !== globalThis.top;
|
|
349
|
+
} catch {
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
//#endregion
|
|
355
|
+
//#region src/plugins/anonymous-token.ts
|
|
356
|
+
const ANONYMOUS_TOKEN_ENDPOINT = "/token/anonymous";
|
|
357
|
+
const anonymousTokenResponseSchema = z.object({
|
|
358
|
+
token: z.string(),
|
|
359
|
+
expires_at: z.number()
|
|
360
|
+
});
|
|
361
|
+
const anonymousTokenClient = () => {
|
|
362
|
+
return {
|
|
363
|
+
id: "anonymous-token",
|
|
364
|
+
pathMethods: { [ANONYMOUS_TOKEN_ENDPOINT]: "GET" },
|
|
365
|
+
getActions: ($fetch) => {
|
|
366
|
+
return { getAnonymousToken: async (fetchOptions) => {
|
|
367
|
+
return await $fetch(ANONYMOUS_TOKEN_ENDPOINT, {
|
|
368
|
+
method: "GET",
|
|
369
|
+
...fetchOptions
|
|
370
|
+
});
|
|
371
|
+
} };
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
};
|
|
232
375
|
|
|
233
376
|
//#endregion
|
|
234
377
|
//#region src/core/better-auth-methods.ts
|
|
235
378
|
const CURRENT_TAB_CLIENT_ID = crypto.randomUUID();
|
|
236
379
|
const BETTER_AUTH_METHODS_IN_FLIGHT_REQUESTS = new InFlightRequestManager();
|
|
237
380
|
const BETTER_AUTH_METHODS_CACHE = new SessionCacheManager();
|
|
381
|
+
const BETTER_AUTH_ANONYMOUS_TOKEN_CACHE = new AnonymousTokenCacheManager();
|
|
238
382
|
const BETTER_AUTH_ENDPOINTS = {
|
|
239
383
|
signUp: "/sign-up",
|
|
240
384
|
signIn: "/sign-in",
|
|
241
385
|
signOut: "/sign-out",
|
|
242
386
|
updateUser: "/update-user",
|
|
243
387
|
getSession: "/get-session",
|
|
244
|
-
token: "/token"
|
|
388
|
+
token: "/token",
|
|
389
|
+
anonymousSignIn: "/sign-in/anonymous",
|
|
390
|
+
anonymousToken: "/token/anonymous"
|
|
245
391
|
};
|
|
392
|
+
/**
|
|
393
|
+
* Handles social sign-in via popup when running inside an iframe.
|
|
394
|
+
* This is necessary because OAuth redirects don't work in iframes due to
|
|
395
|
+
* X-Frame-Options/CSP restrictions from OAuth providers.
|
|
396
|
+
*
|
|
397
|
+
* Flow:
|
|
398
|
+
* 1. Request OAuth URL from server (with disableRedirect)
|
|
399
|
+
* 2. Open popup window with the OAuth URL
|
|
400
|
+
* 3. Wait for popup to complete and send back the session verifier
|
|
401
|
+
* 4. Navigate to callbackURL with verifier - normal page load handles session
|
|
402
|
+
*/
|
|
403
|
+
async function handleSocialSignInViaPopup(input, init) {
|
|
404
|
+
const body = JSON.parse(init?.body || "{}");
|
|
405
|
+
const originalCallbackURL = body.callbackURL || "/";
|
|
406
|
+
const popupCallbackUrl = new URL(NEON_AUTH_POPUP_CALLBACK_ROUTE, globalThis.location.origin);
|
|
407
|
+
popupCallbackUrl.searchParams.set(NEON_AUTH_POPUP_PARAM_NAME, "1");
|
|
408
|
+
popupCallbackUrl.searchParams.set(NEON_AUTH_POPUP_CALLBACK_PARAM_NAME, originalCallbackURL);
|
|
409
|
+
body.callbackURL = popupCallbackUrl.toString();
|
|
410
|
+
body.disableRedirect = true;
|
|
411
|
+
const response = await fetch(input, {
|
|
412
|
+
...init,
|
|
413
|
+
body: JSON.stringify(body)
|
|
414
|
+
});
|
|
415
|
+
const data = await response.json();
|
|
416
|
+
const oauthUrl = data.url;
|
|
417
|
+
if (!oauthUrl) throw new Error("Failed to get OAuth URL");
|
|
418
|
+
const popupResult = await openOAuthPopup(oauthUrl);
|
|
419
|
+
if (!popupResult.verifier) throw new Error("OAuth completed but no session verifier received");
|
|
420
|
+
const navigationUrl = new URL(originalCallbackURL, globalThis.location.origin);
|
|
421
|
+
navigationUrl.searchParams.set(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME, popupResult.verifier);
|
|
422
|
+
globalThis.location.href = navigationUrl.toString();
|
|
423
|
+
return Response.json(data, { status: response.status });
|
|
424
|
+
}
|
|
246
425
|
const BETTER_AUTH_METHODS_HOOKS = {
|
|
247
426
|
signUp: {
|
|
248
427
|
onRequest: () => {},
|
|
@@ -261,6 +440,10 @@ const BETTER_AUTH_METHODS_HOOKS = {
|
|
|
261
440
|
}
|
|
262
441
|
},
|
|
263
442
|
signIn: {
|
|
443
|
+
beforeFetch: (input, init) => {
|
|
444
|
+
if (!(typeof input === "string" ? input : input.toString()).includes("/sign-in/social") || !isIframe()) return null;
|
|
445
|
+
return handleSocialSignInViaPopup(input, init);
|
|
446
|
+
},
|
|
264
447
|
onRequest: () => {},
|
|
265
448
|
onSuccess: (responseData) => {
|
|
266
449
|
if (isSessionResponseData(responseData)) {
|
|
@@ -289,17 +472,24 @@ const BETTER_AUTH_METHODS_HOOKS = {
|
|
|
289
472
|
updateUser: {
|
|
290
473
|
onRequest: () => {},
|
|
291
474
|
onSuccess: (responseData) => {
|
|
292
|
-
if (isSessionResponseData(responseData))
|
|
293
|
-
|
|
294
|
-
data: {
|
|
475
|
+
if (isSessionResponseData(responseData)) {
|
|
476
|
+
const sessionData = {
|
|
295
477
|
session: responseData.session,
|
|
296
478
|
user: responseData.user
|
|
297
|
-
}
|
|
298
|
-
|
|
479
|
+
};
|
|
480
|
+
BETTER_AUTH_METHODS_CACHE.setCachedSession(sessionData);
|
|
481
|
+
emitAuthEvent({
|
|
482
|
+
type: "USER_UPDATE",
|
|
483
|
+
data: sessionData
|
|
484
|
+
});
|
|
485
|
+
} else {
|
|
486
|
+
BETTER_AUTH_METHODS_CACHE.clearSessionCache();
|
|
487
|
+
emitAuthEvent({ type: "USER_UPDATE" });
|
|
488
|
+
}
|
|
299
489
|
}
|
|
300
490
|
},
|
|
301
491
|
getSession: {
|
|
302
|
-
|
|
492
|
+
beforeFetch: () => {
|
|
303
493
|
const cachedData = BETTER_AUTH_METHODS_CACHE.getCachedSession();
|
|
304
494
|
if (!cachedData) return null;
|
|
305
495
|
return Response.json(cachedData, { status: 200 });
|
|
@@ -337,6 +527,18 @@ const BETTER_AUTH_METHODS_HOOKS = {
|
|
|
337
527
|
}
|
|
338
528
|
}
|
|
339
529
|
}
|
|
530
|
+
},
|
|
531
|
+
anonymousToken: {
|
|
532
|
+
beforeFetch: () => {
|
|
533
|
+
const cachedResponse = BETTER_AUTH_ANONYMOUS_TOKEN_CACHE.getCachedResponse();
|
|
534
|
+
if (!cachedResponse) return null;
|
|
535
|
+
return Response.json(cachedResponse, { status: 200 });
|
|
536
|
+
},
|
|
537
|
+
onRequest: () => {},
|
|
538
|
+
onSuccess: (responseData) => {
|
|
539
|
+
const parsed = anonymousTokenResponseSchema.safeParse(responseData);
|
|
540
|
+
if (parsed.success) BETTER_AUTH_ANONYMOUS_TOKEN_CACHE.setCachedResponse(parsed.data);
|
|
541
|
+
}
|
|
340
542
|
}
|
|
341
543
|
};
|
|
342
544
|
/**
|
|
@@ -394,6 +596,8 @@ function isSessionResponseData(responseData) {
|
|
|
394
596
|
return Boolean(responseData && typeof responseData === "object" && "session" in responseData && "user" in responseData && responseData.session !== null && responseData.user !== null);
|
|
395
597
|
}
|
|
396
598
|
function deriveBetterAuthMethodFromUrl(url) {
|
|
599
|
+
if (url.includes("/sign-in/anonymous")) return "anonymousSignIn";
|
|
600
|
+
if (url.includes(BETTER_AUTH_ENDPOINTS.anonymousToken)) return "anonymousToken";
|
|
397
601
|
if (url.includes(BETTER_AUTH_ENDPOINTS.signIn)) return "signIn";
|
|
398
602
|
if (url.includes(BETTER_AUTH_ENDPOINTS.signUp)) return "signUp";
|
|
399
603
|
if (url.includes(BETTER_AUTH_ENDPOINTS.signOut)) return "signOut";
|
|
@@ -401,6 +605,20 @@ function deriveBetterAuthMethodFromUrl(url) {
|
|
|
401
605
|
if (url.includes(BETTER_AUTH_ENDPOINTS.getSession) || url.includes(BETTER_AUTH_ENDPOINTS.token)) return "getSession";
|
|
402
606
|
}
|
|
403
607
|
function initBroadcastChannel() {
|
|
608
|
+
if (isBrowser() && globalThis.opener && globalThis.opener !== globalThis) {
|
|
609
|
+
const params = new URLSearchParams(globalThis.location.search);
|
|
610
|
+
if (params.has(NEON_AUTH_POPUP_PARAM_NAME)) {
|
|
611
|
+
const verifier = params.get(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME);
|
|
612
|
+
const originalCallback = params.get(NEON_AUTH_POPUP_CALLBACK_PARAM_NAME);
|
|
613
|
+
globalThis.opener.postMessage({
|
|
614
|
+
type: OAUTH_POPUP_MESSAGE_TYPE,
|
|
615
|
+
verifier,
|
|
616
|
+
originalCallback
|
|
617
|
+
}, "*");
|
|
618
|
+
globalThis.close();
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
404
622
|
getGlobalBroadcastChannel().subscribe((message) => {
|
|
405
623
|
if (message.clientId === CURRENT_TAB_CLIENT_ID) return;
|
|
406
624
|
const trigger = message.data?.trigger;
|
|
@@ -408,6 +626,96 @@ function initBroadcastChannel() {
|
|
|
408
626
|
});
|
|
409
627
|
}
|
|
410
628
|
|
|
629
|
+
//#endregion
|
|
630
|
+
//#region package.json
|
|
631
|
+
var name = "@neondatabase/auth";
|
|
632
|
+
var version = "0.1.0-beta.21";
|
|
633
|
+
|
|
634
|
+
//#endregion
|
|
635
|
+
//#region ../internal/dist/index.mjs
|
|
636
|
+
const X_NEON_CLIENT_INFO_HEADER = "X-Neon-Client-Info";
|
|
637
|
+
/**
|
|
638
|
+
* Type guard for checking if a property exists on globalThis
|
|
639
|
+
*/
|
|
640
|
+
function hasGlobalProperty(key) {
|
|
641
|
+
return key in globalThis;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Detects the JavaScript framework being used at runtime.
|
|
645
|
+
* Detection order matters to avoid false positives (e.g., Next.js includes React).
|
|
646
|
+
*/
|
|
647
|
+
function detectFramework() {
|
|
648
|
+
if (typeof process !== "undefined" && process.env && (process.env.NEXT_RUNTIME || process.env.__NEXT_PRIVATE_ORIGIN)) return "next";
|
|
649
|
+
if (typeof globalThis.window !== "undefined") {
|
|
650
|
+
if (hasGlobalProperty("__NEXT_DATA__")) return "next";
|
|
651
|
+
if (hasGlobalProperty("__remixContext")) return "remix";
|
|
652
|
+
if (hasGlobalProperty("__REACT_DEVTOOLS_GLOBAL_HOOK__")) return "react";
|
|
653
|
+
if (hasGlobalProperty("__VUE__")) return "vue";
|
|
654
|
+
if (hasGlobalProperty("Zone")) return "angular";
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
function getClientInfo(sdkName, sdkVersion) {
|
|
658
|
+
const base = {
|
|
659
|
+
sdk: sdkName,
|
|
660
|
+
version: sdkVersion,
|
|
661
|
+
runtime: "unknown",
|
|
662
|
+
runtimeVersion: "unknown",
|
|
663
|
+
platform: "unknown",
|
|
664
|
+
arch: "unknown"
|
|
665
|
+
};
|
|
666
|
+
let result;
|
|
667
|
+
if (typeof process !== "undefined" && process.versions?.node) result = {
|
|
668
|
+
...base,
|
|
669
|
+
runtime: "node",
|
|
670
|
+
runtimeVersion: process.versions.node,
|
|
671
|
+
platform: process.platform,
|
|
672
|
+
arch: process.arch
|
|
673
|
+
};
|
|
674
|
+
else if (typeof Deno !== "undefined") result = {
|
|
675
|
+
...base,
|
|
676
|
+
runtime: "deno",
|
|
677
|
+
runtimeVersion: Deno.version?.deno ?? "unknown",
|
|
678
|
+
platform: Deno.build?.os ?? "unknown",
|
|
679
|
+
arch: Deno.build?.arch ?? "unknown"
|
|
680
|
+
};
|
|
681
|
+
else if (typeof Bun !== "undefined") result = {
|
|
682
|
+
...base,
|
|
683
|
+
runtime: "bun",
|
|
684
|
+
runtimeVersion: Bun.version ?? "unknown",
|
|
685
|
+
platform: process?.platform ?? "unknown",
|
|
686
|
+
arch: process?.arch ?? "unknown"
|
|
687
|
+
};
|
|
688
|
+
else if (typeof EdgeRuntime !== "undefined" || typeof process !== "undefined" && !process.versions?.node && typeof globalThis.window === "undefined" && typeof document === "undefined") result = {
|
|
689
|
+
...base,
|
|
690
|
+
runtime: "edge"
|
|
691
|
+
};
|
|
692
|
+
else if (globalThis.window !== void 0 && typeof document !== "undefined") result = {
|
|
693
|
+
...base,
|
|
694
|
+
runtime: "browser",
|
|
695
|
+
runtimeVersion: "unknown",
|
|
696
|
+
platform: "web",
|
|
697
|
+
arch: "unknown"
|
|
698
|
+
};
|
|
699
|
+
else result = base;
|
|
700
|
+
const framework = detectFramework();
|
|
701
|
+
if (framework) result.framework = framework;
|
|
702
|
+
return result;
|
|
703
|
+
}
|
|
704
|
+
function createClientInfoInjector(defaultSdkName, defaultSdkVersion) {
|
|
705
|
+
const cachedClientInfo = JSON.stringify(getClientInfo(defaultSdkName, defaultSdkVersion));
|
|
706
|
+
return function injectClientInfo$1(headers, sdkOverride) {
|
|
707
|
+
const result = new Headers(headers);
|
|
708
|
+
if (result.has(X_NEON_CLIENT_INFO_HEADER)) return result;
|
|
709
|
+
const clientInfoString = sdkOverride ? JSON.stringify(getClientInfo(sdkOverride.name, sdkOverride.version)) : cachedClientInfo;
|
|
710
|
+
result.set(X_NEON_CLIENT_INFO_HEADER, clientInfoString);
|
|
711
|
+
return result;
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
//#endregion
|
|
716
|
+
//#region src/utils/client-info.ts
|
|
717
|
+
const injectClientInfo = createClientInfoInjector(name, version);
|
|
718
|
+
|
|
411
719
|
//#endregion
|
|
412
720
|
//#region src/core/adapter-core.ts
|
|
413
721
|
const FORCE_FETCH_HEADER = "X-Force-Fetch";
|
|
@@ -416,7 +724,7 @@ const supportedBetterAuthClientPlugins = [
|
|
|
416
724
|
adminClient(),
|
|
417
725
|
organizationClient(),
|
|
418
726
|
emailOTPClient(),
|
|
419
|
-
|
|
727
|
+
anonymousTokenClient()
|
|
420
728
|
];
|
|
421
729
|
var NeonAuthAdapterCore = class {
|
|
422
730
|
betterAuthOptions;
|
|
@@ -440,9 +748,9 @@ var NeonAuthAdapterCore = class {
|
|
|
440
748
|
userOnRequest?.(request);
|
|
441
749
|
},
|
|
442
750
|
customFetchImpl: async (url, init) => {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
delete
|
|
751
|
+
const headers = injectClientInfo(init?.headers);
|
|
752
|
+
if (headers.has(FORCE_FETCH_HEADER)) {
|
|
753
|
+
headers.delete(FORCE_FETCH_HEADER);
|
|
446
754
|
const response$1 = await fetch(url, {
|
|
447
755
|
...init,
|
|
448
756
|
headers
|
|
@@ -458,11 +766,14 @@ var NeonAuthAdapterCore = class {
|
|
|
458
766
|
}
|
|
459
767
|
const betterAuthMethod = deriveBetterAuthMethodFromUrl(url.toString());
|
|
460
768
|
if (betterAuthMethod) {
|
|
461
|
-
const response$1 = await BETTER_AUTH_METHODS_HOOKS[betterAuthMethod].
|
|
769
|
+
const response$1 = await BETTER_AUTH_METHODS_HOOKS[betterAuthMethod].beforeFetch?.(url, init);
|
|
462
770
|
if (response$1) return response$1;
|
|
463
771
|
}
|
|
464
772
|
const key = `${init?.method || "GET"}:${url}:${init?.body || ""}`;
|
|
465
|
-
const response = await BETTER_AUTH_METHODS_IN_FLIGHT_REQUESTS.deduplicate(key, () => fetch(url,
|
|
773
|
+
const response = await BETTER_AUTH_METHODS_IN_FLIGHT_REQUESTS.deduplicate(key, () => fetch(url, {
|
|
774
|
+
...init,
|
|
775
|
+
headers
|
|
776
|
+
}));
|
|
466
777
|
if (!response.ok) {
|
|
467
778
|
const errorBody = await response.clone().json().catch(() => ({}));
|
|
468
779
|
const err = new Error(errorBody.message || `HTTP ${response.status} ${response.statusText}`);
|
|
@@ -486,7 +797,21 @@ var NeonAuthAdapterCore = class {
|
|
|
486
797
|
};
|
|
487
798
|
initBroadcastChannel();
|
|
488
799
|
}
|
|
800
|
+
/**
|
|
801
|
+
* Get JWT token for authenticated or anonymous access.
|
|
802
|
+
* Single source of truth for token retrieval logic.
|
|
803
|
+
*
|
|
804
|
+
* @param allowAnonymous - When true, fetches anonymous token if no session exists
|
|
805
|
+
* @returns JWT token string or null if unavailable
|
|
806
|
+
*/
|
|
807
|
+
async getJWTToken(allowAnonymous) {
|
|
808
|
+
const client = this.getBetterAuthInstance();
|
|
809
|
+
const session = await client.getSession();
|
|
810
|
+
if (session.data?.session?.token) return session.data.session.token;
|
|
811
|
+
if (allowAnonymous) return (await client.getAnonymousToken()).data?.token ?? null;
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
489
814
|
};
|
|
490
815
|
|
|
491
816
|
//#endregion
|
|
492
|
-
export {
|
|
817
|
+
export { CURRENT_TAB_CLIENT_ID as i, BETTER_AUTH_METHODS_CACHE as n, BETTER_AUTH_METHODS_HOOKS as r, NeonAuthAdapterCore as t };
|