@neondatabase/neon-js 0.1.0-alpha.1 → 0.1.0-alpha.11
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 +393 -262
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/{index.js → index.mjs} +6 -6
- package/dist/index.d.mts +110 -0
- package/dist/index.mjs +47 -0
- package/dist/package.json +61 -0
- package/package.json +23 -22
- package/CHANGELOG.md +0 -86
- package/LICENSE +0 -201
- package/dist/index.d.ts +0 -147
- package/dist/index.js +0 -1360
package/dist/index.js
DELETED
|
@@ -1,1360 +0,0 @@
|
|
|
1
|
-
import { AuthApiError, AuthError, isAuthError } from "@supabase/auth-js";
|
|
2
|
-
import { StackClientApp, StackServerApp } from "@stackframe/js";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import { PostgrestClient } from "@supabase/postgrest-js";
|
|
5
|
-
|
|
6
|
-
//#region src/auth/adapters/stack-auth/stack-auth-schemas.ts
|
|
7
|
-
const accessTokenSchema = z.object({
|
|
8
|
-
exp: z.number(),
|
|
9
|
-
iat: z.number(),
|
|
10
|
-
sub: z.string(),
|
|
11
|
-
email: z.string().nullable()
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
//#endregion
|
|
15
|
-
//#region src/auth/adapters/stack-auth/stack-auth-helpers.ts
|
|
16
|
-
/**
|
|
17
|
-
* Environment detection helpers for Stack Auth adapter
|
|
18
|
-
* Based on Supabase's auth-js implementation patterns
|
|
19
|
-
*/
|
|
20
|
-
/**
|
|
21
|
-
* Checks if the code is running in a browser environment
|
|
22
|
-
* @returns true if in browser, false otherwise (e.g., Node.js)
|
|
23
|
-
*/
|
|
24
|
-
const isBrowser = () => {
|
|
25
|
-
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
26
|
-
};
|
|
27
|
-
/**
|
|
28
|
-
* Checks if BroadcastChannel API is available
|
|
29
|
-
* Used for cross-tab authentication state synchronization
|
|
30
|
-
* @returns true if BroadcastChannel is available (browser only)
|
|
31
|
-
*/
|
|
32
|
-
const supportsBroadcastChannel = () => {
|
|
33
|
-
return isBrowser() && typeof globalThis.BroadcastChannel !== "undefined";
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
//#endregion
|
|
37
|
-
//#region src/auth/adapters/stack-auth/stack-auth-adapter.ts
|
|
38
|
-
/**
|
|
39
|
-
* Type guard to check if Stack Auth user has internal session access
|
|
40
|
-
* Stack Auth exposes _internalSession with caching methods that we can leverage
|
|
41
|
-
*/
|
|
42
|
-
function hasInternalSession(user) {
|
|
43
|
-
return user !== null && user !== void 0 && typeof user === "object" && "_internalSession" in user && user._internalSession !== null && user._internalSession !== void 0 && typeof user._internalSession === "object" && "getAccessTokenIfNotExpiredYet" in user._internalSession && typeof user._internalSession.getAccessTokenIfNotExpiredYet === "function" && "_refreshToken" in user._internalSession;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Helper to convert signedUpAt (string or Date) to ISO string
|
|
47
|
-
* Stack Auth SDK may return dates as strings or Date objects depending on context
|
|
48
|
-
*/
|
|
49
|
-
function toISOString(date) {
|
|
50
|
-
if (!date) return (/* @__PURE__ */ new Date()).toISOString();
|
|
51
|
-
if (typeof date === "string") return date;
|
|
52
|
-
if (typeof date === "number") return new Date(date).toISOString();
|
|
53
|
-
return date.toISOString();
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Map Stack Auth errors to Supabase error format
|
|
57
|
-
* This is Stack Auth-specific logic
|
|
58
|
-
*/
|
|
59
|
-
function normalizeStackAuthError(error) {
|
|
60
|
-
if (error !== null && error !== void 0 && typeof error === "object" && "status" in error && error.status === "error" && "error" in error && error.error !== null && error.error !== void 0 && typeof error.error === "object" && "message" in error.error) {
|
|
61
|
-
const stackError = error;
|
|
62
|
-
const message = stackError.error.message || "Authentication failed";
|
|
63
|
-
let status = stackError.httpStatus || 400;
|
|
64
|
-
let code = "unknown_error";
|
|
65
|
-
if (status === 429) code = "over_request_rate_limit";
|
|
66
|
-
else if (status === 422) code = "user_already_exists";
|
|
67
|
-
else if (status === 404) code = "user_not_found";
|
|
68
|
-
else if (status === 401) code = "bad_jwt";
|
|
69
|
-
else if (message.includes("Invalid login credentials") || message.includes("incorrect")) {
|
|
70
|
-
code = "invalid_credentials";
|
|
71
|
-
status = 400;
|
|
72
|
-
} else if (message.includes("already exists") || message.includes("already registered")) {
|
|
73
|
-
code = "user_already_exists";
|
|
74
|
-
status = 422;
|
|
75
|
-
} else if (message.includes("not found")) {
|
|
76
|
-
code = "user_not_found";
|
|
77
|
-
status = 404;
|
|
78
|
-
} else if (message.includes("token") && message.includes("invalid")) {
|
|
79
|
-
code = "bad_jwt";
|
|
80
|
-
status = 401;
|
|
81
|
-
} else if (message.includes("token") && message.includes("expired")) {
|
|
82
|
-
code = "bad_jwt";
|
|
83
|
-
status = 401;
|
|
84
|
-
} else if (message.includes("rate limit") || message.includes("Too many requests")) {
|
|
85
|
-
code = "over_request_rate_limit";
|
|
86
|
-
status = 429;
|
|
87
|
-
} else if (message.includes("email") && message.includes("invalid")) {
|
|
88
|
-
code = "email_address_invalid";
|
|
89
|
-
status = 400;
|
|
90
|
-
}
|
|
91
|
-
return new AuthApiError(message, status, code);
|
|
92
|
-
}
|
|
93
|
-
if (error instanceof Error) {
|
|
94
|
-
const message = error.message;
|
|
95
|
-
let status = 500;
|
|
96
|
-
let code = "unexpected_failure";
|
|
97
|
-
if (message.includes("already exists") || message.includes("already registered")) {
|
|
98
|
-
status = 422;
|
|
99
|
-
code = "user_already_exists";
|
|
100
|
-
} else if (message.includes("Invalid login credentials") || message.includes("incorrect")) {
|
|
101
|
-
status = 400;
|
|
102
|
-
code = "invalid_credentials";
|
|
103
|
-
} else if (message.includes("not found")) {
|
|
104
|
-
status = 404;
|
|
105
|
-
code = "user_not_found";
|
|
106
|
-
} else if (message.includes("token") && message.includes("invalid")) {
|
|
107
|
-
status = 401;
|
|
108
|
-
code = "bad_jwt";
|
|
109
|
-
} else if (message.includes("token") && message.includes("expired")) {
|
|
110
|
-
status = 401;
|
|
111
|
-
code = "bad_jwt";
|
|
112
|
-
} else if (message.includes("rate limit") || message.includes("Too many requests") || message.includes("too many requests")) {
|
|
113
|
-
status = 429;
|
|
114
|
-
code = "over_request_rate_limit";
|
|
115
|
-
} else if (message.includes("email") && message.includes("invalid")) {
|
|
116
|
-
status = 400;
|
|
117
|
-
code = "email_address_invalid";
|
|
118
|
-
}
|
|
119
|
-
if (status !== 500) return new AuthApiError(message, status, code);
|
|
120
|
-
return new AuthError(message, status, code);
|
|
121
|
-
}
|
|
122
|
-
return new AuthError("An unexpected error occurred", 500, "unexpected_failure");
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Stack Auth adapter implementing the AuthClient interface
|
|
126
|
-
*/
|
|
127
|
-
var StackAuthAdapter = class {
|
|
128
|
-
stackAuth;
|
|
129
|
-
stateChangeEmitters = /* @__PURE__ */ new Map();
|
|
130
|
-
broadcastChannel = null;
|
|
131
|
-
tokenRefreshCheckInterval = null;
|
|
132
|
-
config = {
|
|
133
|
-
enableTokenRefreshDetection: true,
|
|
134
|
-
tokenRefreshCheckInterval: 3e4
|
|
135
|
-
};
|
|
136
|
-
constructor(params, config) {
|
|
137
|
-
if (config) this.config = {
|
|
138
|
-
...this.config,
|
|
139
|
-
...config
|
|
140
|
-
};
|
|
141
|
-
this.stackAuth = params.secretServerKey ? new StackServerApp(params) : new StackClientApp(params);
|
|
142
|
-
}
|
|
143
|
-
admin = void 0;
|
|
144
|
-
mfa = void 0;
|
|
145
|
-
initialize = async () => {
|
|
146
|
-
try {
|
|
147
|
-
const session = await this.getSession();
|
|
148
|
-
return {
|
|
149
|
-
data: session.data,
|
|
150
|
-
error: session.error
|
|
151
|
-
};
|
|
152
|
-
} catch (error) {
|
|
153
|
-
return {
|
|
154
|
-
data: { session: null },
|
|
155
|
-
error: normalizeStackAuthError(error)
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
signUp = async (credentials) => {
|
|
160
|
-
try {
|
|
161
|
-
if ("email" in credentials && credentials.email && credentials.password) {
|
|
162
|
-
const verificationCallbackUrl = credentials.options?.emailRedirectTo || this.stackAuth.urls.emailVerification;
|
|
163
|
-
const result = await this.stackAuth.signUpWithCredential({
|
|
164
|
-
email: credentials.email,
|
|
165
|
-
password: credentials.password,
|
|
166
|
-
noRedirect: true,
|
|
167
|
-
verificationCallbackUrl
|
|
168
|
-
});
|
|
169
|
-
if (result.status === "error") return {
|
|
170
|
-
data: {
|
|
171
|
-
user: null,
|
|
172
|
-
session: null
|
|
173
|
-
},
|
|
174
|
-
error: normalizeStackAuthError(result)
|
|
175
|
-
};
|
|
176
|
-
if (credentials.options?.data) {
|
|
177
|
-
const user = await this.stackAuth.getUser();
|
|
178
|
-
if (user) await user.update({ clientMetadata: credentials.options.data });
|
|
179
|
-
}
|
|
180
|
-
const sessionResult = await this._fetchFreshSession();
|
|
181
|
-
if (!sessionResult.data.session?.user) return {
|
|
182
|
-
data: {
|
|
183
|
-
user: null,
|
|
184
|
-
session: null
|
|
185
|
-
},
|
|
186
|
-
error: new AuthError("Failed to retrieve user session", 500, "unexpected_failure")
|
|
187
|
-
};
|
|
188
|
-
const data = {
|
|
189
|
-
user: sessionResult.data.session.user,
|
|
190
|
-
session: sessionResult.data.session
|
|
191
|
-
};
|
|
192
|
-
await this.notifyAllSubscribers("SIGNED_IN", sessionResult.data.session);
|
|
193
|
-
return {
|
|
194
|
-
data,
|
|
195
|
-
error: null
|
|
196
|
-
};
|
|
197
|
-
} else if ("phone" in credentials && credentials.phone) return {
|
|
198
|
-
data: {
|
|
199
|
-
user: null,
|
|
200
|
-
session: null
|
|
201
|
-
},
|
|
202
|
-
error: new AuthError("Phone sign-up not supported", 501, "phone_provider_disabled")
|
|
203
|
-
};
|
|
204
|
-
else return {
|
|
205
|
-
data: {
|
|
206
|
-
user: null,
|
|
207
|
-
session: null
|
|
208
|
-
},
|
|
209
|
-
error: new AuthError("Invalid credentials format", 400, "validation_failed")
|
|
210
|
-
};
|
|
211
|
-
} catch (error) {
|
|
212
|
-
return {
|
|
213
|
-
data: {
|
|
214
|
-
user: null,
|
|
215
|
-
session: null
|
|
216
|
-
},
|
|
217
|
-
error: normalizeStackAuthError(error)
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
};
|
|
221
|
-
signInAnonymously = async () => {
|
|
222
|
-
return {
|
|
223
|
-
data: {
|
|
224
|
-
user: null,
|
|
225
|
-
session: null
|
|
226
|
-
},
|
|
227
|
-
error: new AuthError("Anonymous sign-in is not supported by Stack Auth", 501, "anonymous_provider_disabled")
|
|
228
|
-
};
|
|
229
|
-
};
|
|
230
|
-
signInWithPassword = async (credentials) => {
|
|
231
|
-
try {
|
|
232
|
-
if ("email" in credentials && credentials.email) {
|
|
233
|
-
const result = await this.stackAuth.signInWithCredential({
|
|
234
|
-
email: credentials.email,
|
|
235
|
-
password: credentials.password,
|
|
236
|
-
noRedirect: true
|
|
237
|
-
});
|
|
238
|
-
if (result.status === "error") return {
|
|
239
|
-
data: {
|
|
240
|
-
user: null,
|
|
241
|
-
session: null
|
|
242
|
-
},
|
|
243
|
-
error: normalizeStackAuthError(result)
|
|
244
|
-
};
|
|
245
|
-
const sessionResult = await this._fetchFreshSession();
|
|
246
|
-
if (!sessionResult.data.session?.user) return {
|
|
247
|
-
data: {
|
|
248
|
-
user: null,
|
|
249
|
-
session: null
|
|
250
|
-
},
|
|
251
|
-
error: new AuthError("Failed to retrieve user session", 500, "unexpected_failure")
|
|
252
|
-
};
|
|
253
|
-
const data = {
|
|
254
|
-
user: sessionResult.data.session.user,
|
|
255
|
-
session: sessionResult.data.session
|
|
256
|
-
};
|
|
257
|
-
await this.notifyAllSubscribers("SIGNED_IN", sessionResult.data.session);
|
|
258
|
-
return {
|
|
259
|
-
data,
|
|
260
|
-
error: null
|
|
261
|
-
};
|
|
262
|
-
} else if ("phone" in credentials && credentials.phone) return {
|
|
263
|
-
data: {
|
|
264
|
-
user: null,
|
|
265
|
-
session: null
|
|
266
|
-
},
|
|
267
|
-
error: new AuthError("Phone sign-in not supported", 501, "phone_provider_disabled")
|
|
268
|
-
};
|
|
269
|
-
else return {
|
|
270
|
-
data: {
|
|
271
|
-
user: null,
|
|
272
|
-
session: null
|
|
273
|
-
},
|
|
274
|
-
error: new AuthError("Invalid credentials format", 400, "validation_failed")
|
|
275
|
-
};
|
|
276
|
-
} catch (error) {
|
|
277
|
-
return {
|
|
278
|
-
data: {
|
|
279
|
-
user: null,
|
|
280
|
-
session: null
|
|
281
|
-
},
|
|
282
|
-
error: normalizeStackAuthError(error)
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
};
|
|
286
|
-
signInWithOAuth = async (credentials) => {
|
|
287
|
-
try {
|
|
288
|
-
const { provider, options } = credentials;
|
|
289
|
-
await this.stackAuth.signInWithOAuth(provider, { returnTo: options?.redirectTo });
|
|
290
|
-
return {
|
|
291
|
-
data: {
|
|
292
|
-
provider,
|
|
293
|
-
url: options?.redirectTo || ""
|
|
294
|
-
},
|
|
295
|
-
error: null
|
|
296
|
-
};
|
|
297
|
-
} catch (error) {
|
|
298
|
-
return {
|
|
299
|
-
data: {
|
|
300
|
-
provider: credentials.provider,
|
|
301
|
-
url: null
|
|
302
|
-
},
|
|
303
|
-
error: normalizeStackAuthError(error)
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
};
|
|
307
|
-
signInWithOtp = async (credentials) => {
|
|
308
|
-
try {
|
|
309
|
-
if ("email" in credentials && credentials.email) {
|
|
310
|
-
const callbackUrl = credentials.options?.emailRedirectTo || this.stackAuth.urls.magicLinkCallback;
|
|
311
|
-
const result = await this.stackAuth.sendMagicLinkEmail(credentials.email, { callbackUrl });
|
|
312
|
-
if (result.status === "error") return {
|
|
313
|
-
data: {
|
|
314
|
-
user: null,
|
|
315
|
-
session: null,
|
|
316
|
-
messageId: void 0
|
|
317
|
-
},
|
|
318
|
-
error: normalizeStackAuthError(result)
|
|
319
|
-
};
|
|
320
|
-
return {
|
|
321
|
-
data: {
|
|
322
|
-
user: null,
|
|
323
|
-
session: null,
|
|
324
|
-
messageId: void 0
|
|
325
|
-
},
|
|
326
|
-
error: null
|
|
327
|
-
};
|
|
328
|
-
} else if ("phone" in credentials && credentials.phone) return {
|
|
329
|
-
data: {
|
|
330
|
-
user: null,
|
|
331
|
-
session: null,
|
|
332
|
-
messageId: void 0
|
|
333
|
-
},
|
|
334
|
-
error: new AuthError("Phone OTP not supported", 501, "phone_provider_disabled")
|
|
335
|
-
};
|
|
336
|
-
else return {
|
|
337
|
-
data: {
|
|
338
|
-
user: null,
|
|
339
|
-
session: null,
|
|
340
|
-
messageId: void 0
|
|
341
|
-
},
|
|
342
|
-
error: new AuthError("Invalid credentials format", 400, "validation_failed")
|
|
343
|
-
};
|
|
344
|
-
} catch (error) {
|
|
345
|
-
return {
|
|
346
|
-
data: {
|
|
347
|
-
user: null,
|
|
348
|
-
session: null,
|
|
349
|
-
messageId: void 0
|
|
350
|
-
},
|
|
351
|
-
error: normalizeStackAuthError(error)
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
};
|
|
355
|
-
signInWithIdToken = async (credentials) => {
|
|
356
|
-
/**
|
|
357
|
-
* Stack Auth does not support direct OIDC ID token authentication.
|
|
358
|
-
*
|
|
359
|
-
* Supabase's signInWithIdToken accepts pre-existing OIDC ID tokens from providers like:
|
|
360
|
-
* - Google, Apple, Azure, Facebook, Kakao, Keycloak
|
|
361
|
-
* - Validates the ID token server-side
|
|
362
|
-
* - Can handle tokens with at_hash (requires access_token) and nonce claims
|
|
363
|
-
*
|
|
364
|
-
* Stack Auth uses OAuth authorization code flow with redirects instead:
|
|
365
|
-
* - Requires redirecting users to the OAuth provider
|
|
366
|
-
* - Handles the OAuth callback to exchange authorization code for tokens
|
|
367
|
-
* - Does not accept pre-existing ID tokens directly
|
|
368
|
-
*
|
|
369
|
-
* For OAuth providers, use signInWithOAuth instead:
|
|
370
|
-
* ```
|
|
371
|
-
* await authAdapter.signInWithOAuth({ provider: 'google', options: { redirectTo: '...' } });
|
|
372
|
-
* ```
|
|
373
|
-
*/
|
|
374
|
-
const attemptedProvider = credentials.provider;
|
|
375
|
-
const hasAccessToken = !!credentials.access_token;
|
|
376
|
-
const hasNonce = !!credentials.nonce;
|
|
377
|
-
return {
|
|
378
|
-
data: {
|
|
379
|
-
user: null,
|
|
380
|
-
session: null
|
|
381
|
-
},
|
|
382
|
-
error: new AuthError(`Stack Auth does not support OIDC ID token authentication. Attempted with provider: ${attemptedProvider}${hasAccessToken ? " (with access_token)" : ""}${hasNonce ? " (with nonce)" : ""}. Stack Auth uses OAuth authorization code flow and does not accept pre-existing ID tokens. Please use signInWithOAuth() to redirect users to the OAuth provider for authentication.`, 501, "id_token_provider_disabled")
|
|
383
|
-
};
|
|
384
|
-
};
|
|
385
|
-
signInWithSSO = async (params) => {
|
|
386
|
-
return {
|
|
387
|
-
data: null,
|
|
388
|
-
error: new AuthError(`Stack Auth does not support enterprise SAML SSO. Attempted with ${"providerId" in params ? `provider ID: ${params.providerId}` : `domain: ${"domain" in params ? params.domain : "unknown"}`}. Stack Auth only supports OAuth social providers (Google, GitHub, Microsoft, etc.). Please use signInWithOAuth() for OAuth providers instead.`, 501, "sso_provider_disabled")
|
|
389
|
-
};
|
|
390
|
-
};
|
|
391
|
-
signInWithWeb3 = async (credentials) => {
|
|
392
|
-
/**
|
|
393
|
-
* Stack Auth does not support Web3/crypto wallet authentication (Ethereum, Solana, etc.)
|
|
394
|
-
*
|
|
395
|
-
* Supabase's signInWithWeb3 enables authentication with crypto wallets like:
|
|
396
|
-
* - Ethereum: MetaMask, WalletConnect, Coinbase Wallet (using EIP-1193)
|
|
397
|
-
* - Solana: Phantom, Solflare (using Sign-In with Solana standard)
|
|
398
|
-
*
|
|
399
|
-
* Stack Auth only supports:
|
|
400
|
-
* - OAuth social providers (Google, GitHub, Microsoft, etc.)
|
|
401
|
-
* - Email/Password credentials
|
|
402
|
-
* - Magic link (passwordless email)
|
|
403
|
-
* - Passkey/WebAuthn
|
|
404
|
-
* - Anonymous sign-in
|
|
405
|
-
*
|
|
406
|
-
* For OAuth providers, use signInWithOAuth instead:
|
|
407
|
-
* ```
|
|
408
|
-
* await authAdapter.signInWithOAuth({ provider: 'google', options: { redirectTo: '...' } });
|
|
409
|
-
* ```
|
|
410
|
-
*/
|
|
411
|
-
const attemptedChain = credentials.chain;
|
|
412
|
-
return {
|
|
413
|
-
data: {
|
|
414
|
-
user: null,
|
|
415
|
-
session: null
|
|
416
|
-
},
|
|
417
|
-
error: new AuthError(`Stack Auth does not support Web3 authentication. Attempted with chain: ${attemptedChain}. Stack Auth does not support crypto wallet sign-in (Ethereum, Solana, etc.). Supported authentication methods: OAuth, email/password, magic link, passkey, or anonymous. For social authentication, please use signInWithOAuth() instead.`, 501, "web3_provider_disabled")
|
|
418
|
-
};
|
|
419
|
-
};
|
|
420
|
-
signOut = async () => {
|
|
421
|
-
try {
|
|
422
|
-
const internalSession = await this._getSessionFromStackAuthInternals();
|
|
423
|
-
if (!internalSession) throw new AuthError("No session found", 401, "session_not_found");
|
|
424
|
-
await this.stackAuth._interface.signOut(internalSession);
|
|
425
|
-
await this.notifyAllSubscribers("SIGNED_OUT", null);
|
|
426
|
-
return { error: null };
|
|
427
|
-
} catch (error) {
|
|
428
|
-
return { error: normalizeStackAuthError(error) };
|
|
429
|
-
}
|
|
430
|
-
};
|
|
431
|
-
verifyOtp = async (params) => {
|
|
432
|
-
try {
|
|
433
|
-
if ("email" in params && params.email) {
|
|
434
|
-
const { token, type } = params;
|
|
435
|
-
if (type === "magiclink" || type === "email") {
|
|
436
|
-
let internalSession = await this._getSessionFromStackAuthInternals();
|
|
437
|
-
if (!internalSession) internalSession = this.stackAuth._interface.createSession({
|
|
438
|
-
refreshToken: null,
|
|
439
|
-
accessToken: null
|
|
440
|
-
});
|
|
441
|
-
const result = await this.stackAuth._interface.signInWithMagicLink(token, internalSession);
|
|
442
|
-
if (result.status === "error") return {
|
|
443
|
-
data: {
|
|
444
|
-
user: null,
|
|
445
|
-
session: null
|
|
446
|
-
},
|
|
447
|
-
error: normalizeStackAuthError(result)
|
|
448
|
-
};
|
|
449
|
-
const sessionResult = await this.getSession();
|
|
450
|
-
if (!sessionResult.data.session) return {
|
|
451
|
-
data: {
|
|
452
|
-
user: null,
|
|
453
|
-
session: null
|
|
454
|
-
},
|
|
455
|
-
error: new AuthError("Failed to retrieve session after OTP verification", 500, "unexpected_failure")
|
|
456
|
-
};
|
|
457
|
-
await this.notifyAllSubscribers("SIGNED_IN", sessionResult.data.session);
|
|
458
|
-
return {
|
|
459
|
-
data: {
|
|
460
|
-
user: sessionResult.data.session.user,
|
|
461
|
-
session: sessionResult.data.session
|
|
462
|
-
},
|
|
463
|
-
error: null
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
if (type === "signup" || type === "invite") {
|
|
467
|
-
const result = await this.stackAuth._interface.verifyEmail(token);
|
|
468
|
-
if (result.status === "error") return {
|
|
469
|
-
data: {
|
|
470
|
-
user: null,
|
|
471
|
-
session: null
|
|
472
|
-
},
|
|
473
|
-
error: normalizeStackAuthError(result)
|
|
474
|
-
};
|
|
475
|
-
const sessionResult = await this.getSession();
|
|
476
|
-
return {
|
|
477
|
-
data: {
|
|
478
|
-
user: sessionResult.data.session?.user ?? null,
|
|
479
|
-
session: sessionResult.data.session
|
|
480
|
-
},
|
|
481
|
-
error: null
|
|
482
|
-
};
|
|
483
|
-
}
|
|
484
|
-
if (type === "recovery") {
|
|
485
|
-
const result = await this.stackAuth._interface.resetPassword({
|
|
486
|
-
code: token,
|
|
487
|
-
onlyVerifyCode: true
|
|
488
|
-
});
|
|
489
|
-
if (result.status === "error") return {
|
|
490
|
-
data: {
|
|
491
|
-
user: null,
|
|
492
|
-
session: null
|
|
493
|
-
},
|
|
494
|
-
error: normalizeStackAuthError(result)
|
|
495
|
-
};
|
|
496
|
-
return {
|
|
497
|
-
data: {
|
|
498
|
-
user: null,
|
|
499
|
-
session: null
|
|
500
|
-
},
|
|
501
|
-
error: null
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
if (type === "email_change") {
|
|
505
|
-
const result = await this.stackAuth._interface.verifyEmail(token);
|
|
506
|
-
if (result.status === "error") return {
|
|
507
|
-
data: {
|
|
508
|
-
user: null,
|
|
509
|
-
session: null
|
|
510
|
-
},
|
|
511
|
-
error: normalizeStackAuthError(result)
|
|
512
|
-
};
|
|
513
|
-
const sessionResult = await this.getSession();
|
|
514
|
-
await this.notifyAllSubscribers("USER_UPDATED", sessionResult.data.session);
|
|
515
|
-
return {
|
|
516
|
-
data: {
|
|
517
|
-
user: sessionResult.data.session?.user ?? null,
|
|
518
|
-
session: sessionResult.data.session
|
|
519
|
-
},
|
|
520
|
-
error: null
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
return {
|
|
524
|
-
data: {
|
|
525
|
-
user: null,
|
|
526
|
-
session: null
|
|
527
|
-
},
|
|
528
|
-
error: new AuthError(`Unsupported email OTP type: ${type}`, 400, "validation_failed")
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
if ("phone" in params && params.phone) return {
|
|
532
|
-
data: {
|
|
533
|
-
user: null,
|
|
534
|
-
session: null
|
|
535
|
-
},
|
|
536
|
-
error: new AuthError("Phone OTP verification not supported by Stack Auth", 501, "phone_provider_disabled")
|
|
537
|
-
};
|
|
538
|
-
if ("token_hash" in params && params.token_hash) {
|
|
539
|
-
const { token_hash, type } = params;
|
|
540
|
-
if (type === "magiclink" || type === "email") {
|
|
541
|
-
let internalSession = await this._getSessionFromStackAuthInternals();
|
|
542
|
-
if (!internalSession) internalSession = this.stackAuth._interface.createSession({
|
|
543
|
-
refreshToken: null,
|
|
544
|
-
accessToken: null
|
|
545
|
-
});
|
|
546
|
-
const result = await this.stackAuth._interface.signInWithMagicLink(token_hash, internalSession);
|
|
547
|
-
if (result.status === "error") return {
|
|
548
|
-
data: {
|
|
549
|
-
user: null,
|
|
550
|
-
session: null
|
|
551
|
-
},
|
|
552
|
-
error: normalizeStackAuthError(result)
|
|
553
|
-
};
|
|
554
|
-
const sessionResult = await this.getSession();
|
|
555
|
-
if (!sessionResult.data.session) return {
|
|
556
|
-
data: {
|
|
557
|
-
user: null,
|
|
558
|
-
session: null
|
|
559
|
-
},
|
|
560
|
-
error: new AuthError("Failed to retrieve session after token hash verification", 500, "unexpected_failure")
|
|
561
|
-
};
|
|
562
|
-
await this.notifyAllSubscribers("SIGNED_IN", sessionResult.data.session);
|
|
563
|
-
return {
|
|
564
|
-
data: {
|
|
565
|
-
user: sessionResult.data.session.user,
|
|
566
|
-
session: sessionResult.data.session
|
|
567
|
-
},
|
|
568
|
-
error: null
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
if (type === "signup" || type === "invite") {
|
|
572
|
-
const result = await this.stackAuth._interface.verifyEmail(token_hash);
|
|
573
|
-
if (result.status === "error") return {
|
|
574
|
-
data: {
|
|
575
|
-
user: null,
|
|
576
|
-
session: null
|
|
577
|
-
},
|
|
578
|
-
error: normalizeStackAuthError(result)
|
|
579
|
-
};
|
|
580
|
-
const sessionResult = await this.getSession();
|
|
581
|
-
return {
|
|
582
|
-
data: {
|
|
583
|
-
user: sessionResult.data.session?.user ?? null,
|
|
584
|
-
session: sessionResult.data.session
|
|
585
|
-
},
|
|
586
|
-
error: null
|
|
587
|
-
};
|
|
588
|
-
}
|
|
589
|
-
if (type === "recovery") {
|
|
590
|
-
const result = await this.stackAuth._interface.resetPassword({
|
|
591
|
-
code: token_hash,
|
|
592
|
-
onlyVerifyCode: true
|
|
593
|
-
});
|
|
594
|
-
if (result.status === "error") return {
|
|
595
|
-
data: {
|
|
596
|
-
user: null,
|
|
597
|
-
session: null
|
|
598
|
-
},
|
|
599
|
-
error: normalizeStackAuthError(result)
|
|
600
|
-
};
|
|
601
|
-
return {
|
|
602
|
-
data: {
|
|
603
|
-
user: null,
|
|
604
|
-
session: null
|
|
605
|
-
},
|
|
606
|
-
error: null
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
if (type === "email_change") {
|
|
610
|
-
const result = await this.stackAuth._interface.verifyEmail(token_hash);
|
|
611
|
-
if (result.status === "error") return {
|
|
612
|
-
data: {
|
|
613
|
-
user: null,
|
|
614
|
-
session: null
|
|
615
|
-
},
|
|
616
|
-
error: normalizeStackAuthError(result)
|
|
617
|
-
};
|
|
618
|
-
const sessionResult = await this.getSession();
|
|
619
|
-
await this.notifyAllSubscribers("USER_UPDATED", sessionResult.data.session);
|
|
620
|
-
return {
|
|
621
|
-
data: {
|
|
622
|
-
user: sessionResult.data.session?.user ?? null,
|
|
623
|
-
session: sessionResult.data.session
|
|
624
|
-
},
|
|
625
|
-
error: null
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
return {
|
|
629
|
-
data: {
|
|
630
|
-
user: null,
|
|
631
|
-
session: null
|
|
632
|
-
},
|
|
633
|
-
error: new AuthError(`Unsupported token hash OTP type: ${type}`, 400, "validation_failed")
|
|
634
|
-
};
|
|
635
|
-
}
|
|
636
|
-
return {
|
|
637
|
-
data: {
|
|
638
|
-
user: null,
|
|
639
|
-
session: null
|
|
640
|
-
},
|
|
641
|
-
error: new AuthError("Invalid OTP verification parameters", 400, "validation_failed")
|
|
642
|
-
};
|
|
643
|
-
} catch (error) {
|
|
644
|
-
return {
|
|
645
|
-
data: {
|
|
646
|
-
user: null,
|
|
647
|
-
session: null
|
|
648
|
-
},
|
|
649
|
-
error: normalizeStackAuthError(error)
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
};
|
|
653
|
-
/**
|
|
654
|
-
* Fetches fresh session data from Stack Auth API.
|
|
655
|
-
* Always makes a network request to get the latest user metadata.
|
|
656
|
-
* Used when we need fresh data (after setSession, refreshSession, etc.)
|
|
657
|
-
*/
|
|
658
|
-
async _fetchFreshSession() {
|
|
659
|
-
try {
|
|
660
|
-
const user = await this.stackAuth.getUser();
|
|
661
|
-
if (user) {
|
|
662
|
-
const tokens = await user.currentSession.getTokens();
|
|
663
|
-
if (tokens.accessToken) {
|
|
664
|
-
const payload = accessTokenSchema.parse(JSON.parse(atob(tokens.accessToken.split(".")[1])));
|
|
665
|
-
return {
|
|
666
|
-
data: { session: {
|
|
667
|
-
access_token: tokens.accessToken,
|
|
668
|
-
refresh_token: tokens.refreshToken ?? "",
|
|
669
|
-
expires_at: payload.exp,
|
|
670
|
-
expires_in: Math.max(0, payload.exp - Math.floor(Date.now() / 1e3)),
|
|
671
|
-
token_type: "bearer",
|
|
672
|
-
user: {
|
|
673
|
-
id: user.id,
|
|
674
|
-
email: user.primaryEmail || "",
|
|
675
|
-
email_confirmed_at: user.primaryEmailVerified ? toISOString(user.signedUpAt) : void 0,
|
|
676
|
-
last_sign_in_at: toISOString(user.signedUpAt),
|
|
677
|
-
created_at: toISOString(user.signedUpAt),
|
|
678
|
-
updated_at: toISOString(user.signedUpAt),
|
|
679
|
-
aud: "authenticated",
|
|
680
|
-
role: "authenticated",
|
|
681
|
-
app_metadata: user.clientReadOnlyMetadata,
|
|
682
|
-
user_metadata: {
|
|
683
|
-
displayName: user.displayName,
|
|
684
|
-
profileImageUrl: user.profileImageUrl,
|
|
685
|
-
...user.clientMetadata
|
|
686
|
-
},
|
|
687
|
-
identities: []
|
|
688
|
-
}
|
|
689
|
-
} },
|
|
690
|
-
error: null
|
|
691
|
-
};
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
return {
|
|
695
|
-
data: { session: null },
|
|
696
|
-
error: null
|
|
697
|
-
};
|
|
698
|
-
} catch (error) {
|
|
699
|
-
console.error("Error fetching fresh session:", error);
|
|
700
|
-
return {
|
|
701
|
-
data: { session: null },
|
|
702
|
-
error: normalizeStackAuthError(error)
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
getSession = async () => {
|
|
707
|
-
try {
|
|
708
|
-
const cachedTokens = await this._getCachedTokensFromStackAuthInternals();
|
|
709
|
-
if (cachedTokens?.accessToken) {
|
|
710
|
-
const payload = accessTokenSchema.parse(JSON.parse(atob(cachedTokens.accessToken.split(".")[1])));
|
|
711
|
-
return {
|
|
712
|
-
data: { session: {
|
|
713
|
-
access_token: cachedTokens.accessToken,
|
|
714
|
-
refresh_token: cachedTokens.refreshToken ?? "",
|
|
715
|
-
expires_at: payload.exp,
|
|
716
|
-
expires_in: Math.max(0, payload.exp - Math.floor(Date.now() / 1e3)),
|
|
717
|
-
token_type: "bearer",
|
|
718
|
-
user: {
|
|
719
|
-
id: payload.sub,
|
|
720
|
-
email: payload.email || "",
|
|
721
|
-
created_at: (/* @__PURE__ */ new Date(payload.iat * 1e3)).toISOString(),
|
|
722
|
-
aud: "authenticated",
|
|
723
|
-
role: "authenticated",
|
|
724
|
-
app_metadata: {},
|
|
725
|
-
user_metadata: {}
|
|
726
|
-
}
|
|
727
|
-
} },
|
|
728
|
-
error: null
|
|
729
|
-
};
|
|
730
|
-
}
|
|
731
|
-
return await this._fetchFreshSession();
|
|
732
|
-
} catch (error) {
|
|
733
|
-
console.error("Error getting session:", error);
|
|
734
|
-
return {
|
|
735
|
-
data: { session: null },
|
|
736
|
-
error: normalizeStackAuthError(error)
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
};
|
|
740
|
-
refreshSession = async () => {
|
|
741
|
-
try {
|
|
742
|
-
const sessionResult = await this.getSession();
|
|
743
|
-
if (sessionResult.error) return {
|
|
744
|
-
data: {
|
|
745
|
-
user: null,
|
|
746
|
-
session: null
|
|
747
|
-
},
|
|
748
|
-
error: sessionResult.error
|
|
749
|
-
};
|
|
750
|
-
return {
|
|
751
|
-
data: {
|
|
752
|
-
user: sessionResult.data.session?.user ?? null,
|
|
753
|
-
session: sessionResult.data.session
|
|
754
|
-
},
|
|
755
|
-
error: null
|
|
756
|
-
};
|
|
757
|
-
} catch (error) {
|
|
758
|
-
return {
|
|
759
|
-
data: {
|
|
760
|
-
user: null,
|
|
761
|
-
session: null
|
|
762
|
-
},
|
|
763
|
-
error: normalizeStackAuthError(error)
|
|
764
|
-
};
|
|
765
|
-
}
|
|
766
|
-
};
|
|
767
|
-
setSession = async () => {
|
|
768
|
-
return {
|
|
769
|
-
data: {
|
|
770
|
-
user: null,
|
|
771
|
-
session: null
|
|
772
|
-
},
|
|
773
|
-
error: new AuthError("Setting external sessions is not supported by Stack Auth", 501, "not_implemented")
|
|
774
|
-
};
|
|
775
|
-
};
|
|
776
|
-
getUser = async () => {
|
|
777
|
-
try {
|
|
778
|
-
const user = await this.stackAuth.getUser();
|
|
779
|
-
if (!user) return {
|
|
780
|
-
data: { user: null },
|
|
781
|
-
error: new AuthError("No user session found", 401, "session_not_found")
|
|
782
|
-
};
|
|
783
|
-
return {
|
|
784
|
-
data: { user: {
|
|
785
|
-
id: user.id,
|
|
786
|
-
aud: "authenticated",
|
|
787
|
-
role: "authenticated",
|
|
788
|
-
email: user.primaryEmail || "",
|
|
789
|
-
email_confirmed_at: user.primaryEmailVerified ? toISOString(user.signedUpAt) : void 0,
|
|
790
|
-
phone: void 0,
|
|
791
|
-
confirmed_at: user.primaryEmailVerified ? toISOString(user.signedUpAt) : void 0,
|
|
792
|
-
last_sign_in_at: toISOString(user.signedUpAt),
|
|
793
|
-
app_metadata: {},
|
|
794
|
-
user_metadata: {
|
|
795
|
-
...user.clientMetadata,
|
|
796
|
-
...user.displayName ? { displayName: user.displayName } : {},
|
|
797
|
-
...user.profileImageUrl ? { profileImageUrl: user.profileImageUrl } : {}
|
|
798
|
-
},
|
|
799
|
-
identities: [],
|
|
800
|
-
created_at: toISOString(user.signedUpAt),
|
|
801
|
-
updated_at: toISOString(user.signedUpAt)
|
|
802
|
-
} },
|
|
803
|
-
error: null
|
|
804
|
-
};
|
|
805
|
-
} catch (error) {
|
|
806
|
-
return {
|
|
807
|
-
data: { user: null },
|
|
808
|
-
error: normalizeStackAuthError(error)
|
|
809
|
-
};
|
|
810
|
-
}
|
|
811
|
-
};
|
|
812
|
-
getClaims = async () => {
|
|
813
|
-
try {
|
|
814
|
-
const user = await this.stackAuth.getUser();
|
|
815
|
-
if (!user) return {
|
|
816
|
-
data: null,
|
|
817
|
-
error: new AuthError("No user session found", 401, "session_not_found")
|
|
818
|
-
};
|
|
819
|
-
let accessToken = null;
|
|
820
|
-
if (hasInternalSession(user)) accessToken = user._internalSession.getAccessTokenIfNotExpiredYet(0)?.token ?? null;
|
|
821
|
-
if (!accessToken) accessToken = (await user.currentSession.getTokens()).accessToken;
|
|
822
|
-
if (!accessToken) return {
|
|
823
|
-
data: null,
|
|
824
|
-
error: new AuthError("No access token found", 401, "session_not_found")
|
|
825
|
-
};
|
|
826
|
-
const tokenParts = accessToken.split(".");
|
|
827
|
-
if (tokenParts.length !== 3) return {
|
|
828
|
-
data: null,
|
|
829
|
-
error: new AuthError("Invalid token format", 401, "bad_jwt")
|
|
830
|
-
};
|
|
831
|
-
try {
|
|
832
|
-
return {
|
|
833
|
-
data: JSON.parse(atob(tokenParts[1])),
|
|
834
|
-
error: null
|
|
835
|
-
};
|
|
836
|
-
} catch {
|
|
837
|
-
return {
|
|
838
|
-
data: null,
|
|
839
|
-
error: new AuthError("Failed to decode token", 401, "bad_jwt")
|
|
840
|
-
};
|
|
841
|
-
}
|
|
842
|
-
} catch (error) {
|
|
843
|
-
return {
|
|
844
|
-
data: null,
|
|
845
|
-
error: normalizeStackAuthError(error)
|
|
846
|
-
};
|
|
847
|
-
}
|
|
848
|
-
};
|
|
849
|
-
updateUser = async (attributes) => {
|
|
850
|
-
try {
|
|
851
|
-
const user = await this.stackAuth.getUser();
|
|
852
|
-
if (!user) return {
|
|
853
|
-
data: { user: null },
|
|
854
|
-
error: new AuthError("No user session found", 401, "session_not_found")
|
|
855
|
-
};
|
|
856
|
-
if (attributes.password) return {
|
|
857
|
-
data: { user: null },
|
|
858
|
-
error: new AuthError("Password updates require reauthentication. Stack Auth does not support the nonce-based reauthentication flow (reauthenticate() method). For password changes, users must: 1) Sign out, 2) Use \"Forgot Password\" flow (resetPasswordForEmail), or 3) Use Stack Auth directly with updatePassword({ oldPassword, newPassword }).", 400, "feature_not_supported")
|
|
859
|
-
};
|
|
860
|
-
const updateData = {};
|
|
861
|
-
if (attributes.data) {
|
|
862
|
-
const data$1 = attributes.data;
|
|
863
|
-
if (data$1 && "displayName" in data$1 && typeof data$1.displayName === "string") updateData.displayName = data$1.displayName;
|
|
864
|
-
if (data$1 && "profileImageUrl" in data$1 && typeof data$1.profileImageUrl === "string") updateData.profileImageUrl = data$1.profileImageUrl;
|
|
865
|
-
updateData.clientMetadata = {
|
|
866
|
-
...user.clientMetadata,
|
|
867
|
-
...attributes.data
|
|
868
|
-
};
|
|
869
|
-
}
|
|
870
|
-
await user.update(updateData);
|
|
871
|
-
if (attributes.email) console.warn("Email updates require server-side Stack Auth configuration");
|
|
872
|
-
const updatedUser = await this.stackAuth.getUser();
|
|
873
|
-
if (!updatedUser) throw new Error("Failed to retrieve updated user");
|
|
874
|
-
const user_metadata = {
|
|
875
|
-
...updatedUser.clientMetadata,
|
|
876
|
-
...updatedUser.displayName ? { displayName: updatedUser.displayName } : {},
|
|
877
|
-
...updatedUser.profileImageUrl ? { profileImageUrl: updatedUser.profileImageUrl } : {}
|
|
878
|
-
};
|
|
879
|
-
const data = { user: {
|
|
880
|
-
id: updatedUser.id,
|
|
881
|
-
aud: "authenticated",
|
|
882
|
-
role: "authenticated",
|
|
883
|
-
email: updatedUser.primaryEmail || "",
|
|
884
|
-
email_confirmed_at: updatedUser.primaryEmailVerified ? toISOString(updatedUser.signedUpAt) : void 0,
|
|
885
|
-
phone: void 0,
|
|
886
|
-
confirmed_at: updatedUser.primaryEmailVerified ? toISOString(updatedUser.signedUpAt) : void 0,
|
|
887
|
-
last_sign_in_at: toISOString(updatedUser.signedUpAt),
|
|
888
|
-
app_metadata: {},
|
|
889
|
-
user_metadata,
|
|
890
|
-
identities: [],
|
|
891
|
-
created_at: toISOString(updatedUser.signedUpAt),
|
|
892
|
-
updated_at: toISOString(updatedUser.signedUpAt)
|
|
893
|
-
} };
|
|
894
|
-
const sessionResult = await this.getSession();
|
|
895
|
-
await this.notifyAllSubscribers("USER_UPDATED", sessionResult.data.session);
|
|
896
|
-
return {
|
|
897
|
-
data,
|
|
898
|
-
error: null
|
|
899
|
-
};
|
|
900
|
-
} catch (error) {
|
|
901
|
-
return {
|
|
902
|
-
data: { user: null },
|
|
903
|
-
error: normalizeStackAuthError(error)
|
|
904
|
-
};
|
|
905
|
-
}
|
|
906
|
-
};
|
|
907
|
-
getUserIdentities = async () => {
|
|
908
|
-
try {
|
|
909
|
-
const user = await this.stackAuth.getUser();
|
|
910
|
-
if (!user) return {
|
|
911
|
-
data: null,
|
|
912
|
-
error: new AuthError("No user session found", 401, "session_not_found")
|
|
913
|
-
};
|
|
914
|
-
return {
|
|
915
|
-
data: { identities: (await user.listOAuthProviders()).map((provider) => ({
|
|
916
|
-
id: provider.id,
|
|
917
|
-
user_id: user.id,
|
|
918
|
-
identity_id: provider.id,
|
|
919
|
-
provider: provider.type,
|
|
920
|
-
identity_data: {
|
|
921
|
-
email: provider.email || null,
|
|
922
|
-
account_id: provider.accountId || null,
|
|
923
|
-
provider_type: provider.type,
|
|
924
|
-
user_id: provider.userId,
|
|
925
|
-
allow_sign_in: provider.allowSignIn,
|
|
926
|
-
allow_connected_accounts: provider.allowConnectedAccounts
|
|
927
|
-
},
|
|
928
|
-
created_at: toISOString(user.signedUpAt),
|
|
929
|
-
last_sign_in_at: toISOString(user.signedUpAt),
|
|
930
|
-
updated_at: toISOString(user.signedUpAt)
|
|
931
|
-
})) },
|
|
932
|
-
error: null
|
|
933
|
-
};
|
|
934
|
-
} catch (error) {
|
|
935
|
-
return {
|
|
936
|
-
data: null,
|
|
937
|
-
error: normalizeStackAuthError(error)
|
|
938
|
-
};
|
|
939
|
-
}
|
|
940
|
-
};
|
|
941
|
-
linkIdentity = async (credentials) => {
|
|
942
|
-
try {
|
|
943
|
-
const user = await this.stackAuth.getUser();
|
|
944
|
-
if (!user) return {
|
|
945
|
-
data: {
|
|
946
|
-
provider: credentials.provider,
|
|
947
|
-
url: null
|
|
948
|
-
},
|
|
949
|
-
error: new AuthError("No user session found", 401, "session_not_found")
|
|
950
|
-
};
|
|
951
|
-
const scopes = credentials.options?.scopes ? credentials.options.scopes.split(" ") : void 0;
|
|
952
|
-
await user.getConnectedAccount(credentials.provider, {
|
|
953
|
-
or: "redirect",
|
|
954
|
-
scopes
|
|
955
|
-
});
|
|
956
|
-
return {
|
|
957
|
-
data: {
|
|
958
|
-
provider: credentials.provider,
|
|
959
|
-
url: credentials.options?.redirectTo || ""
|
|
960
|
-
},
|
|
961
|
-
error: null
|
|
962
|
-
};
|
|
963
|
-
} catch (error) {
|
|
964
|
-
return {
|
|
965
|
-
data: {
|
|
966
|
-
provider: credentials.provider,
|
|
967
|
-
url: null
|
|
968
|
-
},
|
|
969
|
-
error: normalizeStackAuthError(error)
|
|
970
|
-
};
|
|
971
|
-
}
|
|
972
|
-
};
|
|
973
|
-
unlinkIdentity = async (identity) => {
|
|
974
|
-
try {
|
|
975
|
-
const user = await this.stackAuth.getUser();
|
|
976
|
-
if (!user) return {
|
|
977
|
-
data: null,
|
|
978
|
-
error: new AuthError("No user session found", 401, "session_not_found")
|
|
979
|
-
};
|
|
980
|
-
const provider = await user.getOAuthProvider(identity.identity_id);
|
|
981
|
-
if (!provider) return {
|
|
982
|
-
data: null,
|
|
983
|
-
error: new AuthError(`OAuth provider with ID ${identity.identity_id} not found`, 404, "identity_not_found")
|
|
984
|
-
};
|
|
985
|
-
await provider.delete();
|
|
986
|
-
const sessionResult = await this.getSession();
|
|
987
|
-
await this.notifyAllSubscribers("USER_UPDATED", sessionResult.data.session);
|
|
988
|
-
return {
|
|
989
|
-
data: {},
|
|
990
|
-
error: null
|
|
991
|
-
};
|
|
992
|
-
} catch (error) {
|
|
993
|
-
return {
|
|
994
|
-
data: null,
|
|
995
|
-
error: normalizeStackAuthError(error)
|
|
996
|
-
};
|
|
997
|
-
}
|
|
998
|
-
};
|
|
999
|
-
resetPasswordForEmail = async (email, options) => {
|
|
1000
|
-
try {
|
|
1001
|
-
const result = await this.stackAuth.sendForgotPasswordEmail(email, { callbackUrl: options?.redirectTo });
|
|
1002
|
-
if (result.status === "error") return {
|
|
1003
|
-
data: null,
|
|
1004
|
-
error: normalizeStackAuthError(result)
|
|
1005
|
-
};
|
|
1006
|
-
return {
|
|
1007
|
-
data: {},
|
|
1008
|
-
error: null
|
|
1009
|
-
};
|
|
1010
|
-
} catch (error) {
|
|
1011
|
-
return {
|
|
1012
|
-
data: null,
|
|
1013
|
-
error: normalizeStackAuthError(error)
|
|
1014
|
-
};
|
|
1015
|
-
}
|
|
1016
|
-
};
|
|
1017
|
-
reauthenticate = async () => {
|
|
1018
|
-
return {
|
|
1019
|
-
data: {
|
|
1020
|
-
user: null,
|
|
1021
|
-
session: null
|
|
1022
|
-
},
|
|
1023
|
-
error: new AuthError("Stack Auth does not support nonce-based reauthentication. For password changes, use the password reset flow (resetPasswordForEmail) or access Stack Auth directly.", 400, "feature_not_supported")
|
|
1024
|
-
};
|
|
1025
|
-
};
|
|
1026
|
-
resend = async (credentials) => {
|
|
1027
|
-
try {
|
|
1028
|
-
if ("email" in credentials) {
|
|
1029
|
-
const { email, type, options } = credentials;
|
|
1030
|
-
if (type === "signup") {
|
|
1031
|
-
const user = await this.stackAuth.getUser();
|
|
1032
|
-
if (user && user.primaryEmail === email) await user.sendVerificationEmail();
|
|
1033
|
-
else {
|
|
1034
|
-
const result = await this.stackAuth.sendMagicLinkEmail(email, { callbackUrl: options?.emailRedirectTo });
|
|
1035
|
-
if (result.status === "error") return {
|
|
1036
|
-
data: {
|
|
1037
|
-
user: null,
|
|
1038
|
-
session: null
|
|
1039
|
-
},
|
|
1040
|
-
error: normalizeStackAuthError(result)
|
|
1041
|
-
};
|
|
1042
|
-
}
|
|
1043
|
-
return {
|
|
1044
|
-
data: {
|
|
1045
|
-
user: null,
|
|
1046
|
-
session: null
|
|
1047
|
-
},
|
|
1048
|
-
error: null
|
|
1049
|
-
};
|
|
1050
|
-
}
|
|
1051
|
-
if (type === "email_change") {
|
|
1052
|
-
const user = await this.stackAuth.getUser();
|
|
1053
|
-
if (!user) return {
|
|
1054
|
-
data: {
|
|
1055
|
-
user: null,
|
|
1056
|
-
session: null
|
|
1057
|
-
},
|
|
1058
|
-
error: new AuthError("No user session found", 401, "session_not_found")
|
|
1059
|
-
};
|
|
1060
|
-
const targetChannel = (await user.listContactChannels()).find((ch) => ch.value === email && ch.type === "email");
|
|
1061
|
-
if (!targetChannel) return {
|
|
1062
|
-
data: {
|
|
1063
|
-
user: null,
|
|
1064
|
-
session: null
|
|
1065
|
-
},
|
|
1066
|
-
error: new AuthError("Email not found in user contact channels", 404, "email_not_found")
|
|
1067
|
-
};
|
|
1068
|
-
await targetChannel.sendVerificationEmail({ callbackUrl: options?.emailRedirectTo });
|
|
1069
|
-
return {
|
|
1070
|
-
data: {
|
|
1071
|
-
user: null,
|
|
1072
|
-
session: null
|
|
1073
|
-
},
|
|
1074
|
-
error: null
|
|
1075
|
-
};
|
|
1076
|
-
}
|
|
1077
|
-
return {
|
|
1078
|
-
data: {
|
|
1079
|
-
user: null,
|
|
1080
|
-
session: null
|
|
1081
|
-
},
|
|
1082
|
-
error: new AuthError(`Unsupported resend type: ${type}`, 400, "validation_failed")
|
|
1083
|
-
};
|
|
1084
|
-
}
|
|
1085
|
-
if ("phone" in credentials) return {
|
|
1086
|
-
data: {
|
|
1087
|
-
user: null,
|
|
1088
|
-
session: null
|
|
1089
|
-
},
|
|
1090
|
-
error: new AuthError("Phone OTP resend not supported by Stack Auth", 501, "phone_provider_disabled")
|
|
1091
|
-
};
|
|
1092
|
-
return {
|
|
1093
|
-
data: {
|
|
1094
|
-
user: null,
|
|
1095
|
-
session: null
|
|
1096
|
-
},
|
|
1097
|
-
error: new AuthError("Invalid credentials format", 400, "validation_failed")
|
|
1098
|
-
};
|
|
1099
|
-
} catch (error) {
|
|
1100
|
-
return {
|
|
1101
|
-
data: {
|
|
1102
|
-
user: null,
|
|
1103
|
-
session: null
|
|
1104
|
-
},
|
|
1105
|
-
error: normalizeStackAuthError(error)
|
|
1106
|
-
};
|
|
1107
|
-
}
|
|
1108
|
-
};
|
|
1109
|
-
onAuthStateChange = (callback) => {
|
|
1110
|
-
const id = crypto.randomUUID();
|
|
1111
|
-
const subscription = {
|
|
1112
|
-
id,
|
|
1113
|
-
callback,
|
|
1114
|
-
unsubscribe: () => {
|
|
1115
|
-
this.stateChangeEmitters.delete(id);
|
|
1116
|
-
if (this.stateChangeEmitters.size === 0) {
|
|
1117
|
-
this.stopTokenRefreshDetection();
|
|
1118
|
-
this.closeBroadcastChannel();
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
};
|
|
1122
|
-
this.stateChangeEmitters.set(id, subscription);
|
|
1123
|
-
if (this.stateChangeEmitters.size === 1) {
|
|
1124
|
-
this.initializeBroadcastChannel();
|
|
1125
|
-
this.startTokenRefreshDetection();
|
|
1126
|
-
}
|
|
1127
|
-
this.emitInitialSession(callback);
|
|
1128
|
-
return { data: { subscription: {
|
|
1129
|
-
id,
|
|
1130
|
-
callback,
|
|
1131
|
-
unsubscribe: subscription.unsubscribe
|
|
1132
|
-
} } };
|
|
1133
|
-
};
|
|
1134
|
-
async _getSessionFromStackAuthInternals() {
|
|
1135
|
-
const tokenStore = await this.stackAuth._getOrCreateTokenStore(await this.stackAuth._createCookieHelper());
|
|
1136
|
-
return this.stackAuth._getSessionFromTokenStore(tokenStore);
|
|
1137
|
-
}
|
|
1138
|
-
async _getCachedTokensFromStackAuthInternals() {
|
|
1139
|
-
try {
|
|
1140
|
-
const session = await this._getSessionFromStackAuthInternals();
|
|
1141
|
-
const accessToken = session?.getAccessTokenIfNotExpiredYet(0);
|
|
1142
|
-
if (!accessToken) return null;
|
|
1143
|
-
return {
|
|
1144
|
-
accessToken: accessToken.token,
|
|
1145
|
-
refreshToken: session?._refreshToken?.token ?? null
|
|
1146
|
-
};
|
|
1147
|
-
} catch {
|
|
1148
|
-
return null;
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
async emitInitialSession(callback) {
|
|
1152
|
-
try {
|
|
1153
|
-
const { data, error } = await this.getSession();
|
|
1154
|
-
if (error) {
|
|
1155
|
-
await callback("INITIAL_SESSION", null);
|
|
1156
|
-
return;
|
|
1157
|
-
}
|
|
1158
|
-
await callback("INITIAL_SESSION", data.session);
|
|
1159
|
-
} catch (error) {
|
|
1160
|
-
await callback("INITIAL_SESSION", null);
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
async notifyAllSubscribers(event, session, broadcast = true) {
|
|
1164
|
-
if (broadcast && this.broadcastChannel) try {
|
|
1165
|
-
this.broadcastChannel.postMessage({
|
|
1166
|
-
event,
|
|
1167
|
-
session,
|
|
1168
|
-
timestamp: Date.now()
|
|
1169
|
-
});
|
|
1170
|
-
} catch (error) {
|
|
1171
|
-
console.warn("BroadcastChannel postMessage failed:", error);
|
|
1172
|
-
}
|
|
1173
|
-
const promises = Array.from(this.stateChangeEmitters.values()).map((subscription) => {
|
|
1174
|
-
try {
|
|
1175
|
-
return Promise.resolve(subscription.callback(event, session));
|
|
1176
|
-
} catch (error) {
|
|
1177
|
-
console.error("Auth state change callback error:", error);
|
|
1178
|
-
return Promise.resolve();
|
|
1179
|
-
}
|
|
1180
|
-
});
|
|
1181
|
-
await Promise.allSettled(promises);
|
|
1182
|
-
}
|
|
1183
|
-
initializeBroadcastChannel() {
|
|
1184
|
-
if (!supportsBroadcastChannel()) return;
|
|
1185
|
-
if (!this.broadcastChannel) try {
|
|
1186
|
-
this.broadcastChannel = new BroadcastChannel("stack-auth-state-changes");
|
|
1187
|
-
this.broadcastChannel.onmessage = async (event) => {
|
|
1188
|
-
const { event: authEvent, session } = event.data;
|
|
1189
|
-
await this.notifyAllSubscribers(authEvent, session, false);
|
|
1190
|
-
};
|
|
1191
|
-
} catch (error) {
|
|
1192
|
-
console.error("Failed to create BroadcastChannel, cross-tab sync will not be available:", error);
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
closeBroadcastChannel() {
|
|
1196
|
-
if (this.broadcastChannel) {
|
|
1197
|
-
this.broadcastChannel.close();
|
|
1198
|
-
this.broadcastChannel = null;
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
startTokenRefreshDetection() {
|
|
1202
|
-
if (!this.config.enableTokenRefreshDetection) return;
|
|
1203
|
-
if (this.tokenRefreshCheckInterval) return;
|
|
1204
|
-
this.tokenRefreshCheckInterval = setInterval(async () => {
|
|
1205
|
-
try {
|
|
1206
|
-
const sessionResult = await this.getSession();
|
|
1207
|
-
if (!sessionResult.data.session) return;
|
|
1208
|
-
const session = sessionResult.data.session;
|
|
1209
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
1210
|
-
const expiresInSeconds = (session.expires_at ?? now) - now;
|
|
1211
|
-
if (expiresInSeconds <= 0) {
|
|
1212
|
-
await this.notifyAllSubscribers("SIGNED_OUT", null);
|
|
1213
|
-
return;
|
|
1214
|
-
}
|
|
1215
|
-
if (expiresInSeconds <= 90 && expiresInSeconds > 0) await this.notifyAllSubscribers("TOKEN_REFRESHED", session);
|
|
1216
|
-
} catch (error) {
|
|
1217
|
-
console.error("Token refresh detection error:", error);
|
|
1218
|
-
}
|
|
1219
|
-
}, this.config.tokenRefreshCheckInterval);
|
|
1220
|
-
}
|
|
1221
|
-
stopTokenRefreshDetection() {
|
|
1222
|
-
if (this.tokenRefreshCheckInterval) {
|
|
1223
|
-
clearInterval(this.tokenRefreshCheckInterval);
|
|
1224
|
-
this.tokenRefreshCheckInterval = null;
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
/**
|
|
1228
|
-
* Exchange an OAuth authorization code for a session.
|
|
1229
|
-
*
|
|
1230
|
-
* Note: Stack Auth handles OAuth callbacks automatically via callOAuthCallback().
|
|
1231
|
-
* This method delegates to Stack Auth's internal flow which:
|
|
1232
|
-
* - Retrieves the code and state from the current URL
|
|
1233
|
-
* - Retrieves the PKCE verifier from cookies (stored during signInWithOAuth)
|
|
1234
|
-
* - Exchanges the code for access/refresh tokens
|
|
1235
|
-
* - Creates and stores the user session
|
|
1236
|
-
*
|
|
1237
|
-
* @param authCode - The authorization code (Stack Auth reads this from URL automatically)
|
|
1238
|
-
* @returns Session data or error
|
|
1239
|
-
*/
|
|
1240
|
-
exchangeCodeForSession = async (_authCode) => {
|
|
1241
|
-
try {
|
|
1242
|
-
if (await this.stackAuth.callOAuthCallback()) {
|
|
1243
|
-
const sessionResult = await this.getSession();
|
|
1244
|
-
if (sessionResult.data.session) {
|
|
1245
|
-
await this.notifyAllSubscribers("SIGNED_IN", sessionResult.data.session);
|
|
1246
|
-
return {
|
|
1247
|
-
data: {
|
|
1248
|
-
session: sessionResult.data.session,
|
|
1249
|
-
user: sessionResult.data.session.user
|
|
1250
|
-
},
|
|
1251
|
-
error: null
|
|
1252
|
-
};
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
return {
|
|
1256
|
-
data: {
|
|
1257
|
-
session: null,
|
|
1258
|
-
user: null
|
|
1259
|
-
},
|
|
1260
|
-
error: new AuthError("OAuth callback completed but no session was created", 500, "oauth_callback_failed")
|
|
1261
|
-
};
|
|
1262
|
-
} catch (error) {
|
|
1263
|
-
return {
|
|
1264
|
-
data: {
|
|
1265
|
-
session: null,
|
|
1266
|
-
user: null
|
|
1267
|
-
},
|
|
1268
|
-
error: normalizeStackAuthError(error)
|
|
1269
|
-
};
|
|
1270
|
-
}
|
|
1271
|
-
};
|
|
1272
|
-
startAutoRefresh = async () => {
|
|
1273
|
-
return Promise.resolve();
|
|
1274
|
-
};
|
|
1275
|
-
stopAutoRefresh = async () => {
|
|
1276
|
-
return Promise.resolve();
|
|
1277
|
-
};
|
|
1278
|
-
};
|
|
1279
|
-
|
|
1280
|
-
//#endregion
|
|
1281
|
-
//#region src/client/neon-client.ts
|
|
1282
|
-
var NeonClient = class extends PostgrestClient {
|
|
1283
|
-
auth;
|
|
1284
|
-
constructor({ url, options }) {
|
|
1285
|
-
super(url, {
|
|
1286
|
-
headers: options?.global?.headers,
|
|
1287
|
-
fetch: options?.global?.fetch,
|
|
1288
|
-
schema: options?.db?.schema
|
|
1289
|
-
});
|
|
1290
|
-
}
|
|
1291
|
-
};
|
|
1292
|
-
|
|
1293
|
-
//#endregion
|
|
1294
|
-
//#region src/client/fetch-with-auth.ts
|
|
1295
|
-
/**
|
|
1296
|
-
* Error thrown when authentication is required but no session exists
|
|
1297
|
-
*/
|
|
1298
|
-
var AuthRequiredError = class extends Error {
|
|
1299
|
-
constructor(message = "Authentication required. User must be signed in to access Neon database.") {
|
|
1300
|
-
super(message);
|
|
1301
|
-
this.name = "AuthRequiredError";
|
|
1302
|
-
}
|
|
1303
|
-
};
|
|
1304
|
-
/**
|
|
1305
|
-
* Creates a fetch wrapper that injects auth headers into every request
|
|
1306
|
-
*
|
|
1307
|
-
* Unlike Supabase, Neon requires authentication - requests without a valid
|
|
1308
|
-
* session will throw an AuthRequiredError.
|
|
1309
|
-
*
|
|
1310
|
-
* @param getAccessToken - Async function that returns current access token
|
|
1311
|
-
* @param customFetch - Optional custom fetch implementation
|
|
1312
|
-
* @returns Wrapped fetch function with auth headers
|
|
1313
|
-
*/
|
|
1314
|
-
function fetchWithAuth(getAccessToken, customFetch) {
|
|
1315
|
-
const baseFetch = customFetch ?? fetch;
|
|
1316
|
-
return async (input, init) => {
|
|
1317
|
-
const accessToken = await getAccessToken();
|
|
1318
|
-
if (!accessToken) throw new AuthRequiredError();
|
|
1319
|
-
const headers = new Headers(init?.headers);
|
|
1320
|
-
if (!headers.has("Authorization")) headers.set("Authorization", `Bearer ${accessToken}`);
|
|
1321
|
-
return baseFetch(input, {
|
|
1322
|
-
...init,
|
|
1323
|
-
headers
|
|
1324
|
-
});
|
|
1325
|
-
};
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
//#endregion
|
|
1329
|
-
//#region src/client/client-factory.ts
|
|
1330
|
-
/**
|
|
1331
|
-
* Factory function to create NeonClient with seamless auth integration
|
|
1332
|
-
*
|
|
1333
|
-
* @param options - Configuration options
|
|
1334
|
-
* @returns NeonClient instance with auth-aware fetch wrapper
|
|
1335
|
-
* @throws AuthRequiredError when making requests without authentication
|
|
1336
|
-
*/
|
|
1337
|
-
function createClient({ url, auth: authOptions, options }) {
|
|
1338
|
-
const auth = new StackAuthAdapter(authOptions);
|
|
1339
|
-
const getAccessToken = async () => {
|
|
1340
|
-
const { data, error } = await auth.getSession();
|
|
1341
|
-
if (error || !data.session) return null;
|
|
1342
|
-
return data.session.access_token;
|
|
1343
|
-
};
|
|
1344
|
-
const authFetch = fetchWithAuth(getAccessToken, options?.global?.fetch);
|
|
1345
|
-
const client = new NeonClient({
|
|
1346
|
-
url,
|
|
1347
|
-
options: {
|
|
1348
|
-
...options,
|
|
1349
|
-
global: {
|
|
1350
|
-
...options?.global,
|
|
1351
|
-
fetch: authFetch
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
});
|
|
1355
|
-
client.auth = auth;
|
|
1356
|
-
return client;
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
//#endregion
|
|
1360
|
-
export { AuthApiError, AuthError, NeonClient, StackAuthAdapter, createClient, isAuthError };
|