@neondatabase/auth 0.1.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 +260 -0
- package/dist/adapter-core-BDOw-gBC.mjs +494 -0
- package/dist/adapter-core-C12KoaiU.d.mts +2247 -0
- package/dist/adapters-CivF9wql.mjs +1 -0
- package/dist/adapters-Dkx0zoMR.mjs +1 -0
- package/dist/better-auth-react-adapter-BXL48HIU.d.mts +722 -0
- package/dist/better-auth-react-adapter-FnBHa2nr.mjs +49 -0
- package/dist/index-C-svZlpj.d.mts +1 -0
- package/dist/index-DuDD6cIY.d.mts +3 -0
- package/dist/index-UW23fDSn.d.mts +1 -0
- package/dist/index.d.mts +99 -0
- package/dist/index.mjs +3 -0
- package/dist/neon-auth-C9XTFffv.mjs +67 -0
- package/dist/next/index.d.mts +2363 -0
- package/dist/next/index.mjs +179 -0
- package/dist/react/adapters/index.d.mts +4 -0
- package/dist/react/adapters/index.mjs +3 -0
- package/dist/react/index.d.mts +5 -0
- package/dist/react/index.mjs +93 -0
- package/dist/react/ui/index.d.mts +3 -0
- package/dist/react/ui/index.mjs +93 -0
- package/dist/react/ui/server.d.mts +1 -0
- package/dist/react/ui/server.mjs +3 -0
- package/dist/supabase-adapter-crabDnl2.d.mts +128 -0
- package/dist/supabase-adapter-ggmqWgPe.mjs +1623 -0
- package/dist/ui/css.css +2 -0
- package/dist/ui/css.d.ts +1 -0
- package/dist/ui/tailwind.css +6 -0
- package/dist/ui/theme.css +125 -0
- package/dist/ui-CNFBSekF.mjs +401 -0
- package/dist/vanilla/adapters/index.d.mts +4 -0
- package/dist/vanilla/adapters/index.mjs +3 -0
- package/dist/vanilla/index.d.mts +4 -0
- package/dist/vanilla/index.mjs +3 -0
- package/package.json +123 -0
|
@@ -0,0 +1,1623 @@
|
|
|
1
|
+
import { a as DEFAULT_SESSION_EXPIRY_MS, i as CURRENT_TAB_CLIENT_ID, n as BETTER_AUTH_METHODS_CACHE, r as BETTER_AUTH_METHODS_HOOKS, t as NeonAuthAdapterCore } from "./adapter-core-BDOw-gBC.mjs";
|
|
2
|
+
import { createAuthClient, getGlobalBroadcastChannel } from "better-auth/client";
|
|
3
|
+
import { AuthApiError, AuthError, isAuthError } from "@supabase/auth-js";
|
|
4
|
+
import { base64url, decodeJwt, decodeProtectedHeader } from "jose";
|
|
5
|
+
|
|
6
|
+
//#region src/adapters/better-auth-vanilla/better-auth-vanilla-adapter.ts
|
|
7
|
+
/**
|
|
8
|
+
* Internal implementation class - use BetterAuthVanillaAdapter factory function instead
|
|
9
|
+
*/
|
|
10
|
+
var BetterAuthVanillaAdapterImpl = class extends NeonAuthAdapterCore {
|
|
11
|
+
_betterAuth;
|
|
12
|
+
constructor(betterAuthClientOptions) {
|
|
13
|
+
super(betterAuthClientOptions);
|
|
14
|
+
this._betterAuth = createAuthClient(this.betterAuthOptions);
|
|
15
|
+
}
|
|
16
|
+
getBetterAuthInstance() {
|
|
17
|
+
return this._betterAuth;
|
|
18
|
+
}
|
|
19
|
+
async getJWTToken() {
|
|
20
|
+
const session = await this._betterAuth.getSession();
|
|
21
|
+
if (session.error) throw session.error;
|
|
22
|
+
return session.data?.session?.token ?? null;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Factory function that returns an adapter builder.
|
|
27
|
+
* The builder is called by createClient/createAuthClient with the URL.
|
|
28
|
+
*
|
|
29
|
+
* @param options - Optional adapter configuration (baseURL is injected separately)
|
|
30
|
+
* @returns A builder function that creates the adapter instance
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const client = createClient({
|
|
35
|
+
* auth: {
|
|
36
|
+
* url: 'https://auth.example.com',
|
|
37
|
+
* adapter: BetterAuthVanillaAdapter(),
|
|
38
|
+
* },
|
|
39
|
+
* dataApi: { url: 'https://data-api.example.com' },
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
function BetterAuthVanillaAdapter(options) {
|
|
44
|
+
return (url) => new BetterAuthVanillaAdapterImpl({
|
|
45
|
+
baseURL: url,
|
|
46
|
+
...options
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/utils/date.ts
|
|
52
|
+
function toISOString(date) {
|
|
53
|
+
if (!date) return (/* @__PURE__ */ new Date()).toISOString();
|
|
54
|
+
if (typeof date === "string") return date;
|
|
55
|
+
if (typeof date === "number") return new Date(date).toISOString();
|
|
56
|
+
return date.toISOString();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/adapters/supabase/errors/definitions.ts
|
|
61
|
+
/**
|
|
62
|
+
* Error codes for type-safe error handling
|
|
63
|
+
*/
|
|
64
|
+
const AuthErrorCode = {
|
|
65
|
+
BadJwt: "bad_jwt",
|
|
66
|
+
InvalidCredentials: "invalid_credentials",
|
|
67
|
+
SessionExpired: "session_expired",
|
|
68
|
+
SessionNotFound: "session_not_found",
|
|
69
|
+
InvalidGrant: "invalid_grant",
|
|
70
|
+
UserNotFound: "user_not_found",
|
|
71
|
+
UserAlreadyExists: "user_already_exists",
|
|
72
|
+
EmailExists: "email_exists",
|
|
73
|
+
PhoneExists: "phone_exists",
|
|
74
|
+
EmailNotConfirmed: "email_not_confirmed",
|
|
75
|
+
PhoneNotConfirmed: "phone_not_confirmed",
|
|
76
|
+
ValidationFailed: "validation_failed",
|
|
77
|
+
BadJson: "bad_json",
|
|
78
|
+
WeakPassword: "weak_password",
|
|
79
|
+
EmailAddressInvalid: "email_address_invalid",
|
|
80
|
+
FeatureNotSupported: "feature_not_supported",
|
|
81
|
+
NotImplemented: "not_implemented",
|
|
82
|
+
OAuthProviderNotSupported: "oauth_provider_not_supported",
|
|
83
|
+
PhoneProviderDisabled: "phone_provider_disabled",
|
|
84
|
+
SsoProviderDisabled: "sso_provider_disabled",
|
|
85
|
+
AnonymousProviderDisabled: "anonymous_provider_disabled",
|
|
86
|
+
Web3ProviderDisabled: "web3_provider_disabled",
|
|
87
|
+
BadOAuthCallback: "bad_oauth_callback",
|
|
88
|
+
OAuthCallbackFailed: "oauth_callback_failed",
|
|
89
|
+
OverRequestRateLimit: "over_request_rate_limit",
|
|
90
|
+
OverEmailSendRateLimit: "over_email_send_rate_limit",
|
|
91
|
+
OverSmsSendRateLimit: "over_sms_send_rate_limit",
|
|
92
|
+
UnexpectedFailure: "unexpected_failure",
|
|
93
|
+
InternalError: "internal_error",
|
|
94
|
+
IdentityNotFound: "identity_not_found",
|
|
95
|
+
UnknownError: "unknown_error"
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Complete error definitions map
|
|
99
|
+
* Maps error codes to HTTP status codes and default messages
|
|
100
|
+
*/
|
|
101
|
+
const ERROR_DEFINITIONS = {
|
|
102
|
+
[AuthErrorCode.BadJwt]: {
|
|
103
|
+
code: AuthErrorCode.BadJwt,
|
|
104
|
+
status: 401,
|
|
105
|
+
message: "Invalid or expired session token",
|
|
106
|
+
description: "The JWT token is malformed, expired, or has an invalid signature"
|
|
107
|
+
},
|
|
108
|
+
[AuthErrorCode.InvalidCredentials]: {
|
|
109
|
+
code: AuthErrorCode.InvalidCredentials,
|
|
110
|
+
status: 401,
|
|
111
|
+
message: "Invalid email or password",
|
|
112
|
+
description: "The provided credentials do not match any user account"
|
|
113
|
+
},
|
|
114
|
+
[AuthErrorCode.SessionExpired]: {
|
|
115
|
+
code: AuthErrorCode.SessionExpired,
|
|
116
|
+
status: 401,
|
|
117
|
+
message: "Session has expired",
|
|
118
|
+
description: "The user session has exceeded its timeout period"
|
|
119
|
+
},
|
|
120
|
+
[AuthErrorCode.SessionNotFound]: {
|
|
121
|
+
code: AuthErrorCode.SessionNotFound,
|
|
122
|
+
status: 401,
|
|
123
|
+
message: "No active session found",
|
|
124
|
+
description: "The user does not have an active session or the session was invalidated"
|
|
125
|
+
},
|
|
126
|
+
[AuthErrorCode.InvalidGrant]: {
|
|
127
|
+
code: AuthErrorCode.InvalidGrant,
|
|
128
|
+
status: 401,
|
|
129
|
+
message: "Invalid authorization grant",
|
|
130
|
+
description: "OAuth/OIDC grant validation failed"
|
|
131
|
+
},
|
|
132
|
+
[AuthErrorCode.UserNotFound]: {
|
|
133
|
+
code: AuthErrorCode.UserNotFound,
|
|
134
|
+
status: 404,
|
|
135
|
+
message: "User not found",
|
|
136
|
+
description: "No user exists with the provided identifier"
|
|
137
|
+
},
|
|
138
|
+
[AuthErrorCode.UserAlreadyExists]: {
|
|
139
|
+
code: AuthErrorCode.UserAlreadyExists,
|
|
140
|
+
status: 409,
|
|
141
|
+
message: "User already exists",
|
|
142
|
+
description: "A user with this email or phone number is already registered"
|
|
143
|
+
},
|
|
144
|
+
[AuthErrorCode.EmailExists]: {
|
|
145
|
+
code: AuthErrorCode.EmailExists,
|
|
146
|
+
status: 409,
|
|
147
|
+
message: "Email address already registered",
|
|
148
|
+
description: "This email address is already associated with an account"
|
|
149
|
+
},
|
|
150
|
+
[AuthErrorCode.PhoneExists]: {
|
|
151
|
+
code: AuthErrorCode.PhoneExists,
|
|
152
|
+
status: 409,
|
|
153
|
+
message: "Phone number already registered",
|
|
154
|
+
description: "This phone number is already associated with an account"
|
|
155
|
+
},
|
|
156
|
+
[AuthErrorCode.EmailNotConfirmed]: {
|
|
157
|
+
code: AuthErrorCode.EmailNotConfirmed,
|
|
158
|
+
status: 422,
|
|
159
|
+
message: "Email verification required",
|
|
160
|
+
description: "The user must verify their email before signing in"
|
|
161
|
+
},
|
|
162
|
+
[AuthErrorCode.PhoneNotConfirmed]: {
|
|
163
|
+
code: AuthErrorCode.PhoneNotConfirmed,
|
|
164
|
+
status: 422,
|
|
165
|
+
message: "Phone verification required",
|
|
166
|
+
description: "The user must verify their phone number before signing in"
|
|
167
|
+
},
|
|
168
|
+
[AuthErrorCode.ValidationFailed]: {
|
|
169
|
+
code: AuthErrorCode.ValidationFailed,
|
|
170
|
+
status: 400,
|
|
171
|
+
message: "Invalid request parameters",
|
|
172
|
+
description: "One or more request parameters are invalid or missing"
|
|
173
|
+
},
|
|
174
|
+
[AuthErrorCode.BadJson]: {
|
|
175
|
+
code: AuthErrorCode.BadJson,
|
|
176
|
+
status: 400,
|
|
177
|
+
message: "Invalid JSON in request body",
|
|
178
|
+
description: "The request body contains malformed JSON"
|
|
179
|
+
},
|
|
180
|
+
[AuthErrorCode.WeakPassword]: {
|
|
181
|
+
code: AuthErrorCode.WeakPassword,
|
|
182
|
+
status: 400,
|
|
183
|
+
message: "Password does not meet security requirements",
|
|
184
|
+
description: "The password is too weak or does not meet complexity requirements"
|
|
185
|
+
},
|
|
186
|
+
[AuthErrorCode.EmailAddressInvalid]: {
|
|
187
|
+
code: AuthErrorCode.EmailAddressInvalid,
|
|
188
|
+
status: 400,
|
|
189
|
+
message: "Invalid email address format",
|
|
190
|
+
description: "The provided email address is not in a valid format"
|
|
191
|
+
},
|
|
192
|
+
[AuthErrorCode.FeatureNotSupported]: {
|
|
193
|
+
code: AuthErrorCode.FeatureNotSupported,
|
|
194
|
+
status: 403,
|
|
195
|
+
message: "Feature not available",
|
|
196
|
+
description: "This feature is not supported in the current configuration"
|
|
197
|
+
},
|
|
198
|
+
[AuthErrorCode.NotImplemented]: {
|
|
199
|
+
code: AuthErrorCode.NotImplemented,
|
|
200
|
+
status: 501,
|
|
201
|
+
message: "Feature not implemented",
|
|
202
|
+
description: "This feature has not been implemented yet"
|
|
203
|
+
},
|
|
204
|
+
[AuthErrorCode.OAuthProviderNotSupported]: {
|
|
205
|
+
code: AuthErrorCode.OAuthProviderNotSupported,
|
|
206
|
+
status: 403,
|
|
207
|
+
message: "OAuth provider not supported",
|
|
208
|
+
description: "The requested OAuth provider is not enabled"
|
|
209
|
+
},
|
|
210
|
+
[AuthErrorCode.PhoneProviderDisabled]: {
|
|
211
|
+
code: AuthErrorCode.PhoneProviderDisabled,
|
|
212
|
+
status: 403,
|
|
213
|
+
message: "Phone authentication not available",
|
|
214
|
+
description: "Phone number authentication is not enabled"
|
|
215
|
+
},
|
|
216
|
+
[AuthErrorCode.SsoProviderDisabled]: {
|
|
217
|
+
code: AuthErrorCode.SsoProviderDisabled,
|
|
218
|
+
status: 403,
|
|
219
|
+
message: "SSO not supported",
|
|
220
|
+
description: "Enterprise SSO authentication is not available"
|
|
221
|
+
},
|
|
222
|
+
[AuthErrorCode.AnonymousProviderDisabled]: {
|
|
223
|
+
code: AuthErrorCode.AnonymousProviderDisabled,
|
|
224
|
+
status: 403,
|
|
225
|
+
message: "Anonymous authentication not available",
|
|
226
|
+
description: "Anonymous sign-in is not enabled"
|
|
227
|
+
},
|
|
228
|
+
[AuthErrorCode.Web3ProviderDisabled]: {
|
|
229
|
+
code: AuthErrorCode.Web3ProviderDisabled,
|
|
230
|
+
status: 403,
|
|
231
|
+
message: "Web3 authentication not supported",
|
|
232
|
+
description: "Web3/blockchain authentication is not available"
|
|
233
|
+
},
|
|
234
|
+
[AuthErrorCode.BadOAuthCallback]: {
|
|
235
|
+
code: AuthErrorCode.BadOAuthCallback,
|
|
236
|
+
status: 400,
|
|
237
|
+
message: "Invalid OAuth callback",
|
|
238
|
+
description: "The OAuth callback request is missing required parameters"
|
|
239
|
+
},
|
|
240
|
+
[AuthErrorCode.OAuthCallbackFailed]: {
|
|
241
|
+
code: AuthErrorCode.OAuthCallbackFailed,
|
|
242
|
+
status: 500,
|
|
243
|
+
message: "OAuth authentication failed",
|
|
244
|
+
description: "The OAuth callback completed but no session was created"
|
|
245
|
+
},
|
|
246
|
+
[AuthErrorCode.OverRequestRateLimit]: {
|
|
247
|
+
code: AuthErrorCode.OverRequestRateLimit,
|
|
248
|
+
status: 429,
|
|
249
|
+
message: "Too many requests",
|
|
250
|
+
description: "Rate limit exceeded. Please try again later"
|
|
251
|
+
},
|
|
252
|
+
[AuthErrorCode.OverEmailSendRateLimit]: {
|
|
253
|
+
code: AuthErrorCode.OverEmailSendRateLimit,
|
|
254
|
+
status: 429,
|
|
255
|
+
message: "Too many email requests",
|
|
256
|
+
description: "Too many emails sent. Please wait before trying again"
|
|
257
|
+
},
|
|
258
|
+
[AuthErrorCode.OverSmsSendRateLimit]: {
|
|
259
|
+
code: AuthErrorCode.OverSmsSendRateLimit,
|
|
260
|
+
status: 429,
|
|
261
|
+
message: "Too many SMS requests",
|
|
262
|
+
description: "Too many SMS messages sent. Please wait before trying again"
|
|
263
|
+
},
|
|
264
|
+
[AuthErrorCode.UnexpectedFailure]: {
|
|
265
|
+
code: AuthErrorCode.UnexpectedFailure,
|
|
266
|
+
status: 500,
|
|
267
|
+
message: "An unexpected error occurred",
|
|
268
|
+
description: "The server encountered an unexpected condition"
|
|
269
|
+
},
|
|
270
|
+
[AuthErrorCode.InternalError]: {
|
|
271
|
+
code: AuthErrorCode.InternalError,
|
|
272
|
+
status: 500,
|
|
273
|
+
message: "Internal server error",
|
|
274
|
+
description: "An internal error occurred while processing the request"
|
|
275
|
+
},
|
|
276
|
+
[AuthErrorCode.IdentityNotFound]: {
|
|
277
|
+
code: AuthErrorCode.IdentityNotFound,
|
|
278
|
+
status: 404,
|
|
279
|
+
message: "Identity not found",
|
|
280
|
+
description: "The requested user identity does not exist"
|
|
281
|
+
},
|
|
282
|
+
[AuthErrorCode.UnknownError]: {
|
|
283
|
+
code: AuthErrorCode.UnknownError,
|
|
284
|
+
status: 500,
|
|
285
|
+
message: "An unknown error occurred",
|
|
286
|
+
description: "The error could not be categorized"
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
/**
|
|
290
|
+
* Helper to get error definition by code
|
|
291
|
+
*/
|
|
292
|
+
function getErrorDefinition(code) {
|
|
293
|
+
return ERROR_DEFINITIONS[code];
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Create an AuthError or AuthApiError with proper status and message
|
|
297
|
+
*
|
|
298
|
+
* @param code - The error code from AuthErrorCode
|
|
299
|
+
* @param customMessage - Optional custom message (defaults to error definition message)
|
|
300
|
+
* @returns AuthError for 5xx errors, AuthApiError for 4xx errors
|
|
301
|
+
*/
|
|
302
|
+
function createAuthError(code, customMessage) {
|
|
303
|
+
const def = getErrorDefinition(code);
|
|
304
|
+
const message = customMessage || def.message;
|
|
305
|
+
const status = def.status;
|
|
306
|
+
if (status !== 500 && status !== 501 && status !== 503) return new AuthApiError(message, status, def.code);
|
|
307
|
+
return new AuthError(message, status, def.code);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
//#endregion
|
|
311
|
+
//#region src/adapters/supabase/errors/mappings.ts
|
|
312
|
+
/**
|
|
313
|
+
* Maps Better Auth error codes to AuthErrorCode
|
|
314
|
+
* Based on Better Auth SDK error codes
|
|
315
|
+
*
|
|
316
|
+
* @see https://www.better-auth.com/docs/concepts/error-handling
|
|
317
|
+
*/
|
|
318
|
+
const BETTER_AUTH_ERROR_MAP = {
|
|
319
|
+
"INVALID_EMAIL_OR_PASSWORD": AuthErrorCode.InvalidCredentials,
|
|
320
|
+
"INVALID_PASSWORD": AuthErrorCode.InvalidCredentials,
|
|
321
|
+
"INVALID_EMAIL": AuthErrorCode.EmailAddressInvalid,
|
|
322
|
+
"USER_NOT_FOUND": AuthErrorCode.UserNotFound,
|
|
323
|
+
"INVALID_TOKEN": AuthErrorCode.BadJwt,
|
|
324
|
+
"SESSION_EXPIRED": AuthErrorCode.SessionExpired,
|
|
325
|
+
"FAILED_TO_GET_SESSION": AuthErrorCode.SessionNotFound,
|
|
326
|
+
"USER_ALREADY_EXISTS": AuthErrorCode.UserAlreadyExists,
|
|
327
|
+
"EMAIL_NOT_VERIFIED": AuthErrorCode.EmailNotConfirmed,
|
|
328
|
+
"USER_EMAIL_NOT_FOUND": AuthErrorCode.UserNotFound,
|
|
329
|
+
"PASSWORD_TOO_SHORT": AuthErrorCode.WeakPassword,
|
|
330
|
+
"PASSWORD_TOO_LONG": AuthErrorCode.WeakPassword,
|
|
331
|
+
"USER_ALREADY_HAS_PASSWORD": AuthErrorCode.ValidationFailed,
|
|
332
|
+
"CREDENTIAL_ACCOUNT_NOT_FOUND": AuthErrorCode.IdentityNotFound,
|
|
333
|
+
"FAILED_TO_UNLINK_LAST_ACCOUNT": AuthErrorCode.ValidationFailed,
|
|
334
|
+
"ACCOUNT_NOT_FOUND": AuthErrorCode.IdentityNotFound,
|
|
335
|
+
"SOCIAL_ACCOUNT_ALREADY_LINKED": AuthErrorCode.ValidationFailed,
|
|
336
|
+
"PROVIDER_NOT_FOUND": AuthErrorCode.OAuthProviderNotSupported,
|
|
337
|
+
"ID_TOKEN_NOT_SUPPORTED": AuthErrorCode.FeatureNotSupported,
|
|
338
|
+
"FAILED_TO_CREATE_USER": AuthErrorCode.InternalError,
|
|
339
|
+
"FAILED_TO_CREATE_SESSION": AuthErrorCode.InternalError,
|
|
340
|
+
"FAILED_TO_UPDATE_USER": AuthErrorCode.InternalError,
|
|
341
|
+
"EMAIL_CAN_NOT_BE_UPDATED": AuthErrorCode.FeatureNotSupported
|
|
342
|
+
};
|
|
343
|
+
/**
|
|
344
|
+
* Maps HTTP status codes from Better Auth to AuthErrorCode
|
|
345
|
+
*/
|
|
346
|
+
const STATUS_CODE_ERROR_MAP = {
|
|
347
|
+
400: AuthErrorCode.ValidationFailed,
|
|
348
|
+
401: AuthErrorCode.BadJwt,
|
|
349
|
+
403: AuthErrorCode.FeatureNotSupported,
|
|
350
|
+
404: AuthErrorCode.UserNotFound,
|
|
351
|
+
409: AuthErrorCode.UserAlreadyExists,
|
|
352
|
+
422: AuthErrorCode.ValidationFailed,
|
|
353
|
+
429: AuthErrorCode.OverRequestRateLimit,
|
|
354
|
+
500: AuthErrorCode.UnexpectedFailure,
|
|
355
|
+
501: AuthErrorCode.NotImplemented,
|
|
356
|
+
503: AuthErrorCode.FeatureNotSupported
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
//#endregion
|
|
360
|
+
//#region src/core/better-auth-helpers.ts
|
|
361
|
+
/**
|
|
362
|
+
* Normalize Better Auth errors to standard AuthError format
|
|
363
|
+
*
|
|
364
|
+
* Handles three error formats:
|
|
365
|
+
* 1. BetterFetchError: { status, statusText, message?, code? }
|
|
366
|
+
* 2. BetterAuthErrorResponse: { status, statusText, message?, code? }
|
|
367
|
+
* 3. Standard Error: { message, name, stack }
|
|
368
|
+
*
|
|
369
|
+
* Maps Better Auth errors to appropriate AuthError/AuthApiError with:
|
|
370
|
+
* - Correct HTTP status codes
|
|
371
|
+
* - Standard error codes (snake_case)
|
|
372
|
+
* - User-friendly, security-conscious messages
|
|
373
|
+
*/
|
|
374
|
+
function normalizeBetterAuthError(error) {
|
|
375
|
+
if (error !== null && error !== void 0 && typeof error === "object" && "status" in error && "statusText" in error) {
|
|
376
|
+
const betterError = error;
|
|
377
|
+
const status = betterError.status;
|
|
378
|
+
if ("code" in betterError && betterError.code && typeof betterError.code === "string") {
|
|
379
|
+
const mappedCode = BETTER_AUTH_ERROR_MAP[betterError.code];
|
|
380
|
+
if (mappedCode) {
|
|
381
|
+
const def$2 = getErrorDefinition(mappedCode);
|
|
382
|
+
return createNormalizedError(def$2.message, def$2.status, def$2.code, status);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const def$1 = getErrorDefinition(mapStatusCodeToErrorCode(status, betterError.message || betterError.statusText));
|
|
386
|
+
return createNormalizedError(betterError.message || def$1.message, status, def$1.code, status);
|
|
387
|
+
}
|
|
388
|
+
if (error instanceof Error) {
|
|
389
|
+
const def$1 = getErrorDefinition(mapMessageToErrorCode(error.message));
|
|
390
|
+
return createNormalizedError(error.message || def$1.message, def$1.status, def$1.code, def$1.status);
|
|
391
|
+
}
|
|
392
|
+
const def = getErrorDefinition(AuthErrorCode.UnknownError);
|
|
393
|
+
return new AuthError(def.message, def.status, def.code);
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Map HTTP status code to AuthErrorCode
|
|
397
|
+
* Uses message content for disambiguation when status code is ambiguous
|
|
398
|
+
*/
|
|
399
|
+
function mapStatusCodeToErrorCode(status, message) {
|
|
400
|
+
const lowerMessage = message?.toLowerCase() || "";
|
|
401
|
+
switch (status) {
|
|
402
|
+
case 401:
|
|
403
|
+
if (lowerMessage.includes("token") || lowerMessage.includes("jwt")) return AuthErrorCode.BadJwt;
|
|
404
|
+
if (lowerMessage.includes("session")) return AuthErrorCode.SessionNotFound;
|
|
405
|
+
if (lowerMessage.includes("expired")) return AuthErrorCode.SessionExpired;
|
|
406
|
+
return AuthErrorCode.InvalidCredentials;
|
|
407
|
+
case 404:
|
|
408
|
+
if (lowerMessage.includes("identity") || lowerMessage.includes("account")) return AuthErrorCode.IdentityNotFound;
|
|
409
|
+
if (lowerMessage.includes("session")) return AuthErrorCode.SessionNotFound;
|
|
410
|
+
return AuthErrorCode.UserNotFound;
|
|
411
|
+
case 409:
|
|
412
|
+
if (lowerMessage.includes("email")) return AuthErrorCode.EmailExists;
|
|
413
|
+
if (lowerMessage.includes("phone")) return AuthErrorCode.PhoneExists;
|
|
414
|
+
return AuthErrorCode.UserAlreadyExists;
|
|
415
|
+
case 422:
|
|
416
|
+
if (lowerMessage.includes("email") && lowerMessage.includes("confirm")) return AuthErrorCode.EmailNotConfirmed;
|
|
417
|
+
if (lowerMessage.includes("phone") && lowerMessage.includes("confirm")) return AuthErrorCode.PhoneNotConfirmed;
|
|
418
|
+
return AuthErrorCode.ValidationFailed;
|
|
419
|
+
case 429:
|
|
420
|
+
if (lowerMessage.includes("email")) return AuthErrorCode.OverEmailSendRateLimit;
|
|
421
|
+
if (lowerMessage.includes("sms") || lowerMessage.includes("phone")) return AuthErrorCode.OverSmsSendRateLimit;
|
|
422
|
+
return AuthErrorCode.OverRequestRateLimit;
|
|
423
|
+
case 400:
|
|
424
|
+
if (lowerMessage.includes("password") && lowerMessage.includes("weak")) return AuthErrorCode.WeakPassword;
|
|
425
|
+
if (lowerMessage.includes("email") && lowerMessage.includes("invalid")) return AuthErrorCode.EmailAddressInvalid;
|
|
426
|
+
if (lowerMessage.includes("json")) return AuthErrorCode.BadJson;
|
|
427
|
+
if (lowerMessage.includes("oauth") || lowerMessage.includes("callback")) return AuthErrorCode.BadOAuthCallback;
|
|
428
|
+
return AuthErrorCode.ValidationFailed;
|
|
429
|
+
case 403:
|
|
430
|
+
if (lowerMessage.includes("provider") || lowerMessage.includes("oauth")) return AuthErrorCode.OAuthProviderNotSupported;
|
|
431
|
+
if (lowerMessage.includes("phone")) return AuthErrorCode.PhoneProviderDisabled;
|
|
432
|
+
if (lowerMessage.includes("sso")) return AuthErrorCode.SsoProviderDisabled;
|
|
433
|
+
return AuthErrorCode.FeatureNotSupported;
|
|
434
|
+
case 501: return AuthErrorCode.NotImplemented;
|
|
435
|
+
case 503: return AuthErrorCode.FeatureNotSupported;
|
|
436
|
+
default:
|
|
437
|
+
if (lowerMessage.includes("oauth")) return AuthErrorCode.OAuthCallbackFailed;
|
|
438
|
+
return AuthErrorCode.UnexpectedFailure;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Map error message content to AuthErrorCode
|
|
443
|
+
* Used as fallback when status code is not available
|
|
444
|
+
*/
|
|
445
|
+
function mapMessageToErrorCode(message) {
|
|
446
|
+
const lower = message.toLowerCase();
|
|
447
|
+
if (lower.includes("invalid login") || lower.includes("incorrect") || lower.includes("wrong password")) return AuthErrorCode.InvalidCredentials;
|
|
448
|
+
if (lower.includes("token") && (lower.includes("invalid") || lower.includes("expired"))) return AuthErrorCode.BadJwt;
|
|
449
|
+
if (lower.includes("session") && lower.includes("expired")) return AuthErrorCode.SessionExpired;
|
|
450
|
+
if (lower.includes("session") && lower.includes("not found")) return AuthErrorCode.SessionNotFound;
|
|
451
|
+
if (lower.includes("already exists") || lower.includes("already registered")) return AuthErrorCode.UserAlreadyExists;
|
|
452
|
+
if (lower.includes("not found") && lower.includes("user")) return AuthErrorCode.UserNotFound;
|
|
453
|
+
if (lower.includes("not found") && lower.includes("identity")) return AuthErrorCode.IdentityNotFound;
|
|
454
|
+
if (lower.includes("email") && lower.includes("not confirmed")) return AuthErrorCode.EmailNotConfirmed;
|
|
455
|
+
if (lower.includes("phone") && lower.includes("not confirmed")) return AuthErrorCode.PhoneNotConfirmed;
|
|
456
|
+
if (lower.includes("weak password") || lower.includes("password") && lower.includes("requirements")) return AuthErrorCode.WeakPassword;
|
|
457
|
+
if (lower.includes("email") && lower.includes("invalid")) return AuthErrorCode.EmailAddressInvalid;
|
|
458
|
+
if (lower.includes("rate limit") || lower.includes("too many requests")) return AuthErrorCode.OverRequestRateLimit;
|
|
459
|
+
if (lower.includes("oauth") && lower.includes("failed")) return AuthErrorCode.OAuthCallbackFailed;
|
|
460
|
+
if (lower.includes("provider") && lower.includes("not supported")) return AuthErrorCode.OAuthProviderNotSupported;
|
|
461
|
+
return AuthErrorCode.UnexpectedFailure;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Create normalized error with correct type (AuthError vs AuthApiError)
|
|
465
|
+
* Uses AuthApiError for non-500 status codes (API/client errors)
|
|
466
|
+
* Uses AuthError for 500 status codes (server errors)
|
|
467
|
+
*/
|
|
468
|
+
function createNormalizedError(message, targetStatus, code, _originalStatus) {
|
|
469
|
+
const status = targetStatus;
|
|
470
|
+
if (status !== 500 && status !== 501 && status !== 503) return new AuthApiError(message, status, code);
|
|
471
|
+
return new AuthError(message, status, code);
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Map Better Auth session to Session format
|
|
475
|
+
*/
|
|
476
|
+
function mapBetterAuthSession(betterAuthSession, betterAuthUser) {
|
|
477
|
+
if (!betterAuthSession || !betterAuthUser) return null;
|
|
478
|
+
let expiresAt;
|
|
479
|
+
if (typeof betterAuthSession.expiresAt === "string") expiresAt = Math.floor(new Date(betterAuthSession.expiresAt).getTime() / 1e3);
|
|
480
|
+
else if (typeof betterAuthSession.expiresAt === "object" && betterAuthSession.expiresAt instanceof Date) expiresAt = Math.floor(betterAuthSession.expiresAt.getTime() / 1e3);
|
|
481
|
+
else expiresAt = Math.floor(Date.now() / 1e3) + Math.floor(DEFAULT_SESSION_EXPIRY_MS / 1e3);
|
|
482
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
483
|
+
const expiresIn = Math.max(0, expiresAt - now);
|
|
484
|
+
return {
|
|
485
|
+
access_token: betterAuthSession.token,
|
|
486
|
+
refresh_token: "",
|
|
487
|
+
expires_at: expiresAt,
|
|
488
|
+
expires_in: expiresIn,
|
|
489
|
+
token_type: "bearer",
|
|
490
|
+
user: mapBetterAuthUser(betterAuthUser)
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Map Better Auth user to User format
|
|
495
|
+
*/
|
|
496
|
+
function mapBetterAuthUser(betterAuthUser) {
|
|
497
|
+
const createdAt = toISOString(betterAuthUser.createdAt);
|
|
498
|
+
const updatedAt = toISOString(betterAuthUser.updatedAt);
|
|
499
|
+
const userMetadata = {};
|
|
500
|
+
if (betterAuthUser.name) userMetadata.displayName = betterAuthUser.name;
|
|
501
|
+
if (betterAuthUser.image) userMetadata.profileImageUrl = betterAuthUser.image;
|
|
502
|
+
const userRecord = betterAuthUser;
|
|
503
|
+
for (const key of Object.keys(userRecord)) if (![
|
|
504
|
+
"id",
|
|
505
|
+
"email",
|
|
506
|
+
"emailVerified",
|
|
507
|
+
"name",
|
|
508
|
+
"image",
|
|
509
|
+
"createdAt",
|
|
510
|
+
"updatedAt"
|
|
511
|
+
].includes(key)) userMetadata[key] = userRecord[key];
|
|
512
|
+
return {
|
|
513
|
+
id: betterAuthUser.id,
|
|
514
|
+
email: betterAuthUser.email || "",
|
|
515
|
+
email_confirmed_at: betterAuthUser.emailVerified ? createdAt : void 0,
|
|
516
|
+
phone: void 0,
|
|
517
|
+
confirmed_at: betterAuthUser.emailVerified ? createdAt : void 0,
|
|
518
|
+
last_sign_in_at: updatedAt,
|
|
519
|
+
app_metadata: {},
|
|
520
|
+
user_metadata: userMetadata,
|
|
521
|
+
identities: [],
|
|
522
|
+
created_at: createdAt,
|
|
523
|
+
updated_at: updatedAt,
|
|
524
|
+
aud: "authenticated",
|
|
525
|
+
role: "authenticated"
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
function mapBetterAuthIdentity(betterAuthUserIdentityAccount, accountInfoData) {
|
|
529
|
+
return {
|
|
530
|
+
id: betterAuthUserIdentityAccount.id,
|
|
531
|
+
user_id: betterAuthUserIdentityAccount.id,
|
|
532
|
+
identity_id: betterAuthUserIdentityAccount.accountId,
|
|
533
|
+
provider: betterAuthUserIdentityAccount.providerId,
|
|
534
|
+
created_at: toISOString(betterAuthUserIdentityAccount.createdAt),
|
|
535
|
+
updated_at: toISOString(betterAuthUserIdentityAccount.updatedAt),
|
|
536
|
+
last_sign_in_at: toISOString(betterAuthUserIdentityAccount.updatedAt),
|
|
537
|
+
identity_data: accountInfoData ? {
|
|
538
|
+
provider: betterAuthUserIdentityAccount.providerId,
|
|
539
|
+
provider_id: betterAuthUserIdentityAccount.accountId,
|
|
540
|
+
scopes: betterAuthUserIdentityAccount.scopes,
|
|
541
|
+
email: accountInfoData.data.email,
|
|
542
|
+
name: accountInfoData.data.user.name,
|
|
543
|
+
picture: accountInfoData.data.user.picture,
|
|
544
|
+
email_verified: accountInfoData.data.user.email_verified,
|
|
545
|
+
...accountInfoData.data
|
|
546
|
+
} : {
|
|
547
|
+
provider: betterAuthUserIdentityAccount.providerId,
|
|
548
|
+
provider_id: betterAuthUserIdentityAccount.accountId,
|
|
549
|
+
scopes: betterAuthUserIdentityAccount.scopes
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
//#endregion
|
|
555
|
+
//#region src/adapters/supabase/supabase-adapter.ts
|
|
556
|
+
/**
|
|
557
|
+
* Duck-type check for Better Auth API errors.
|
|
558
|
+
* Replaces `instanceof APIError` to avoid importing server-side code.
|
|
559
|
+
*/
|
|
560
|
+
function isBetterAuthAPIError(error) {
|
|
561
|
+
return error !== null && typeof error === "object" && "status" in error && typeof error.status === "number";
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Internal implementation class - use SupabaseAuthAdapter factory function instead
|
|
565
|
+
*/
|
|
566
|
+
var SupabaseAuthAdapterImpl = class extends NeonAuthAdapterCore {
|
|
567
|
+
admin = void 0;
|
|
568
|
+
mfa = void 0;
|
|
569
|
+
oauth = void 0;
|
|
570
|
+
_betterAuth;
|
|
571
|
+
_stateChangeEmitters = /* @__PURE__ */ new Map();
|
|
572
|
+
constructor(betterAuthClientOptions) {
|
|
573
|
+
super(betterAuthClientOptions);
|
|
574
|
+
this._betterAuth = createAuthClient(this.betterAuthOptions);
|
|
575
|
+
/**
|
|
576
|
+
* useSession() - Automatic Session Management
|
|
577
|
+
*
|
|
578
|
+
* Enabled by Default:
|
|
579
|
+
* - ✅ Refetch on Window Focus: Automatically refetches session when user returns to the tab
|
|
580
|
+
* - ✅ Cross-Tab Sync: Syncs session state across all browser tabs (sign out in one = sign out in all)
|
|
581
|
+
* - ✅ Online/Offline Detection: Refetches session when network connection is restored
|
|
582
|
+
* - ❌ Interval Polling: Disabled (refetchInterval: 0)
|
|
583
|
+
*
|
|
584
|
+
* Returns:
|
|
585
|
+
* - data: Session object (user + session)
|
|
586
|
+
* - isPending: Loading state
|
|
587
|
+
* - isRefetching: Refetch in progress
|
|
588
|
+
* - error: Error object if any
|
|
589
|
+
* - refetch(): Manual refetch function
|
|
590
|
+
*
|
|
591
|
+
* Customize with:
|
|
592
|
+
* createAuthClient({
|
|
593
|
+
* sessionOptions: {
|
|
594
|
+
* refetchOnWindowFocus: true, // default
|
|
595
|
+
* refetchInterval: 0, // default (seconds, 0 = off)
|
|
596
|
+
* refetchWhenOffline: false // default
|
|
597
|
+
* }
|
|
598
|
+
* })
|
|
599
|
+
*/
|
|
600
|
+
this._betterAuth.useSession.subscribe((value) => {
|
|
601
|
+
if (!value.data?.session || !value.data?.user) {
|
|
602
|
+
BETTER_AUTH_METHODS_CACHE.clearSessionCache();
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
getGlobalBroadcastChannel().subscribe((message) => {
|
|
607
|
+
if (message.clientId === CURRENT_TAB_CLIENT_ID) return;
|
|
608
|
+
if (message.data && "sessionData" in message.data) {
|
|
609
|
+
const sessionData = message.data.sessionData;
|
|
610
|
+
const trigger = message.data.trigger;
|
|
611
|
+
if (sessionData) BETTER_AUTH_METHODS_CACHE.setCachedSession(sessionData);
|
|
612
|
+
else BETTER_AUTH_METHODS_CACHE.clearSessionCache();
|
|
613
|
+
const supabaseSession = sessionData ? mapBetterAuthSession(sessionData.session, sessionData.user) : null;
|
|
614
|
+
const promises = [...this._stateChangeEmitters.values()].map((subscription) => {
|
|
615
|
+
try {
|
|
616
|
+
return Promise.resolve(subscription.callback(trigger, supabaseSession));
|
|
617
|
+
} catch {
|
|
618
|
+
return Promise.resolve();
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
Promise.allSettled(promises);
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
getBetterAuthInstance() {
|
|
626
|
+
return this._betterAuth;
|
|
627
|
+
}
|
|
628
|
+
async getJWTToken() {
|
|
629
|
+
const session = await this.getSession();
|
|
630
|
+
if (session.error) return null;
|
|
631
|
+
return session.data.session?.access_token ?? null;
|
|
632
|
+
}
|
|
633
|
+
initialize = async () => {
|
|
634
|
+
try {
|
|
635
|
+
const session = await this.getSession();
|
|
636
|
+
if (session.error) throw session.error;
|
|
637
|
+
return {
|
|
638
|
+
data: session.data,
|
|
639
|
+
error: null
|
|
640
|
+
};
|
|
641
|
+
} catch (error) {
|
|
642
|
+
if (isAuthError(error)) return {
|
|
643
|
+
data: { session: null },
|
|
644
|
+
error
|
|
645
|
+
};
|
|
646
|
+
if (isBetterAuthAPIError(error)) return {
|
|
647
|
+
data: { session: null },
|
|
648
|
+
error: normalizeBetterAuthError(error)
|
|
649
|
+
};
|
|
650
|
+
throw error;
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
async getSession(options) {
|
|
654
|
+
try {
|
|
655
|
+
const currentSession = await this._betterAuth.getSession(options?.forceFetch ? { fetchOptions: { headers: { "X-Force-Fetch": "true" } } } : void 0);
|
|
656
|
+
if (!currentSession.data?.session) return {
|
|
657
|
+
data: { session: null },
|
|
658
|
+
error: null
|
|
659
|
+
};
|
|
660
|
+
return {
|
|
661
|
+
data: { session: mapBetterAuthSession(currentSession.data.session, currentSession.data.user) },
|
|
662
|
+
error: null
|
|
663
|
+
};
|
|
664
|
+
} catch (error) {
|
|
665
|
+
if (isAuthError(error)) return {
|
|
666
|
+
data: { session: null },
|
|
667
|
+
error
|
|
668
|
+
};
|
|
669
|
+
if (isBetterAuthAPIError(error)) return {
|
|
670
|
+
data: { session: null },
|
|
671
|
+
error: normalizeBetterAuthError(error)
|
|
672
|
+
};
|
|
673
|
+
throw error;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
refreshSession = async () => {
|
|
677
|
+
try {
|
|
678
|
+
const sessionResult = await this.getSession();
|
|
679
|
+
if (sessionResult.error) throw sessionResult.error;
|
|
680
|
+
return {
|
|
681
|
+
data: {
|
|
682
|
+
user: sessionResult.data.session?.user ?? null,
|
|
683
|
+
session: sessionResult.data.session
|
|
684
|
+
},
|
|
685
|
+
error: null
|
|
686
|
+
};
|
|
687
|
+
} catch (error) {
|
|
688
|
+
if (isAuthError(error)) return {
|
|
689
|
+
data: {
|
|
690
|
+
user: null,
|
|
691
|
+
session: null
|
|
692
|
+
},
|
|
693
|
+
error
|
|
694
|
+
};
|
|
695
|
+
if (isBetterAuthAPIError(error)) return {
|
|
696
|
+
data: {
|
|
697
|
+
user: null,
|
|
698
|
+
session: null
|
|
699
|
+
},
|
|
700
|
+
error: normalizeBetterAuthError(error)
|
|
701
|
+
};
|
|
702
|
+
throw error;
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
setSession = async () => {
|
|
706
|
+
return {
|
|
707
|
+
data: {
|
|
708
|
+
user: null,
|
|
709
|
+
session: null
|
|
710
|
+
},
|
|
711
|
+
error: createAuthError(AuthErrorCode.NotImplemented, "setSession() is not supported by Better Auth. Use signInWithPassword() instead.")
|
|
712
|
+
};
|
|
713
|
+
};
|
|
714
|
+
signUp = async (credentials) => {
|
|
715
|
+
try {
|
|
716
|
+
if ("email" in credentials && credentials.email && credentials.password) {
|
|
717
|
+
const displayName = credentials.options?.data && "displayName" in credentials.options.data && typeof credentials.options.data.displayName === "string" ? credentials.options.data.displayName : "";
|
|
718
|
+
const result = await this._betterAuth.signUp.email({
|
|
719
|
+
email: credentials.email,
|
|
720
|
+
password: credentials.password,
|
|
721
|
+
name: displayName,
|
|
722
|
+
callbackURL: credentials.options?.emailRedirectTo,
|
|
723
|
+
...credentials.options?.data
|
|
724
|
+
});
|
|
725
|
+
if (result.error) throw normalizeBetterAuthError(result.error);
|
|
726
|
+
const sessionResult = await this.getSession();
|
|
727
|
+
if (!sessionResult.data.session?.user) throw createAuthError(AuthErrorCode.SessionNotFound, "Failed to retrieve user session");
|
|
728
|
+
return {
|
|
729
|
+
data: {
|
|
730
|
+
user: sessionResult.data.session.user,
|
|
731
|
+
session: sessionResult.data.session
|
|
732
|
+
},
|
|
733
|
+
error: null
|
|
734
|
+
};
|
|
735
|
+
} else if ("phone" in credentials && credentials.phone) throw createAuthError(AuthErrorCode.PhoneProviderDisabled, "Phone sign-up not supported");
|
|
736
|
+
else throw createAuthError(AuthErrorCode.ValidationFailed, "Invalid credentials format");
|
|
737
|
+
} catch (error) {
|
|
738
|
+
if (isAuthError(error)) return {
|
|
739
|
+
data: {
|
|
740
|
+
user: null,
|
|
741
|
+
session: null
|
|
742
|
+
},
|
|
743
|
+
error
|
|
744
|
+
};
|
|
745
|
+
if (isBetterAuthAPIError(error)) return {
|
|
746
|
+
data: {
|
|
747
|
+
user: null,
|
|
748
|
+
session: null
|
|
749
|
+
},
|
|
750
|
+
error: normalizeBetterAuthError(error)
|
|
751
|
+
};
|
|
752
|
+
throw error;
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
signInAnonymously = async (credentials) => {
|
|
756
|
+
try {
|
|
757
|
+
const result = await this._betterAuth.signIn.anonymous({ query: credentials?.options?.data });
|
|
758
|
+
if (result.error) throw normalizeBetterAuthError(result.error);
|
|
759
|
+
const sessionResult = await this.getSession();
|
|
760
|
+
if (!sessionResult.data.session?.user) throw createAuthError(AuthErrorCode.SessionNotFound, "Failed to retrieve user session");
|
|
761
|
+
return {
|
|
762
|
+
data: {
|
|
763
|
+
user: sessionResult.data.session.user,
|
|
764
|
+
session: sessionResult.data.session
|
|
765
|
+
},
|
|
766
|
+
error: null
|
|
767
|
+
};
|
|
768
|
+
} catch (error) {
|
|
769
|
+
if (isAuthError(error)) return {
|
|
770
|
+
data: {
|
|
771
|
+
user: null,
|
|
772
|
+
session: null
|
|
773
|
+
},
|
|
774
|
+
error
|
|
775
|
+
};
|
|
776
|
+
if (isBetterAuthAPIError(error)) return {
|
|
777
|
+
data: {
|
|
778
|
+
user: null,
|
|
779
|
+
session: null
|
|
780
|
+
},
|
|
781
|
+
error: normalizeBetterAuthError(error)
|
|
782
|
+
};
|
|
783
|
+
throw error;
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
signInWithPassword = async (credentials) => {
|
|
787
|
+
try {
|
|
788
|
+
if ("email" in credentials && credentials.email) {
|
|
789
|
+
const result = await this._betterAuth.signIn.email({
|
|
790
|
+
email: credentials.email,
|
|
791
|
+
password: credentials.password
|
|
792
|
+
});
|
|
793
|
+
if (result.error) throw normalizeBetterAuthError(result.error);
|
|
794
|
+
const sessionResult = await this.getSession();
|
|
795
|
+
if (!sessionResult.data.session?.user) throw createAuthError(AuthErrorCode.SessionNotFound, "Failed to retrieve user session");
|
|
796
|
+
return {
|
|
797
|
+
data: {
|
|
798
|
+
user: sessionResult.data.session.user,
|
|
799
|
+
session: sessionResult.data.session
|
|
800
|
+
},
|
|
801
|
+
error: null
|
|
802
|
+
};
|
|
803
|
+
} else if ("phone" in credentials && credentials.phone) throw createAuthError(AuthErrorCode.PhoneProviderDisabled, "Phone sign-in not supported");
|
|
804
|
+
else throw createAuthError(AuthErrorCode.ValidationFailed, "Invalid credentials format");
|
|
805
|
+
} catch (error) {
|
|
806
|
+
if (isAuthError(error)) return {
|
|
807
|
+
data: {
|
|
808
|
+
user: null,
|
|
809
|
+
session: null
|
|
810
|
+
},
|
|
811
|
+
error
|
|
812
|
+
};
|
|
813
|
+
if (isBetterAuthAPIError(error)) return {
|
|
814
|
+
data: {
|
|
815
|
+
user: null,
|
|
816
|
+
session: null
|
|
817
|
+
},
|
|
818
|
+
error: normalizeBetterAuthError(error)
|
|
819
|
+
};
|
|
820
|
+
throw error;
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
signInWithOAuth = async (credentials) => {
|
|
824
|
+
try {
|
|
825
|
+
const { provider, options } = credentials;
|
|
826
|
+
await this._betterAuth.signIn.social({
|
|
827
|
+
provider,
|
|
828
|
+
scopes: options?.scopes?.split(" "),
|
|
829
|
+
disableRedirect: options?.skipBrowserRedirect,
|
|
830
|
+
callbackURL: options?.redirectTo || (globalThis.window === void 0 ? "" : globalThis.location.origin)
|
|
831
|
+
});
|
|
832
|
+
return {
|
|
833
|
+
data: {
|
|
834
|
+
provider,
|
|
835
|
+
url: options?.redirectTo || (globalThis.window === void 0 ? "" : globalThis.location.origin)
|
|
836
|
+
},
|
|
837
|
+
error: null
|
|
838
|
+
};
|
|
839
|
+
} catch (error) {
|
|
840
|
+
if (isAuthError(error)) return {
|
|
841
|
+
data: {
|
|
842
|
+
provider: credentials.provider,
|
|
843
|
+
url: null
|
|
844
|
+
},
|
|
845
|
+
error
|
|
846
|
+
};
|
|
847
|
+
if (isBetterAuthAPIError(error)) return {
|
|
848
|
+
data: {
|
|
849
|
+
provider: credentials.provider,
|
|
850
|
+
url: null
|
|
851
|
+
},
|
|
852
|
+
error: normalizeBetterAuthError(error)
|
|
853
|
+
};
|
|
854
|
+
throw error;
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
signInWithOtp = async (credentials) => {
|
|
858
|
+
try {
|
|
859
|
+
if ("email" in credentials) {
|
|
860
|
+
await this._betterAuth.emailOtp.sendVerificationOtp({
|
|
861
|
+
email: credentials.email,
|
|
862
|
+
type: "sign-in"
|
|
863
|
+
});
|
|
864
|
+
return {
|
|
865
|
+
data: {
|
|
866
|
+
user: null,
|
|
867
|
+
session: null,
|
|
868
|
+
messageId: void 0
|
|
869
|
+
},
|
|
870
|
+
error: null
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
throw createAuthError(AuthErrorCode.NotImplemented, `We haven't implemented this type of otp authentication.`);
|
|
874
|
+
} catch (error) {
|
|
875
|
+
if (isAuthError(error)) return {
|
|
876
|
+
data: {
|
|
877
|
+
user: null,
|
|
878
|
+
session: null,
|
|
879
|
+
messageId: void 0
|
|
880
|
+
},
|
|
881
|
+
error
|
|
882
|
+
};
|
|
883
|
+
if (isBetterAuthAPIError(error)) return {
|
|
884
|
+
data: {
|
|
885
|
+
user: null,
|
|
886
|
+
session: null,
|
|
887
|
+
messageId: void 0
|
|
888
|
+
},
|
|
889
|
+
error: normalizeBetterAuthError(error)
|
|
890
|
+
};
|
|
891
|
+
throw error;
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
signInWithIdToken = async (credentials) => {
|
|
895
|
+
try {
|
|
896
|
+
const result = await this._betterAuth.signIn.social({
|
|
897
|
+
provider: credentials.provider,
|
|
898
|
+
idToken: {
|
|
899
|
+
token: credentials.token,
|
|
900
|
+
accessToken: credentials.access_token,
|
|
901
|
+
nonce: credentials.nonce
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
if (result.error) throw normalizeBetterAuthError(result.error);
|
|
905
|
+
if (!("user" in result.data) || !result.data.user) throw createAuthError(AuthErrorCode.OAuthCallbackFailed, "Failed to sign in with ID token");
|
|
906
|
+
const session = await this.getSession();
|
|
907
|
+
if (session.error || !session.data.session) throw session.error || createAuthError(AuthErrorCode.SessionNotFound, "Failed to get session");
|
|
908
|
+
return {
|
|
909
|
+
data: {
|
|
910
|
+
user: session.data.session.user,
|
|
911
|
+
session: session.data.session
|
|
912
|
+
},
|
|
913
|
+
error: null
|
|
914
|
+
};
|
|
915
|
+
} catch (error) {
|
|
916
|
+
if (isAuthError(error)) return {
|
|
917
|
+
data: {
|
|
918
|
+
user: null,
|
|
919
|
+
session: null
|
|
920
|
+
},
|
|
921
|
+
error
|
|
922
|
+
};
|
|
923
|
+
if (isBetterAuthAPIError(error)) return {
|
|
924
|
+
data: {
|
|
925
|
+
user: null,
|
|
926
|
+
session: null
|
|
927
|
+
},
|
|
928
|
+
error: normalizeBetterAuthError(error)
|
|
929
|
+
};
|
|
930
|
+
throw error;
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
signInWithSSO = async (params) => {
|
|
934
|
+
const attemptedWith = "providerId" in params ? `provider ID: ${params.providerId}` : `domain: ${"domain" in params ? params.domain : "unknown"}`;
|
|
935
|
+
return {
|
|
936
|
+
data: null,
|
|
937
|
+
error: createAuthError(AuthErrorCode.SsoProviderDisabled, `Better Auth does not support enterprise SAML SSO. Attempted with ${attemptedWith}. Use signInWithOAuth() for OAuth providers instead.`)
|
|
938
|
+
};
|
|
939
|
+
};
|
|
940
|
+
signInWithWeb3 = async (credentials) => {
|
|
941
|
+
const attemptedChain = credentials.chain;
|
|
942
|
+
return {
|
|
943
|
+
data: {
|
|
944
|
+
user: null,
|
|
945
|
+
session: null
|
|
946
|
+
},
|
|
947
|
+
error: createAuthError(AuthErrorCode.Web3ProviderDisabled, `Better Auth does not support Web3 authentication. Attempted with chain: ${attemptedChain}. Supported: OAuth, email/password, magic link.`)
|
|
948
|
+
};
|
|
949
|
+
};
|
|
950
|
+
signOut = async () => {
|
|
951
|
+
try {
|
|
952
|
+
const result = await this._betterAuth.signOut();
|
|
953
|
+
if (result.error) throw normalizeBetterAuthError(result.error);
|
|
954
|
+
return { error: null };
|
|
955
|
+
} catch (error) {
|
|
956
|
+
if (isAuthError(error)) return { error };
|
|
957
|
+
if (isBetterAuthAPIError(error)) return { error: normalizeBetterAuthError(error) };
|
|
958
|
+
throw error;
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
getUser = async () => {
|
|
962
|
+
try {
|
|
963
|
+
const sessionResult = await this.getSession();
|
|
964
|
+
if (sessionResult.error || !sessionResult.data.session) throw sessionResult.error || createAuthError(AuthErrorCode.SessionNotFound, "No user session found");
|
|
965
|
+
return {
|
|
966
|
+
data: { user: sessionResult.data.session.user },
|
|
967
|
+
error: null
|
|
968
|
+
};
|
|
969
|
+
} catch (error) {
|
|
970
|
+
if (isAuthError(error)) return {
|
|
971
|
+
data: { user: null },
|
|
972
|
+
error
|
|
973
|
+
};
|
|
974
|
+
if (isBetterAuthAPIError(error)) return {
|
|
975
|
+
data: { user: null },
|
|
976
|
+
error: normalizeBetterAuthError(error)
|
|
977
|
+
};
|
|
978
|
+
throw error;
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
getClaims = async (jwtArg) => {
|
|
982
|
+
try {
|
|
983
|
+
let jwt = jwtArg;
|
|
984
|
+
if (!jwt) {
|
|
985
|
+
const sessionResult = await this.getSession();
|
|
986
|
+
if (sessionResult.error || !sessionResult.data?.session) throw sessionResult.error || createAuthError(AuthErrorCode.SessionNotFound, "No user session found");
|
|
987
|
+
jwt = sessionResult.data.session.access_token;
|
|
988
|
+
}
|
|
989
|
+
if (!jwt) throw createAuthError(AuthErrorCode.SessionNotFound, "No access token found");
|
|
990
|
+
if (jwt.split(".").length !== 3) throw createAuthError(AuthErrorCode.BadJwt, "Invalid token format");
|
|
991
|
+
return {
|
|
992
|
+
data: {
|
|
993
|
+
header: decodeProtectedHeader(jwt),
|
|
994
|
+
claims: decodeJwt(jwt),
|
|
995
|
+
signature: base64url.decode(jwt.split(".")[2])
|
|
996
|
+
},
|
|
997
|
+
error: null
|
|
998
|
+
};
|
|
999
|
+
} catch (error) {
|
|
1000
|
+
if (isAuthError(error)) return {
|
|
1001
|
+
data: null,
|
|
1002
|
+
error
|
|
1003
|
+
};
|
|
1004
|
+
if (isBetterAuthAPIError(error)) return {
|
|
1005
|
+
data: null,
|
|
1006
|
+
error: normalizeBetterAuthError(error)
|
|
1007
|
+
};
|
|
1008
|
+
throw error;
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
updateUser = async (attributes) => {
|
|
1012
|
+
try {
|
|
1013
|
+
if (attributes.password) throw createAuthError(AuthErrorCode.FeatureNotSupported, "The password cannot be updated through the updateUser method, use the changePassword method instead.");
|
|
1014
|
+
if (attributes.email) throw createAuthError(AuthErrorCode.FeatureNotSupported, "The email cannot be updated through the updateUser method, use the changeEmail method instead.");
|
|
1015
|
+
const result = await this._betterAuth.updateUser({ ...attributes.data });
|
|
1016
|
+
if (result.data?.status) throw createAuthError(AuthErrorCode.InternalError, "Failed to update user");
|
|
1017
|
+
if (result?.error) throw normalizeBetterAuthError(result.error);
|
|
1018
|
+
const updatedSessionResult = await this.getSession({ forceFetch: true });
|
|
1019
|
+
if (!updatedSessionResult.data.session) throw createAuthError(AuthErrorCode.SessionNotFound, "Failed to retrieve updated user");
|
|
1020
|
+
return {
|
|
1021
|
+
data: { user: updatedSessionResult.data.session.user },
|
|
1022
|
+
error: null
|
|
1023
|
+
};
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
if (isAuthError(error)) return {
|
|
1026
|
+
data: { user: null },
|
|
1027
|
+
error
|
|
1028
|
+
};
|
|
1029
|
+
if (isBetterAuthAPIError(error)) return {
|
|
1030
|
+
data: { user: null },
|
|
1031
|
+
error: normalizeBetterAuthError(error)
|
|
1032
|
+
};
|
|
1033
|
+
throw error;
|
|
1034
|
+
}
|
|
1035
|
+
};
|
|
1036
|
+
getUserIdentities = async () => {
|
|
1037
|
+
try {
|
|
1038
|
+
const sessionResult = await this.getSession();
|
|
1039
|
+
if (sessionResult.error || !sessionResult.data.session) throw sessionResult.error || createAuthError(AuthErrorCode.SessionNotFound, "No user session found");
|
|
1040
|
+
const result = await this._betterAuth.listAccounts();
|
|
1041
|
+
if (!result) throw createAuthError(AuthErrorCode.InternalError, "Failed to list accounts");
|
|
1042
|
+
if (result.error) throw normalizeBetterAuthError(result.error);
|
|
1043
|
+
const identitiesPromises = result.data.map(async (account) => {
|
|
1044
|
+
let accountInfo = null;
|
|
1045
|
+
try {
|
|
1046
|
+
accountInfo = (await this._betterAuth.accountInfo({ query: { accountId: account.accountId } })).data;
|
|
1047
|
+
} catch (error) {
|
|
1048
|
+
console.warn(`Failed to get account info for ${account.providerId}:`, error);
|
|
1049
|
+
}
|
|
1050
|
+
return mapBetterAuthIdentity(account, accountInfo ?? null);
|
|
1051
|
+
});
|
|
1052
|
+
return {
|
|
1053
|
+
data: { identities: await Promise.all(identitiesPromises) },
|
|
1054
|
+
error: null
|
|
1055
|
+
};
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
if (isAuthError(error)) return {
|
|
1058
|
+
data: null,
|
|
1059
|
+
error
|
|
1060
|
+
};
|
|
1061
|
+
if (isBetterAuthAPIError(error)) return {
|
|
1062
|
+
data: null,
|
|
1063
|
+
error: normalizeBetterAuthError(error)
|
|
1064
|
+
};
|
|
1065
|
+
throw error;
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
linkIdentity = async (credentials) => {
|
|
1069
|
+
const provider = credentials.provider;
|
|
1070
|
+
try {
|
|
1071
|
+
const sessionResult = await this.getSession();
|
|
1072
|
+
if (sessionResult.error || !sessionResult.data.session) throw sessionResult.error || createAuthError(AuthErrorCode.SessionNotFound, "No user session found");
|
|
1073
|
+
if ("token" in credentials) {
|
|
1074
|
+
const result$1 = await this._betterAuth.linkSocial({
|
|
1075
|
+
provider,
|
|
1076
|
+
idToken: {
|
|
1077
|
+
token: credentials.token,
|
|
1078
|
+
accessToken: credentials.access_token,
|
|
1079
|
+
nonce: credentials.nonce
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
if (result$1.error) throw normalizeBetterAuthError(result$1.error);
|
|
1083
|
+
return {
|
|
1084
|
+
data: {
|
|
1085
|
+
user: sessionResult.data.session.user,
|
|
1086
|
+
session: sessionResult.data.session,
|
|
1087
|
+
provider,
|
|
1088
|
+
url: result$1.data?.url
|
|
1089
|
+
},
|
|
1090
|
+
error: null
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
const callbackURL = credentials.options?.redirectTo || (globalThis.window === void 0 ? "" : globalThis.location.origin);
|
|
1094
|
+
const scopes = credentials.options?.scopes?.split(" ").filter((s) => s.length > 0);
|
|
1095
|
+
const result = await this._betterAuth.linkSocial({
|
|
1096
|
+
provider,
|
|
1097
|
+
callbackURL,
|
|
1098
|
+
errorCallbackURL: callbackURL ? `${callbackURL}?error=linking-failed` : void 0,
|
|
1099
|
+
scopes
|
|
1100
|
+
});
|
|
1101
|
+
if (result.error) throw normalizeBetterAuthError(result.error);
|
|
1102
|
+
return {
|
|
1103
|
+
data: {
|
|
1104
|
+
provider,
|
|
1105
|
+
url: result.data?.url,
|
|
1106
|
+
user: sessionResult.data.session.user,
|
|
1107
|
+
session: sessionResult.data.session
|
|
1108
|
+
},
|
|
1109
|
+
error: null
|
|
1110
|
+
};
|
|
1111
|
+
} catch (error) {
|
|
1112
|
+
if (isAuthError(error)) return {
|
|
1113
|
+
data: {
|
|
1114
|
+
provider,
|
|
1115
|
+
url: null,
|
|
1116
|
+
user: null,
|
|
1117
|
+
session: null
|
|
1118
|
+
},
|
|
1119
|
+
error
|
|
1120
|
+
};
|
|
1121
|
+
if (isBetterAuthAPIError(error)) return {
|
|
1122
|
+
data: {
|
|
1123
|
+
provider,
|
|
1124
|
+
url: null,
|
|
1125
|
+
user: null,
|
|
1126
|
+
session: null
|
|
1127
|
+
},
|
|
1128
|
+
error: normalizeBetterAuthError(error)
|
|
1129
|
+
};
|
|
1130
|
+
throw error;
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
unlinkIdentity = async (identity) => {
|
|
1134
|
+
try {
|
|
1135
|
+
const sessionResult = await this.getSession();
|
|
1136
|
+
if (sessionResult.error || !sessionResult.data.session) throw sessionResult.error || createAuthError(AuthErrorCode.SessionNotFound, "No user session found");
|
|
1137
|
+
const identities = await this.getUserIdentities();
|
|
1138
|
+
if (identities.error || !identities.data) throw identities.error || createAuthError(AuthErrorCode.InternalError, "Failed to fetch identities");
|
|
1139
|
+
const targetIdentity = identities.data.identities.find((i) => i.id === identity.identity_id);
|
|
1140
|
+
if (!targetIdentity) throw createAuthError(AuthErrorCode.IdentityNotFound, "Identity not found");
|
|
1141
|
+
const providerId = targetIdentity.provider;
|
|
1142
|
+
const accountId = targetIdentity.identity_id;
|
|
1143
|
+
const result = await this._betterAuth.unlinkAccount({
|
|
1144
|
+
providerId,
|
|
1145
|
+
accountId
|
|
1146
|
+
});
|
|
1147
|
+
if (result?.error) throw normalizeBetterAuthError(result.error);
|
|
1148
|
+
const updatedSession = await this.getSession({ forceFetch: true });
|
|
1149
|
+
if (updatedSession.data.session) BETTER_AUTH_METHODS_HOOKS["updateUser"].onSuccess(updatedSession.data.session);
|
|
1150
|
+
return {
|
|
1151
|
+
data: {},
|
|
1152
|
+
error: null
|
|
1153
|
+
};
|
|
1154
|
+
} catch (error) {
|
|
1155
|
+
if (isAuthError(error)) return {
|
|
1156
|
+
data: null,
|
|
1157
|
+
error
|
|
1158
|
+
};
|
|
1159
|
+
if (isBetterAuthAPIError(error)) return {
|
|
1160
|
+
data: null,
|
|
1161
|
+
error: normalizeBetterAuthError(error)
|
|
1162
|
+
};
|
|
1163
|
+
throw error;
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
verifyOtp = async (params) => {
|
|
1167
|
+
try {
|
|
1168
|
+
if ("email" in params && params.email) return await this.verifyEmailOtp(params);
|
|
1169
|
+
if ("phone" in params && params.phone) return await this.verifyPhoneOtp(params);
|
|
1170
|
+
if ("token_hash" in params && params.token_hash) throw createAuthError(AuthErrorCode.FeatureNotSupported, "Token hash verification not supported");
|
|
1171
|
+
throw createAuthError(AuthErrorCode.ValidationFailed, "Invalid OTP verification parameters");
|
|
1172
|
+
} catch (error) {
|
|
1173
|
+
if (isAuthError(error)) return {
|
|
1174
|
+
data: {
|
|
1175
|
+
user: null,
|
|
1176
|
+
session: null
|
|
1177
|
+
},
|
|
1178
|
+
error
|
|
1179
|
+
};
|
|
1180
|
+
if (isBetterAuthAPIError(error)) return {
|
|
1181
|
+
data: {
|
|
1182
|
+
user: null,
|
|
1183
|
+
session: null
|
|
1184
|
+
},
|
|
1185
|
+
error: normalizeBetterAuthError(error)
|
|
1186
|
+
};
|
|
1187
|
+
throw error;
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
resetPasswordForEmail = async (email, options) => {
|
|
1191
|
+
try {
|
|
1192
|
+
const result = await this._betterAuth.requestPasswordReset({
|
|
1193
|
+
email,
|
|
1194
|
+
redirectTo: options?.redirectTo || (globalThis.window === void 0 ? "" : globalThis.location.origin)
|
|
1195
|
+
});
|
|
1196
|
+
if (result?.error) throw normalizeBetterAuthError(result.error);
|
|
1197
|
+
return {
|
|
1198
|
+
data: {},
|
|
1199
|
+
error: null
|
|
1200
|
+
};
|
|
1201
|
+
} catch (error) {
|
|
1202
|
+
if (isAuthError(error)) return {
|
|
1203
|
+
data: null,
|
|
1204
|
+
error
|
|
1205
|
+
};
|
|
1206
|
+
if (isBetterAuthAPIError(error)) return {
|
|
1207
|
+
data: null,
|
|
1208
|
+
error: normalizeBetterAuthError(error)
|
|
1209
|
+
};
|
|
1210
|
+
throw error;
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
reauthenticate = async () => {
|
|
1214
|
+
try {
|
|
1215
|
+
const newSession = await this.getSession();
|
|
1216
|
+
if (newSession.error || !newSession.data.session) throw newSession.error || createAuthError(AuthErrorCode.SessionNotFound, "No session found");
|
|
1217
|
+
return {
|
|
1218
|
+
data: {
|
|
1219
|
+
user: newSession.data.session?.user || null,
|
|
1220
|
+
session: newSession.data.session
|
|
1221
|
+
},
|
|
1222
|
+
error: null
|
|
1223
|
+
};
|
|
1224
|
+
} catch (error) {
|
|
1225
|
+
if (isAuthError(error)) return {
|
|
1226
|
+
data: {
|
|
1227
|
+
user: null,
|
|
1228
|
+
session: null
|
|
1229
|
+
},
|
|
1230
|
+
error
|
|
1231
|
+
};
|
|
1232
|
+
if (isBetterAuthAPIError(error)) return {
|
|
1233
|
+
data: {
|
|
1234
|
+
user: null,
|
|
1235
|
+
session: null
|
|
1236
|
+
},
|
|
1237
|
+
error: normalizeBetterAuthError(error)
|
|
1238
|
+
};
|
|
1239
|
+
throw error;
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
resend = async (credentials) => {
|
|
1243
|
+
try {
|
|
1244
|
+
if ("email" in credentials) {
|
|
1245
|
+
const { email, type, options } = credentials;
|
|
1246
|
+
if (type === "signup" || type === "email_change") {
|
|
1247
|
+
const result = await this._betterAuth.sendVerificationEmail({
|
|
1248
|
+
email,
|
|
1249
|
+
callbackURL: options?.emailRedirectTo || (globalThis.window === void 0 ? "" : globalThis.location.origin)
|
|
1250
|
+
});
|
|
1251
|
+
if (result?.error) throw normalizeBetterAuthError(result.error);
|
|
1252
|
+
return {
|
|
1253
|
+
data: {
|
|
1254
|
+
user: null,
|
|
1255
|
+
session: null
|
|
1256
|
+
},
|
|
1257
|
+
error: null
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
throw createAuthError(AuthErrorCode.ValidationFailed, `Unsupported resend type: ${type}`);
|
|
1261
|
+
}
|
|
1262
|
+
if ("phone" in credentials) {
|
|
1263
|
+
const { phone, type } = credentials;
|
|
1264
|
+
if (type === "sms" || type === "phone_change") {
|
|
1265
|
+
const result = await this._betterAuth.phoneNumber.sendOtp({ phoneNumber: phone });
|
|
1266
|
+
if (result?.error) throw normalizeBetterAuthError(result.error);
|
|
1267
|
+
return {
|
|
1268
|
+
data: {
|
|
1269
|
+
messageId: type === "sms" ? "sms-otp-sent" : "phone-change-otp-sent",
|
|
1270
|
+
user: null,
|
|
1271
|
+
session: null
|
|
1272
|
+
},
|
|
1273
|
+
error: null
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
throw createAuthError(AuthErrorCode.ValidationFailed, "Invalid credentials format");
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
if (isAuthError(error)) return {
|
|
1280
|
+
data: {
|
|
1281
|
+
user: null,
|
|
1282
|
+
session: null
|
|
1283
|
+
},
|
|
1284
|
+
error
|
|
1285
|
+
};
|
|
1286
|
+
if (isBetterAuthAPIError(error)) return {
|
|
1287
|
+
data: {
|
|
1288
|
+
user: null,
|
|
1289
|
+
session: null
|
|
1290
|
+
},
|
|
1291
|
+
error: normalizeBetterAuthError(error)
|
|
1292
|
+
};
|
|
1293
|
+
throw error;
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
exchangeCodeForSession = async (_authCode) => {
|
|
1297
|
+
try {
|
|
1298
|
+
const sessionResult = await this.getSession();
|
|
1299
|
+
if (sessionResult.data.session) return {
|
|
1300
|
+
data: {
|
|
1301
|
+
session: sessionResult.data.session,
|
|
1302
|
+
user: sessionResult.data.session.user
|
|
1303
|
+
},
|
|
1304
|
+
error: null
|
|
1305
|
+
};
|
|
1306
|
+
throw createAuthError(AuthErrorCode.OAuthCallbackFailed, "OAuth callback completed but no session was created. Make sure the OAuth callback has been processed.");
|
|
1307
|
+
} catch (error) {
|
|
1308
|
+
if (isAuthError(error)) return {
|
|
1309
|
+
data: {
|
|
1310
|
+
session: null,
|
|
1311
|
+
user: null
|
|
1312
|
+
},
|
|
1313
|
+
error
|
|
1314
|
+
};
|
|
1315
|
+
if (isBetterAuthAPIError(error)) return {
|
|
1316
|
+
data: {
|
|
1317
|
+
session: null,
|
|
1318
|
+
user: null
|
|
1319
|
+
},
|
|
1320
|
+
error: normalizeBetterAuthError(error)
|
|
1321
|
+
};
|
|
1322
|
+
throw error;
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1325
|
+
onAuthStateChange = (callback) => {
|
|
1326
|
+
const id = crypto.randomUUID();
|
|
1327
|
+
const subscription = {
|
|
1328
|
+
id,
|
|
1329
|
+
callback,
|
|
1330
|
+
unsubscribe: () => {
|
|
1331
|
+
this._stateChangeEmitters.delete(id);
|
|
1332
|
+
}
|
|
1333
|
+
};
|
|
1334
|
+
this._stateChangeEmitters.set(id, subscription);
|
|
1335
|
+
this.emitInitialSession(callback);
|
|
1336
|
+
return { data: { subscription: {
|
|
1337
|
+
id,
|
|
1338
|
+
callback,
|
|
1339
|
+
unsubscribe: subscription.unsubscribe
|
|
1340
|
+
} } };
|
|
1341
|
+
};
|
|
1342
|
+
isThrowOnErrorEnabled = () => false;
|
|
1343
|
+
startAutoRefresh = async () => {};
|
|
1344
|
+
stopAutoRefresh = async () => {};
|
|
1345
|
+
async verifyEmailOtp(params) {
|
|
1346
|
+
const { type } = params;
|
|
1347
|
+
if (type === "email") {
|
|
1348
|
+
const result = await this._betterAuth.signIn.emailOtp({
|
|
1349
|
+
email: params.email,
|
|
1350
|
+
otp: params.token
|
|
1351
|
+
});
|
|
1352
|
+
if (result.error) return {
|
|
1353
|
+
data: {
|
|
1354
|
+
user: null,
|
|
1355
|
+
session: null
|
|
1356
|
+
},
|
|
1357
|
+
error: normalizeBetterAuthError(result.error)
|
|
1358
|
+
};
|
|
1359
|
+
const sessionResult = await this.getSession({ forceFetch: true });
|
|
1360
|
+
if (!sessionResult.data.session) return {
|
|
1361
|
+
data: {
|
|
1362
|
+
user: null,
|
|
1363
|
+
session: null
|
|
1364
|
+
},
|
|
1365
|
+
error: createAuthError(AuthErrorCode.SessionNotFound, "Failed to retrieve session after OTP verification. Make sure the magic link callback has been processed.")
|
|
1366
|
+
};
|
|
1367
|
+
return {
|
|
1368
|
+
data: {
|
|
1369
|
+
user: sessionResult.data.session.user,
|
|
1370
|
+
session: sessionResult.data.session
|
|
1371
|
+
},
|
|
1372
|
+
error: null
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
if (type === "magiclink") {
|
|
1376
|
+
const result = await this._betterAuth.magicLink.verify({ query: {
|
|
1377
|
+
token: params.token,
|
|
1378
|
+
callbackURL: params.options?.redirectTo || (globalThis.window === void 0 ? "" : globalThis.location.origin)
|
|
1379
|
+
} });
|
|
1380
|
+
if (result?.error) return {
|
|
1381
|
+
data: {
|
|
1382
|
+
user: null,
|
|
1383
|
+
session: null
|
|
1384
|
+
},
|
|
1385
|
+
error: normalizeBetterAuthError(result.error)
|
|
1386
|
+
};
|
|
1387
|
+
const sessionResult = await this.getSession({ forceFetch: true });
|
|
1388
|
+
if (!sessionResult.data?.session) return {
|
|
1389
|
+
data: {
|
|
1390
|
+
user: null,
|
|
1391
|
+
session: null
|
|
1392
|
+
},
|
|
1393
|
+
error: createAuthError(AuthErrorCode.SessionNotFound, "Failed to retrieve session after magic link verification")
|
|
1394
|
+
};
|
|
1395
|
+
return {
|
|
1396
|
+
data: {
|
|
1397
|
+
user: sessionResult.data.session?.user || null,
|
|
1398
|
+
session: sessionResult.data.session
|
|
1399
|
+
},
|
|
1400
|
+
error: null
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
if (type === "signup" || type === "invite") {
|
|
1404
|
+
const result = await this._betterAuth.emailOtp.verifyEmail({
|
|
1405
|
+
email: params.email,
|
|
1406
|
+
otp: params.token
|
|
1407
|
+
});
|
|
1408
|
+
if (result?.error) return {
|
|
1409
|
+
data: {
|
|
1410
|
+
user: null,
|
|
1411
|
+
session: null
|
|
1412
|
+
},
|
|
1413
|
+
error: normalizeBetterAuthError(result.error)
|
|
1414
|
+
};
|
|
1415
|
+
const sessionResult = await this.getSession({ forceFetch: true });
|
|
1416
|
+
return {
|
|
1417
|
+
data: {
|
|
1418
|
+
user: sessionResult.data.session?.user ?? null,
|
|
1419
|
+
session: sessionResult.data.session
|
|
1420
|
+
},
|
|
1421
|
+
error: null
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
if (type === "recovery") {
|
|
1425
|
+
const checkResult = await this._betterAuth.emailOtp.checkVerificationOtp({
|
|
1426
|
+
email: params.email,
|
|
1427
|
+
otp: params.token,
|
|
1428
|
+
type: "forget-password"
|
|
1429
|
+
});
|
|
1430
|
+
if (checkResult.error || !checkResult.data?.success) return {
|
|
1431
|
+
data: {
|
|
1432
|
+
user: null,
|
|
1433
|
+
session: null
|
|
1434
|
+
},
|
|
1435
|
+
error: normalizeBetterAuthError(checkResult.error)
|
|
1436
|
+
};
|
|
1437
|
+
return {
|
|
1438
|
+
data: {
|
|
1439
|
+
user: null,
|
|
1440
|
+
session: null
|
|
1441
|
+
},
|
|
1442
|
+
error: null
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
if (type === "email_change") {
|
|
1446
|
+
const result = await this._betterAuth.verifyEmail({ query: {
|
|
1447
|
+
token: params.token,
|
|
1448
|
+
callbackURL: params.options?.redirectTo
|
|
1449
|
+
} });
|
|
1450
|
+
if (result?.error) return {
|
|
1451
|
+
data: {
|
|
1452
|
+
user: null,
|
|
1453
|
+
session: null
|
|
1454
|
+
},
|
|
1455
|
+
error: normalizeBetterAuthError(result.error)
|
|
1456
|
+
};
|
|
1457
|
+
const sessionResult = await this.getSession({ forceFetch: true });
|
|
1458
|
+
if (sessionResult.error || !sessionResult.data) return {
|
|
1459
|
+
data: {
|
|
1460
|
+
user: null,
|
|
1461
|
+
session: null
|
|
1462
|
+
},
|
|
1463
|
+
error: sessionResult.error || createAuthError(AuthErrorCode.InternalError, "Failed to get session")
|
|
1464
|
+
};
|
|
1465
|
+
if (sessionResult.data.session) BETTER_AUTH_METHODS_HOOKS["updateUser"].onSuccess(sessionResult.data.session);
|
|
1466
|
+
return {
|
|
1467
|
+
data: {
|
|
1468
|
+
user: sessionResult.data?.session?.user || null,
|
|
1469
|
+
session: sessionResult.data?.session || null
|
|
1470
|
+
},
|
|
1471
|
+
error: null
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
if (type === "invite") {
|
|
1475
|
+
const result = await this._betterAuth.organization.acceptInvitation({ invitationId: params.token });
|
|
1476
|
+
if (result.error) return {
|
|
1477
|
+
data: {
|
|
1478
|
+
user: null,
|
|
1479
|
+
session: null
|
|
1480
|
+
},
|
|
1481
|
+
error: normalizeBetterAuthError(result.error)
|
|
1482
|
+
};
|
|
1483
|
+
const sessionResult = await this.getSession({ forceFetch: true });
|
|
1484
|
+
if (sessionResult.error || !sessionResult.data) return {
|
|
1485
|
+
data: {
|
|
1486
|
+
user: null,
|
|
1487
|
+
session: null
|
|
1488
|
+
},
|
|
1489
|
+
error: sessionResult.error || createAuthError(AuthErrorCode.InternalError, "Failed to get session")
|
|
1490
|
+
};
|
|
1491
|
+
return {
|
|
1492
|
+
data: {
|
|
1493
|
+
user: sessionResult.data?.session?.user || null,
|
|
1494
|
+
session: sessionResult.data?.session
|
|
1495
|
+
},
|
|
1496
|
+
error: null
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
return {
|
|
1500
|
+
data: {
|
|
1501
|
+
user: null,
|
|
1502
|
+
session: null
|
|
1503
|
+
},
|
|
1504
|
+
error: createAuthError(AuthErrorCode.ValidationFailed, `Unsupported email OTP type: ${type}`)
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
async verifyPhoneOtp(params) {
|
|
1508
|
+
if (params.type === "sms") {
|
|
1509
|
+
const result = await this._betterAuth.phoneNumber.verify({
|
|
1510
|
+
phoneNumber: params.phone,
|
|
1511
|
+
code: params.token,
|
|
1512
|
+
disableSession: false,
|
|
1513
|
+
updatePhoneNumber: false
|
|
1514
|
+
});
|
|
1515
|
+
if (result.error) return {
|
|
1516
|
+
data: {
|
|
1517
|
+
user: null,
|
|
1518
|
+
session: null
|
|
1519
|
+
},
|
|
1520
|
+
error: normalizeBetterAuthError(result.error)
|
|
1521
|
+
};
|
|
1522
|
+
const sessionResult = await this.getSession({ forceFetch: true });
|
|
1523
|
+
if (sessionResult.error || !sessionResult.data) return {
|
|
1524
|
+
data: {
|
|
1525
|
+
user: null,
|
|
1526
|
+
session: null
|
|
1527
|
+
},
|
|
1528
|
+
error: sessionResult.error || createAuthError(AuthErrorCode.InternalError, "Failed to get session")
|
|
1529
|
+
};
|
|
1530
|
+
return {
|
|
1531
|
+
data: {
|
|
1532
|
+
user: sessionResult.data?.session?.user || null,
|
|
1533
|
+
session: sessionResult.data?.session || null
|
|
1534
|
+
},
|
|
1535
|
+
error: null
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
if (params.type === "phone_change") {
|
|
1539
|
+
const currentSession = await this._betterAuth.getSession();
|
|
1540
|
+
if (currentSession.error || !currentSession.data?.session) return {
|
|
1541
|
+
data: {
|
|
1542
|
+
user: null,
|
|
1543
|
+
session: null
|
|
1544
|
+
},
|
|
1545
|
+
error: createAuthError(AuthErrorCode.SessionNotFound, "You must be signed in to change your phone number")
|
|
1546
|
+
};
|
|
1547
|
+
const result = await this._betterAuth.phoneNumber.verify({
|
|
1548
|
+
phoneNumber: params.phone,
|
|
1549
|
+
code: params.token,
|
|
1550
|
+
disableSession: false,
|
|
1551
|
+
updatePhoneNumber: true
|
|
1552
|
+
});
|
|
1553
|
+
if (result.error) return {
|
|
1554
|
+
data: {
|
|
1555
|
+
user: null,
|
|
1556
|
+
session: null
|
|
1557
|
+
},
|
|
1558
|
+
error: normalizeBetterAuthError(result.error)
|
|
1559
|
+
};
|
|
1560
|
+
const sessionResult = await this.getSession({ forceFetch: true });
|
|
1561
|
+
if (sessionResult.error || !sessionResult.data) return {
|
|
1562
|
+
data: {
|
|
1563
|
+
user: null,
|
|
1564
|
+
session: null
|
|
1565
|
+
},
|
|
1566
|
+
error: sessionResult.error || createAuthError(AuthErrorCode.InternalError, "Failed to get updated session")
|
|
1567
|
+
};
|
|
1568
|
+
return {
|
|
1569
|
+
data: {
|
|
1570
|
+
user: sessionResult.data.session?.user || null,
|
|
1571
|
+
session: sessionResult.data.session
|
|
1572
|
+
},
|
|
1573
|
+
error: null
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
return {
|
|
1577
|
+
data: {
|
|
1578
|
+
user: null,
|
|
1579
|
+
session: null
|
|
1580
|
+
},
|
|
1581
|
+
error: createAuthError(AuthErrorCode.ValidationFailed, `Unsupported phone OTP type: ${params.type}`)
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
async emitInitialSession(callback) {
|
|
1585
|
+
try {
|
|
1586
|
+
const { data, error } = await this.getSession();
|
|
1587
|
+
if (error) {
|
|
1588
|
+
await callback("INITIAL_SESSION", null);
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
await callback("INITIAL_SESSION", data.session);
|
|
1592
|
+
} catch {
|
|
1593
|
+
await callback("INITIAL_SESSION", null);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
};
|
|
1597
|
+
/**
|
|
1598
|
+
* Factory function that returns an adapter builder.
|
|
1599
|
+
* The builder is called by createClient/createAuthClient with the URL.
|
|
1600
|
+
*
|
|
1601
|
+
* @param options - Optional adapter configuration (baseURL is injected separately)
|
|
1602
|
+
* @returns A builder function that creates the adapter instance
|
|
1603
|
+
*
|
|
1604
|
+
* @example
|
|
1605
|
+
* ```typescript
|
|
1606
|
+
* const client = createClient({
|
|
1607
|
+
* auth: {
|
|
1608
|
+
* url: 'https://auth.example.com',
|
|
1609
|
+
* adapter: SupabaseAuthAdapter(),
|
|
1610
|
+
* },
|
|
1611
|
+
* dataApi: { url: 'https://data-api.example.com' },
|
|
1612
|
+
* });
|
|
1613
|
+
* ```
|
|
1614
|
+
*/
|
|
1615
|
+
function SupabaseAuthAdapter(options) {
|
|
1616
|
+
return (url) => new SupabaseAuthAdapterImpl({
|
|
1617
|
+
baseURL: url,
|
|
1618
|
+
...options
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
//#endregion
|
|
1623
|
+
export { BetterAuthVanillaAdapter as n, SupabaseAuthAdapter as t };
|