better-auth 1.4.10 → 1.5.0-beta.2
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/dist/api/index.d.mts +400 -399
- package/dist/api/index.mjs +6 -4
- package/dist/api/index.mjs.map +1 -1
- package/dist/api/middlewares/origin-check.d.mts +4 -4
- package/dist/api/middlewares/origin-check.mjs +14 -5
- package/dist/api/middlewares/origin-check.mjs.map +1 -1
- package/dist/api/routes/account.d.mts +11 -11
- package/dist/api/routes/account.mjs +59 -30
- package/dist/api/routes/account.mjs.map +1 -1
- package/dist/api/routes/callback.d.mts +2 -2
- package/dist/api/routes/email-verification.d.mts +4 -4
- package/dist/api/routes/email-verification.mjs +14 -14
- package/dist/api/routes/email-verification.mjs.map +1 -1
- package/dist/api/routes/error.d.mts +2 -2
- package/dist/api/routes/ok.d.mts +2 -2
- package/dist/api/routes/reset-password.d.mts +5 -5
- package/dist/api/routes/reset-password.mjs +9 -7
- package/dist/api/routes/reset-password.mjs.map +1 -1
- package/dist/api/routes/session.d.mts +14 -14
- package/dist/api/routes/session.mjs +31 -11
- package/dist/api/routes/session.mjs.map +1 -1
- package/dist/api/routes/sign-in.d.mts +4 -4
- package/dist/api/routes/sign-in.mjs +22 -17
- package/dist/api/routes/sign-in.mjs.map +1 -1
- package/dist/api/routes/sign-out.d.mts +2 -2
- package/dist/api/routes/sign-up.d.mts +3 -3
- package/dist/api/routes/sign-up.mjs +16 -13
- package/dist/api/routes/sign-up.mjs.map +1 -1
- package/dist/api/routes/update-user.d.mts +13 -13
- package/dist/api/routes/update-user.mjs +29 -24
- package/dist/api/routes/update-user.mjs.map +1 -1
- package/dist/api/to-auth-endpoints.mjs +8 -6
- package/dist/api/to-auth-endpoints.mjs.map +1 -1
- package/dist/client/lynx/index.d.mts +13 -13
- package/dist/client/plugins/index.d.mts +11 -1
- package/dist/client/plugins/index.mjs +11 -1
- package/dist/client/svelte/index.d.mts +15 -15
- package/dist/client/types.d.mts +4 -1
- package/dist/client/vue/index.d.mts +15 -15
- package/dist/context/create-context.mjs +2 -2
- package/dist/context/create-context.mjs.map +1 -1
- package/dist/context/helpers.mjs +2 -2
- package/dist/context/helpers.mjs.map +1 -1
- package/dist/db/field.d.mts +6 -6
- package/dist/db/schema.mjs +14 -5
- package/dist/db/schema.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/integrations/next-js.d.mts +4 -4
- package/dist/integrations/svelte-kit.d.mts +2 -2
- package/dist/integrations/tanstack-start.d.mts +4 -4
- package/dist/oauth2/link-account.mjs +3 -2
- package/dist/oauth2/link-account.mjs.map +1 -1
- package/dist/oauth2/state.mjs +3 -3
- package/dist/oauth2/state.mjs.map +1 -1
- package/dist/plugins/admin/admin.d.mts +84 -21
- package/dist/plugins/admin/admin.mjs +3 -4
- package/dist/plugins/admin/admin.mjs.map +1 -1
- package/dist/plugins/admin/client.d.mts +87 -0
- package/dist/plugins/admin/client.mjs +3 -1
- package/dist/plugins/admin/client.mjs.map +1 -1
- package/dist/plugins/admin/error-codes.d.mts +90 -0
- package/dist/plugins/admin/error-codes.mjs.map +1 -1
- package/dist/plugins/admin/routes.mjs +40 -46
- package/dist/plugins/admin/routes.mjs.map +1 -1
- package/dist/plugins/anonymous/client.d.mts +19 -0
- package/dist/plugins/anonymous/client.mjs +4 -1
- package/dist/plugins/anonymous/client.mjs.map +1 -1
- package/dist/plugins/anonymous/error-codes.d.mts +22 -0
- package/dist/plugins/anonymous/index.d.mts +21 -9
- package/dist/plugins/anonymous/index.mjs +5 -5
- package/dist/plugins/anonymous/index.mjs.map +1 -1
- package/dist/plugins/api-key/client.d.mts +103 -0
- package/dist/plugins/api-key/client.mjs +4 -1
- package/dist/plugins/api-key/client.mjs.map +1 -1
- package/dist/plugins/api-key/error-codes.d.mts +106 -0
- package/dist/plugins/api-key/error-codes.mjs +34 -0
- package/dist/plugins/api-key/error-codes.mjs.map +1 -0
- package/dist/plugins/api-key/index.d.mts +138 -69
- package/dist/plugins/api-key/index.mjs +7 -34
- package/dist/plugins/api-key/index.mjs.map +1 -1
- package/dist/plugins/api-key/rate-limit.mjs +3 -2
- package/dist/plugins/api-key/rate-limit.mjs.map +1 -1
- package/dist/plugins/api-key/routes/create-api-key.mjs +19 -17
- package/dist/plugins/api-key/routes/create-api-key.mjs.map +1 -1
- package/dist/plugins/api-key/routes/delete-api-key.mjs +7 -5
- package/dist/plugins/api-key/routes/delete-api-key.mjs.map +1 -1
- package/dist/plugins/api-key/routes/get-api-key.mjs +5 -3
- package/dist/plugins/api-key/routes/get-api-key.mjs.map +1 -1
- package/dist/plugins/api-key/routes/update-api-key.mjs +18 -16
- package/dist/plugins/api-key/routes/update-api-key.mjs.map +1 -1
- package/dist/plugins/api-key/routes/verify-api-key.mjs +16 -35
- package/dist/plugins/api-key/routes/verify-api-key.mjs.map +1 -1
- package/dist/plugins/bearer/index.d.mts +6 -6
- package/dist/plugins/captcha/index.d.mts +2 -2
- package/dist/plugins/captcha/index.mjs +3 -3
- package/dist/plugins/captcha/index.mjs.map +1 -1
- package/dist/plugins/captcha/verify-handlers/captchafox.mjs +2 -2
- package/dist/plugins/captcha/verify-handlers/captchafox.mjs.map +1 -1
- package/dist/plugins/captcha/verify-handlers/cloudflare-turnstile.mjs +2 -2
- package/dist/plugins/captcha/verify-handlers/cloudflare-turnstile.mjs.map +1 -1
- package/dist/plugins/captcha/verify-handlers/google-recaptcha.mjs +2 -2
- package/dist/plugins/captcha/verify-handlers/google-recaptcha.mjs.map +1 -1
- package/dist/plugins/captcha/verify-handlers/h-captcha.mjs +2 -2
- package/dist/plugins/captcha/verify-handlers/h-captcha.mjs.map +1 -1
- package/dist/plugins/custom-session/index.d.mts +5 -5
- package/dist/plugins/device-authorization/index.d.mts +54 -18
- package/dist/plugins/device-authorization/routes.mjs +18 -18
- package/dist/plugins/device-authorization/routes.mjs.map +1 -1
- package/dist/plugins/email-otp/client.d.mts +15 -0
- package/dist/plugins/email-otp/client.mjs +4 -1
- package/dist/plugins/email-otp/client.mjs.map +1 -1
- package/dist/plugins/email-otp/error-codes.d.mts +18 -0
- package/dist/plugins/email-otp/error-codes.mjs +12 -0
- package/dist/plugins/email-otp/error-codes.mjs.map +1 -0
- package/dist/plugins/email-otp/index.d.mts +64 -55
- package/dist/plugins/email-otp/index.mjs +4 -3
- package/dist/plugins/email-otp/index.mjs.map +1 -1
- package/dist/plugins/email-otp/routes.mjs +30 -35
- package/dist/plugins/email-otp/routes.mjs.map +1 -1
- package/dist/plugins/generic-oauth/client.d.mts +27 -0
- package/dist/plugins/generic-oauth/client.mjs +4 -1
- package/dist/plugins/generic-oauth/client.mjs.map +1 -1
- package/dist/plugins/generic-oauth/error-codes.d.mts +30 -0
- package/dist/plugins/generic-oauth/index.d.mts +55 -37
- package/dist/plugins/generic-oauth/index.mjs +4 -4
- package/dist/plugins/generic-oauth/index.mjs.map +1 -1
- package/dist/plugins/generic-oauth/routes.mjs +11 -12
- package/dist/plugins/generic-oauth/routes.mjs.map +1 -1
- package/dist/plugins/haveibeenpwned/index.d.mts +7 -4
- package/dist/plugins/haveibeenpwned/index.mjs +5 -4
- package/dist/plugins/haveibeenpwned/index.mjs.map +1 -1
- package/dist/plugins/index.d.mts +4 -2
- package/dist/plugins/index.mjs +6 -4
- package/dist/plugins/jwt/client.d.mts +2 -2
- package/dist/plugins/jwt/index.d.mts +9 -9
- package/dist/plugins/jwt/index.mjs +2 -2
- package/dist/plugins/jwt/index.mjs.map +1 -1
- package/dist/plugins/last-login-method/index.d.mts +2 -2
- package/dist/plugins/magic-link/index.d.mts +4 -4
- package/dist/plugins/mcp/authorize.mjs +1 -1
- package/dist/plugins/mcp/authorize.mjs.map +1 -1
- package/dist/plugins/mcp/index.d.mts +10 -10
- package/dist/plugins/multi-session/client.d.mts +7 -0
- package/dist/plugins/multi-session/client.mjs +4 -1
- package/dist/plugins/multi-session/client.mjs.map +1 -1
- package/dist/plugins/multi-session/error-codes.d.mts +10 -0
- package/dist/plugins/multi-session/error-codes.mjs +8 -0
- package/dist/plugins/multi-session/error-codes.mjs.map +1 -0
- package/dist/plugins/multi-session/index.d.mts +20 -16
- package/dist/plugins/multi-session/index.mjs +6 -7
- package/dist/plugins/multi-session/index.mjs.map +1 -1
- package/dist/plugins/oauth-proxy/index.d.mts +8 -8
- package/dist/plugins/oidc-provider/authorize.mjs +1 -1
- package/dist/plugins/oidc-provider/authorize.mjs.map +1 -1
- package/dist/plugins/oidc-provider/error.mjs +1 -1
- package/dist/plugins/oidc-provider/error.mjs.map +1 -1
- package/dist/plugins/oidc-provider/index.d.mts +15 -15
- package/dist/plugins/one-tap/client.d.mts +5 -5
- package/dist/plugins/one-tap/index.d.mts +2 -2
- package/dist/plugins/one-time-token/index.d.mts +5 -5
- package/dist/plugins/open-api/index.d.mts +3 -3
- package/dist/plugins/organization/client.d.mts +236 -9
- package/dist/plugins/organization/client.mjs +3 -1
- package/dist/plugins/organization/client.mjs.map +1 -1
- package/dist/plugins/organization/error-codes.d.mts +224 -56
- package/dist/plugins/organization/organization.d.mts +7 -7
- package/dist/plugins/organization/organization.mjs +4 -4
- package/dist/plugins/organization/organization.mjs.map +1 -1
- package/dist/plugins/organization/routes/crud-access-control.d.mts +22 -22
- package/dist/plugins/organization/routes/crud-access-control.mjs +40 -39
- package/dist/plugins/organization/routes/crud-access-control.mjs.map +1 -1
- package/dist/plugins/organization/routes/crud-invites.d.mts +58 -58
- package/dist/plugins/organization/routes/crud-invites.mjs +42 -40
- package/dist/plugins/organization/routes/crud-invites.mjs.map +1 -1
- package/dist/plugins/organization/routes/crud-members.d.mts +67 -67
- package/dist/plugins/organization/routes/crud-members.mjs +41 -54
- package/dist/plugins/organization/routes/crud-members.mjs.map +1 -1
- package/dist/plugins/organization/routes/crud-org.d.mts +22 -22
- package/dist/plugins/organization/routes/crud-org.mjs +28 -25
- package/dist/plugins/organization/routes/crud-org.mjs.map +1 -1
- package/dist/plugins/organization/routes/crud-team.d.mts +34 -34
- package/dist/plugins/organization/routes/crud-team.mjs +41 -47
- package/dist/plugins/organization/routes/crud-team.mjs.map +1 -1
- package/dist/plugins/phone-number/client.d.mts +51 -0
- package/dist/plugins/phone-number/client.mjs +4 -1
- package/dist/plugins/phone-number/client.mjs.map +1 -1
- package/dist/plugins/phone-number/error-codes.d.mts +54 -0
- package/dist/plugins/phone-number/index.d.mts +81 -45
- package/dist/plugins/phone-number/index.mjs +2 -2
- package/dist/plugins/phone-number/index.mjs.map +1 -1
- package/dist/plugins/phone-number/routes.mjs +27 -28
- package/dist/plugins/phone-number/routes.mjs.map +1 -1
- package/dist/plugins/siwe/index.d.mts +3 -3
- package/dist/plugins/siwe/index.mjs +7 -6
- package/dist/plugins/siwe/index.mjs.map +1 -1
- package/dist/plugins/two-factor/backup-codes/index.mjs +7 -7
- package/dist/plugins/two-factor/backup-codes/index.mjs.map +1 -1
- package/dist/plugins/two-factor/client.d.mts +39 -0
- package/dist/plugins/two-factor/client.mjs +4 -1
- package/dist/plugins/two-factor/client.mjs.map +1 -1
- package/dist/plugins/two-factor/error-code.d.mts +36 -9
- package/dist/plugins/two-factor/index.d.mts +54 -27
- package/dist/plugins/two-factor/index.mjs +4 -5
- package/dist/plugins/two-factor/index.mjs.map +1 -1
- package/dist/plugins/two-factor/otp/index.mjs +8 -6
- package/dist/plugins/two-factor/otp/index.mjs.map +1 -1
- package/dist/plugins/two-factor/totp/index.mjs +16 -8
- package/dist/plugins/two-factor/totp/index.mjs.map +1 -1
- package/dist/plugins/two-factor/verify-two-factor.mjs +9 -6
- package/dist/plugins/two-factor/verify-two-factor.mjs.map +1 -1
- package/dist/plugins/username/client.d.mts +35 -0
- package/dist/plugins/username/client.mjs +4 -1
- package/dist/plugins/username/client.mjs.map +1 -1
- package/dist/plugins/username/error-codes.d.mts +32 -8
- package/dist/plugins/username/index.d.mts +44 -20
- package/dist/plugins/username/index.mjs +21 -31
- package/dist/plugins/username/index.mjs.map +1 -1
- package/dist/plugins/username/schema.d.mts +3 -3
- package/dist/test-utils/test-instance.d.mts +481 -344
- package/dist/utils/is-api-error.d.mts +7 -0
- package/dist/utils/is-api-error.mjs +11 -0
- package/dist/utils/is-api-error.mjs.map +1 -0
- package/dist/utils/password.mjs +3 -3
- package/dist/utils/password.mjs.map +1 -1
- package/dist/utils/plugin-helper.mjs +2 -2
- package/dist/utils/plugin-helper.mjs.map +1 -1
- package/package.json +3 -3
package/dist/api/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getIp } from "../utils/get-request-ip.mjs";
|
|
2
|
+
import { isAPIError } from "../utils/is-api-error.mjs";
|
|
2
3
|
import { getOAuthState } from "./middlewares/oauth.mjs";
|
|
3
4
|
import { formCsrfMiddleware, originCheck, originCheckMiddleware } from "./middlewares/origin-check.mjs";
|
|
4
5
|
import "./middlewares/index.mjs";
|
|
@@ -17,7 +18,8 @@ import { changeEmail, changePassword, deleteUser, deleteUserCallback, setPasswor
|
|
|
17
18
|
import "./routes/index.mjs";
|
|
18
19
|
import { toAuthEndpoints } from "./to-auth-endpoints.mjs";
|
|
19
20
|
import { logger } from "@better-auth/core/env";
|
|
20
|
-
import { APIError
|
|
21
|
+
import { APIError } from "@better-auth/core/error";
|
|
22
|
+
import { createRouter } from "better-call";
|
|
21
23
|
import { createAuthEndpoint, createAuthMiddleware, optionsMiddleware } from "@better-auth/core/api";
|
|
22
24
|
|
|
23
25
|
//#region src/api/index.ts
|
|
@@ -171,7 +173,7 @@ const router = (ctx, options) => {
|
|
|
171
173
|
return res;
|
|
172
174
|
},
|
|
173
175
|
onError(e) {
|
|
174
|
-
if (e
|
|
176
|
+
if (isAPIError(e) && e.status === "FOUND") return;
|
|
175
177
|
if (options.onAPIError?.throw) throw e;
|
|
176
178
|
if (options.onAPIError?.onError) {
|
|
177
179
|
options.onAPIError.onError(e, ctx);
|
|
@@ -186,7 +188,7 @@ const router = (ctx, options) => {
|
|
|
186
188
|
return;
|
|
187
189
|
}
|
|
188
190
|
}
|
|
189
|
-
if (e
|
|
191
|
+
if (isAPIError(e)) {
|
|
190
192
|
if (e.status === "INTERNAL_SERVER_ERROR") ctx.logger.error(e.status, e);
|
|
191
193
|
log?.error(e.message);
|
|
192
194
|
} else ctx.logger?.error(e && typeof e === "object" && "name" in e ? e.name : "", e);
|
|
@@ -196,5 +198,5 @@ const router = (ctx, options) => {
|
|
|
196
198
|
};
|
|
197
199
|
|
|
198
200
|
//#endregion
|
|
199
|
-
export { APIError, accountInfo, callbackOAuth, changeEmail, changePassword, checkEndpointConflicts, createAuthEndpoint, createAuthMiddleware, createEmailVerificationToken, deleteUser, deleteUserCallback, error, formCsrfMiddleware, freshSessionMiddleware, getAccessToken, getEndpoints, getIp, getOAuthState, getSession, getSessionFromCtx, linkSocialAccount, listSessions, listUserAccounts, ok, optionsMiddleware, originCheck, originCheckMiddleware, refreshToken, requestOnlySessionMiddleware, requestPasswordReset, requestPasswordResetCallback, resetPassword, revokeOtherSessions, revokeSession, revokeSessions, router, sendVerificationEmail, sendVerificationEmailFn, sensitiveSessionMiddleware, sessionMiddleware, setPassword, signInEmail, signInSocial, signOut, signUpEmail, unlinkAccount, updateUser, verifyEmail };
|
|
201
|
+
export { APIError, accountInfo, callbackOAuth, changeEmail, changePassword, checkEndpointConflicts, createAuthEndpoint, createAuthMiddleware, createEmailVerificationToken, deleteUser, deleteUserCallback, error, formCsrfMiddleware, freshSessionMiddleware, getAccessToken, getEndpoints, getIp, getOAuthState, getSession, getSessionFromCtx, isAPIError, linkSocialAccount, listSessions, listUserAccounts, ok, optionsMiddleware, originCheck, originCheckMiddleware, refreshToken, requestOnlySessionMiddleware, requestPasswordReset, requestPasswordResetCallback, resetPassword, revokeOtherSessions, revokeSession, revokeSessions, router, sendVerificationEmail, sendVerificationEmailFn, sensitiveSessionMiddleware, sessionMiddleware, setPassword, signInEmail, signInSocial, signOut, signUpEmail, unlinkAccount, updateUser, verifyEmail };
|
|
200
202
|
//# sourceMappingURL=index.mjs.map
|
package/dist/api/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["methods: string[]","conflicts: {\n\t\tpath: string;\n\t\tplugins: string[];\n\t\tconflictingMethods: string[];\n\t}[]","conflictingMethods: string[]","APIError"],"sources":["../../src/api/index.ts"],"sourcesContent":["import type {\n\tAuthContext,\n\tAwaitable,\n\tBetterAuthOptions,\n\tBetterAuthPlugin,\n} from \"@better-auth/core\";\nimport type { InternalLogger } from \"@better-auth/core/env\";\nimport { logger } from \"@better-auth/core/env\";\nimport type { Endpoint, Middleware } from \"better-call\";\nimport { APIError, createRouter } from \"better-call\";\nimport type { UnionToIntersection } from \"../types/helper\";\nimport { originCheckMiddleware } from \"./middlewares\";\nimport { onRequestRateLimit } from \"./rate-limiter\";\nimport {\n\taccountInfo,\n\tcallbackOAuth,\n\tchangeEmail,\n\tchangePassword,\n\tdeleteUser,\n\tdeleteUserCallback,\n\terror,\n\tgetAccessToken,\n\tgetSession,\n\tlinkSocialAccount,\n\tlistSessions,\n\tlistUserAccounts,\n\tok,\n\trefreshToken,\n\trequestPasswordReset,\n\trequestPasswordResetCallback,\n\tresetPassword,\n\trevokeOtherSessions,\n\trevokeSession,\n\trevokeSessions,\n\tsendVerificationEmail,\n\tsetPassword,\n\tsignInEmail,\n\tsignInSocial,\n\tsignOut,\n\tsignUpEmail,\n\tunlinkAccount,\n\tupdateUser,\n\tverifyEmail,\n} from \"./routes\";\nimport { toAuthEndpoints } from \"./to-auth-endpoints\";\n\nexport function checkEndpointConflicts(\n\toptions: BetterAuthOptions,\n\tlogger: InternalLogger,\n) {\n\tconst endpointRegistry = new Map<\n\t\tstring,\n\t\t{ pluginId: string; endpointKey: string; methods: string[] }[]\n\t>();\n\n\toptions.plugins?.forEach((plugin) => {\n\t\tif (plugin.endpoints) {\n\t\t\tfor (const [key, endpoint] of Object.entries(plugin.endpoints)) {\n\t\t\t\tif (\n\t\t\t\t\tendpoint &&\n\t\t\t\t\t\"path\" in endpoint &&\n\t\t\t\t\ttypeof endpoint.path === \"string\"\n\t\t\t\t) {\n\t\t\t\t\tconst path = endpoint.path;\n\t\t\t\t\tlet methods: string[] = [];\n\t\t\t\t\tif (endpoint.options && \"method\" in endpoint.options) {\n\t\t\t\t\t\tif (Array.isArray(endpoint.options.method)) {\n\t\t\t\t\t\t\tmethods = endpoint.options.method;\n\t\t\t\t\t\t} else if (typeof endpoint.options.method === \"string\") {\n\t\t\t\t\t\t\tmethods = [endpoint.options.method];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (methods.length === 0) {\n\t\t\t\t\t\tmethods = [\"*\"];\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!endpointRegistry.has(path)) {\n\t\t\t\t\t\tendpointRegistry.set(path, []);\n\t\t\t\t\t}\n\t\t\t\t\tendpointRegistry.get(path)!.push({\n\t\t\t\t\t\tpluginId: plugin.id,\n\t\t\t\t\t\tendpointKey: key,\n\t\t\t\t\t\tmethods,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n\n\tconst conflicts: {\n\t\tpath: string;\n\t\tplugins: string[];\n\t\tconflictingMethods: string[];\n\t}[] = [];\n\tfor (const [path, entries] of endpointRegistry.entries()) {\n\t\tif (entries.length > 1) {\n\t\t\tconst methodMap = new Map<string, string[]>();\n\t\t\tlet hasConflict = false;\n\n\t\t\tfor (const entry of entries) {\n\t\t\t\tfor (const method of entry.methods) {\n\t\t\t\t\tif (!methodMap.has(method)) {\n\t\t\t\t\t\tmethodMap.set(method, []);\n\t\t\t\t\t}\n\t\t\t\t\tmethodMap.get(method)!.push(entry.pluginId);\n\n\t\t\t\t\tif (methodMap.get(method)!.length > 1) {\n\t\t\t\t\t\thasConflict = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (method === \"*\" && entries.length > 1) {\n\t\t\t\t\t\thasConflict = true;\n\t\t\t\t\t} else if (method !== \"*\" && methodMap.has(\"*\")) {\n\t\t\t\t\t\thasConflict = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (hasConflict) {\n\t\t\t\tconst uniquePlugins = [...new Set(entries.map((e) => e.pluginId))];\n\t\t\t\tconst conflictingMethods: string[] = [];\n\n\t\t\t\tfor (const [method, plugins] of methodMap.entries()) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tplugins.length > 1 ||\n\t\t\t\t\t\t(method === \"*\" && entries.length > 1) ||\n\t\t\t\t\t\t(method !== \"*\" && methodMap.has(\"*\"))\n\t\t\t\t\t) {\n\t\t\t\t\t\tconflictingMethods.push(method);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconflicts.push({\n\t\t\t\t\tpath,\n\t\t\t\t\tplugins: uniquePlugins,\n\t\t\t\t\tconflictingMethods,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tif (conflicts.length > 0) {\n\t\tconst conflictMessages = conflicts\n\t\t\t.map(\n\t\t\t\t(conflict) =>\n\t\t\t\t\t` - \"${conflict.path}\" [${conflict.conflictingMethods.join(\", \")}] used by plugins: ${conflict.plugins.join(\", \")}`,\n\t\t\t)\n\t\t\t.join(\"\\n\");\n\t\tlogger.error(\n\t\t\t`Endpoint path conflicts detected! Multiple plugins are trying to use the same endpoint paths with conflicting HTTP methods:\n${conflictMessages}\n\nTo resolve this, you can:\n\t1. Use only one of the conflicting plugins\n\t2. Configure the plugins to use different paths (if supported)\n\t3. Ensure plugins use different HTTP methods for the same path\n`,\n\t\t);\n\t}\n}\n\nexport function getEndpoints<Option extends BetterAuthOptions>(\n\tctx: Awaitable<AuthContext>,\n\toptions: Option,\n) {\n\tconst pluginEndpoints =\n\t\toptions.plugins?.reduce<Record<string, Endpoint>>((acc, plugin) => {\n\t\t\treturn {\n\t\t\t\t...acc,\n\t\t\t\t...plugin.endpoints,\n\t\t\t};\n\t\t}, {}) ?? {};\n\n\ttype PluginEndpoint = UnionToIntersection<\n\t\tOption[\"plugins\"] extends Array<infer T>\n\t\t\t? T extends BetterAuthPlugin\n\t\t\t\t? T extends {\n\t\t\t\t\t\tendpoints: infer E;\n\t\t\t\t\t}\n\t\t\t\t\t? E\n\t\t\t\t\t: {}\n\t\t\t\t: {}\n\t\t\t: {}\n\t>;\n\n\tconst middlewares =\n\t\toptions.plugins\n\t\t\t?.map((plugin) =>\n\t\t\t\tplugin.middlewares?.map((m) => {\n\t\t\t\t\tconst middleware = (async (context: any) => {\n\t\t\t\t\t\tconst authContext = await ctx;\n\t\t\t\t\t\treturn m.middleware({\n\t\t\t\t\t\t\t...context,\n\t\t\t\t\t\t\tcontext: {\n\t\t\t\t\t\t\t\t...authContext,\n\t\t\t\t\t\t\t\t...context.context,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}) as Middleware;\n\t\t\t\t\tmiddleware.options = m.middleware.options;\n\t\t\t\t\treturn {\n\t\t\t\t\t\tpath: m.path,\n\t\t\t\t\t\tmiddleware,\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t\t)\n\t\t\t.filter((plugin) => plugin !== undefined)\n\t\t\t.flat() || [];\n\n\tconst baseEndpoints = {\n\t\tsignInSocial: signInSocial<Option>(),\n\t\tcallbackOAuth,\n\t\tgetSession: getSession<Option>(),\n\t\tsignOut,\n\t\tsignUpEmail: signUpEmail<Option>(),\n\t\tsignInEmail: signInEmail<Option>(),\n\t\tresetPassword,\n\t\tverifyEmail,\n\t\tsendVerificationEmail,\n\t\tchangeEmail,\n\t\tchangePassword,\n\t\tsetPassword,\n\t\tupdateUser: updateUser<Option>(),\n\t\tdeleteUser,\n\t\trequestPasswordReset,\n\t\trequestPasswordResetCallback,\n\t\tlistSessions: listSessions<Option>(),\n\t\trevokeSession,\n\t\trevokeSessions,\n\t\trevokeOtherSessions,\n\t\tlinkSocialAccount,\n\t\tlistUserAccounts,\n\t\tdeleteUserCallback,\n\t\tunlinkAccount,\n\t\trefreshToken,\n\t\tgetAccessToken,\n\t\taccountInfo,\n\t};\n\tconst endpoints = {\n\t\t...baseEndpoints,\n\t\t...pluginEndpoints,\n\t\tok,\n\t\terror,\n\t} as const;\n\tconst api = toAuthEndpoints(endpoints, ctx);\n\treturn {\n\t\tapi: api as typeof endpoints & PluginEndpoint,\n\t\tmiddlewares,\n\t};\n}\nexport const router = <Option extends BetterAuthOptions>(\n\tctx: AuthContext,\n\toptions: Option,\n) => {\n\tconst { api, middlewares } = getEndpoints(ctx, options);\n\tconst basePath = new URL(ctx.baseURL).pathname;\n\n\treturn createRouter(api, {\n\t\trouterContext: ctx,\n\t\topenapi: {\n\t\t\tdisabled: true,\n\t\t},\n\t\tbasePath,\n\t\trouterMiddleware: [\n\t\t\t{\n\t\t\t\tpath: \"/**\",\n\t\t\t\tmiddleware: originCheckMiddleware,\n\t\t\t},\n\t\t\t...middlewares,\n\t\t],\n\t\tallowedMediaTypes: [\"application/json\"],\n\t\tasync onRequest(req) {\n\t\t\t//handle disabled paths\n\t\t\tconst disabledPaths = ctx.options.disabledPaths || [];\n\t\t\tconst pathname = new URL(req.url).pathname.replace(/\\/+$/, \"\") || \"/\";\n\n\t\t\tconst normalizedPath =\n\t\t\t\tbasePath === \"/\"\n\t\t\t\t\t? pathname\n\t\t\t\t\t: pathname.startsWith(basePath)\n\t\t\t\t\t\t? pathname.slice(basePath.length).replace(/\\/+$/, \"\") || \"/\"\n\t\t\t\t\t\t: pathname;\n\t\t\tif (disabledPaths.includes(normalizedPath)) {\n\t\t\t\treturn new Response(\"Not Found\", { status: 404 });\n\t\t\t}\n\n\t\t\tlet currentRequest = req;\n\t\t\tfor (const plugin of ctx.options.plugins || []) {\n\t\t\t\tif (plugin.onRequest) {\n\t\t\t\t\tconst response = await plugin.onRequest(currentRequest, ctx);\n\t\t\t\t\tif (response && \"response\" in response) {\n\t\t\t\t\t\treturn response.response;\n\t\t\t\t\t}\n\t\t\t\t\tif (response && \"request\" in response) {\n\t\t\t\t\t\tcurrentRequest = response.request;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst rateLimitResponse = await onRequestRateLimit(currentRequest, ctx);\n\t\t\tif (rateLimitResponse) {\n\t\t\t\treturn rateLimitResponse;\n\t\t\t}\n\n\t\t\treturn currentRequest;\n\t\t},\n\t\tasync onResponse(res) {\n\t\t\tfor (const plugin of ctx.options.plugins || []) {\n\t\t\t\tif (plugin.onResponse) {\n\t\t\t\t\tconst response = await plugin.onResponse(res, ctx);\n\t\t\t\t\tif (response) {\n\t\t\t\t\t\treturn response.response;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn res;\n\t\t},\n\t\tonError(e) {\n\t\t\tif (e instanceof APIError && e.status === \"FOUND\") {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (options.onAPIError?.throw) {\n\t\t\t\tthrow e;\n\t\t\t}\n\t\t\tif (options.onAPIError?.onError) {\n\t\t\t\toptions.onAPIError.onError(e, ctx);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst optLogLevel = options.logger?.level;\n\t\t\tconst log =\n\t\t\t\toptLogLevel === \"error\" ||\n\t\t\t\toptLogLevel === \"warn\" ||\n\t\t\t\toptLogLevel === \"debug\"\n\t\t\t\t\t? logger\n\t\t\t\t\t: undefined;\n\t\t\tif (options.logger?.disabled !== true) {\n\t\t\t\tif (\n\t\t\t\t\te &&\n\t\t\t\t\ttypeof e === \"object\" &&\n\t\t\t\t\t\"message\" in e &&\n\t\t\t\t\ttypeof e.message === \"string\"\n\t\t\t\t) {\n\t\t\t\t\tif (\n\t\t\t\t\t\te.message.includes(\"no column\") ||\n\t\t\t\t\t\te.message.includes(\"column\") ||\n\t\t\t\t\t\te.message.includes(\"relation\") ||\n\t\t\t\t\t\te.message.includes(\"table\") ||\n\t\t\t\t\t\te.message.includes(\"does not exist\")\n\t\t\t\t\t) {\n\t\t\t\t\t\tctx.logger?.error(e.message);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (e instanceof APIError) {\n\t\t\t\t\tif (e.status === \"INTERNAL_SERVER_ERROR\") {\n\t\t\t\t\t\tctx.logger.error(e.status, e);\n\t\t\t\t\t}\n\t\t\t\t\tlog?.error(e.message);\n\t\t\t\t} else {\n\t\t\t\t\tctx.logger?.error(\n\t\t\t\t\t\te && typeof e === \"object\" && \"name\" in e ? (e.name as string) : \"\",\n\t\t\t\t\t\te,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t});\n};\n\nexport {\n\ttype AuthEndpoint,\n\ttype AuthMiddleware,\n\tcreateAuthEndpoint,\n\tcreateAuthMiddleware,\n\toptionsMiddleware,\n} from \"@better-auth/core/api\";\nexport { APIError } from \"better-call\";\nexport { getIp } from \"../utils/get-request-ip\";\nexport * from \"./middlewares\";\nexport * from \"./routes\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["methods: string[]","conflicts: {\n\t\tpath: string;\n\t\tplugins: string[];\n\t\tconflictingMethods: string[];\n\t}[]","conflictingMethods: string[]"],"sources":["../../src/api/index.ts"],"sourcesContent":["import type {\n\tAuthContext,\n\tAwaitable,\n\tBetterAuthOptions,\n\tBetterAuthPlugin,\n} from \"@better-auth/core\";\nimport type { InternalLogger } from \"@better-auth/core/env\";\nimport { logger } from \"@better-auth/core/env\";\nimport type { Endpoint, Middleware } from \"better-call\";\nimport { createRouter } from \"better-call\";\nimport type { UnionToIntersection } from \"../types/helper\";\nimport { isAPIError } from \"../utils/is-api-error\";\nimport { originCheckMiddleware } from \"./middlewares\";\nimport { onRequestRateLimit } from \"./rate-limiter\";\nimport {\n\taccountInfo,\n\tcallbackOAuth,\n\tchangeEmail,\n\tchangePassword,\n\tdeleteUser,\n\tdeleteUserCallback,\n\terror,\n\tgetAccessToken,\n\tgetSession,\n\tlinkSocialAccount,\n\tlistSessions,\n\tlistUserAccounts,\n\tok,\n\trefreshToken,\n\trequestPasswordReset,\n\trequestPasswordResetCallback,\n\tresetPassword,\n\trevokeOtherSessions,\n\trevokeSession,\n\trevokeSessions,\n\tsendVerificationEmail,\n\tsetPassword,\n\tsignInEmail,\n\tsignInSocial,\n\tsignOut,\n\tsignUpEmail,\n\tunlinkAccount,\n\tupdateUser,\n\tverifyEmail,\n} from \"./routes\";\nimport { toAuthEndpoints } from \"./to-auth-endpoints\";\n\nexport function checkEndpointConflicts(\n\toptions: BetterAuthOptions,\n\tlogger: InternalLogger,\n) {\n\tconst endpointRegistry = new Map<\n\t\tstring,\n\t\t{ pluginId: string; endpointKey: string; methods: string[] }[]\n\t>();\n\n\toptions.plugins?.forEach((plugin) => {\n\t\tif (plugin.endpoints) {\n\t\t\tfor (const [key, endpoint] of Object.entries(plugin.endpoints)) {\n\t\t\t\tif (\n\t\t\t\t\tendpoint &&\n\t\t\t\t\t\"path\" in endpoint &&\n\t\t\t\t\ttypeof endpoint.path === \"string\"\n\t\t\t\t) {\n\t\t\t\t\tconst path = endpoint.path;\n\t\t\t\t\tlet methods: string[] = [];\n\t\t\t\t\tif (endpoint.options && \"method\" in endpoint.options) {\n\t\t\t\t\t\tif (Array.isArray(endpoint.options.method)) {\n\t\t\t\t\t\t\tmethods = endpoint.options.method;\n\t\t\t\t\t\t} else if (typeof endpoint.options.method === \"string\") {\n\t\t\t\t\t\t\tmethods = [endpoint.options.method];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (methods.length === 0) {\n\t\t\t\t\t\tmethods = [\"*\"];\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!endpointRegistry.has(path)) {\n\t\t\t\t\t\tendpointRegistry.set(path, []);\n\t\t\t\t\t}\n\t\t\t\t\tendpointRegistry.get(path)!.push({\n\t\t\t\t\t\tpluginId: plugin.id,\n\t\t\t\t\t\tendpointKey: key,\n\t\t\t\t\t\tmethods,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n\n\tconst conflicts: {\n\t\tpath: string;\n\t\tplugins: string[];\n\t\tconflictingMethods: string[];\n\t}[] = [];\n\tfor (const [path, entries] of endpointRegistry.entries()) {\n\t\tif (entries.length > 1) {\n\t\t\tconst methodMap = new Map<string, string[]>();\n\t\t\tlet hasConflict = false;\n\n\t\t\tfor (const entry of entries) {\n\t\t\t\tfor (const method of entry.methods) {\n\t\t\t\t\tif (!methodMap.has(method)) {\n\t\t\t\t\t\tmethodMap.set(method, []);\n\t\t\t\t\t}\n\t\t\t\t\tmethodMap.get(method)!.push(entry.pluginId);\n\n\t\t\t\t\tif (methodMap.get(method)!.length > 1) {\n\t\t\t\t\t\thasConflict = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (method === \"*\" && entries.length > 1) {\n\t\t\t\t\t\thasConflict = true;\n\t\t\t\t\t} else if (method !== \"*\" && methodMap.has(\"*\")) {\n\t\t\t\t\t\thasConflict = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (hasConflict) {\n\t\t\t\tconst uniquePlugins = [...new Set(entries.map((e) => e.pluginId))];\n\t\t\t\tconst conflictingMethods: string[] = [];\n\n\t\t\t\tfor (const [method, plugins] of methodMap.entries()) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tplugins.length > 1 ||\n\t\t\t\t\t\t(method === \"*\" && entries.length > 1) ||\n\t\t\t\t\t\t(method !== \"*\" && methodMap.has(\"*\"))\n\t\t\t\t\t) {\n\t\t\t\t\t\tconflictingMethods.push(method);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconflicts.push({\n\t\t\t\t\tpath,\n\t\t\t\t\tplugins: uniquePlugins,\n\t\t\t\t\tconflictingMethods,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tif (conflicts.length > 0) {\n\t\tconst conflictMessages = conflicts\n\t\t\t.map(\n\t\t\t\t(conflict) =>\n\t\t\t\t\t` - \"${conflict.path}\" [${conflict.conflictingMethods.join(\", \")}] used by plugins: ${conflict.plugins.join(\", \")}`,\n\t\t\t)\n\t\t\t.join(\"\\n\");\n\t\tlogger.error(\n\t\t\t`Endpoint path conflicts detected! Multiple plugins are trying to use the same endpoint paths with conflicting HTTP methods:\n${conflictMessages}\n\nTo resolve this, you can:\n\t1. Use only one of the conflicting plugins\n\t2. Configure the plugins to use different paths (if supported)\n\t3. Ensure plugins use different HTTP methods for the same path\n`,\n\t\t);\n\t}\n}\n\nexport function getEndpoints<Option extends BetterAuthOptions>(\n\tctx: Awaitable<AuthContext>,\n\toptions: Option,\n) {\n\tconst pluginEndpoints =\n\t\toptions.plugins?.reduce<Record<string, Endpoint>>((acc, plugin) => {\n\t\t\treturn {\n\t\t\t\t...acc,\n\t\t\t\t...plugin.endpoints,\n\t\t\t};\n\t\t}, {}) ?? {};\n\n\ttype PluginEndpoint = UnionToIntersection<\n\t\tOption[\"plugins\"] extends Array<infer T>\n\t\t\t? T extends BetterAuthPlugin\n\t\t\t\t? T extends {\n\t\t\t\t\t\tendpoints: infer E;\n\t\t\t\t\t}\n\t\t\t\t\t? E\n\t\t\t\t\t: {}\n\t\t\t\t: {}\n\t\t\t: {}\n\t>;\n\n\tconst middlewares =\n\t\toptions.plugins\n\t\t\t?.map((plugin) =>\n\t\t\t\tplugin.middlewares?.map((m) => {\n\t\t\t\t\tconst middleware = (async (context: any) => {\n\t\t\t\t\t\tconst authContext = await ctx;\n\t\t\t\t\t\treturn m.middleware({\n\t\t\t\t\t\t\t...context,\n\t\t\t\t\t\t\tcontext: {\n\t\t\t\t\t\t\t\t...authContext,\n\t\t\t\t\t\t\t\t...context.context,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}) as Middleware;\n\t\t\t\t\tmiddleware.options = m.middleware.options;\n\t\t\t\t\treturn {\n\t\t\t\t\t\tpath: m.path,\n\t\t\t\t\t\tmiddleware,\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t\t)\n\t\t\t.filter((plugin) => plugin !== undefined)\n\t\t\t.flat() || [];\n\n\tconst baseEndpoints = {\n\t\tsignInSocial: signInSocial<Option>(),\n\t\tcallbackOAuth,\n\t\tgetSession: getSession<Option>(),\n\t\tsignOut,\n\t\tsignUpEmail: signUpEmail<Option>(),\n\t\tsignInEmail: signInEmail<Option>(),\n\t\tresetPassword,\n\t\tverifyEmail,\n\t\tsendVerificationEmail,\n\t\tchangeEmail,\n\t\tchangePassword,\n\t\tsetPassword,\n\t\tupdateUser: updateUser<Option>(),\n\t\tdeleteUser,\n\t\trequestPasswordReset,\n\t\trequestPasswordResetCallback,\n\t\tlistSessions: listSessions<Option>(),\n\t\trevokeSession,\n\t\trevokeSessions,\n\t\trevokeOtherSessions,\n\t\tlinkSocialAccount,\n\t\tlistUserAccounts,\n\t\tdeleteUserCallback,\n\t\tunlinkAccount,\n\t\trefreshToken,\n\t\tgetAccessToken,\n\t\taccountInfo,\n\t};\n\tconst endpoints = {\n\t\t...baseEndpoints,\n\t\t...pluginEndpoints,\n\t\tok,\n\t\terror,\n\t} as const;\n\tconst api = toAuthEndpoints(endpoints, ctx);\n\treturn {\n\t\tapi: api as typeof endpoints & PluginEndpoint,\n\t\tmiddlewares,\n\t};\n}\nexport const router = <Option extends BetterAuthOptions>(\n\tctx: AuthContext,\n\toptions: Option,\n) => {\n\tconst { api, middlewares } = getEndpoints(ctx, options);\n\tconst basePath = new URL(ctx.baseURL).pathname;\n\n\treturn createRouter(api, {\n\t\trouterContext: ctx,\n\t\topenapi: {\n\t\t\tdisabled: true,\n\t\t},\n\t\tbasePath,\n\t\trouterMiddleware: [\n\t\t\t{\n\t\t\t\tpath: \"/**\",\n\t\t\t\tmiddleware: originCheckMiddleware,\n\t\t\t},\n\t\t\t...middlewares,\n\t\t],\n\t\tallowedMediaTypes: [\"application/json\"],\n\t\tasync onRequest(req) {\n\t\t\t//handle disabled paths\n\t\t\tconst disabledPaths = ctx.options.disabledPaths || [];\n\t\t\tconst pathname = new URL(req.url).pathname.replace(/\\/+$/, \"\") || \"/\";\n\n\t\t\tconst normalizedPath =\n\t\t\t\tbasePath === \"/\"\n\t\t\t\t\t? pathname\n\t\t\t\t\t: pathname.startsWith(basePath)\n\t\t\t\t\t\t? pathname.slice(basePath.length).replace(/\\/+$/, \"\") || \"/\"\n\t\t\t\t\t\t: pathname;\n\t\t\tif (disabledPaths.includes(normalizedPath)) {\n\t\t\t\treturn new Response(\"Not Found\", { status: 404 });\n\t\t\t}\n\n\t\t\tlet currentRequest = req;\n\t\t\tfor (const plugin of ctx.options.plugins || []) {\n\t\t\t\tif (plugin.onRequest) {\n\t\t\t\t\tconst response = await plugin.onRequest(currentRequest, ctx);\n\t\t\t\t\tif (response && \"response\" in response) {\n\t\t\t\t\t\treturn response.response;\n\t\t\t\t\t}\n\t\t\t\t\tif (response && \"request\" in response) {\n\t\t\t\t\t\tcurrentRequest = response.request;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst rateLimitResponse = await onRequestRateLimit(currentRequest, ctx);\n\t\t\tif (rateLimitResponse) {\n\t\t\t\treturn rateLimitResponse;\n\t\t\t}\n\n\t\t\treturn currentRequest;\n\t\t},\n\t\tasync onResponse(res) {\n\t\t\tfor (const plugin of ctx.options.plugins || []) {\n\t\t\t\tif (plugin.onResponse) {\n\t\t\t\t\tconst response = await plugin.onResponse(res, ctx);\n\t\t\t\t\tif (response) {\n\t\t\t\t\t\treturn response.response;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn res;\n\t\t},\n\t\tonError(e) {\n\t\t\tif (isAPIError(e) && e.status === \"FOUND\") {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (options.onAPIError?.throw) {\n\t\t\t\tthrow e;\n\t\t\t}\n\t\t\tif (options.onAPIError?.onError) {\n\t\t\t\toptions.onAPIError.onError(e, ctx);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst optLogLevel = options.logger?.level;\n\t\t\tconst log =\n\t\t\t\toptLogLevel === \"error\" ||\n\t\t\t\toptLogLevel === \"warn\" ||\n\t\t\t\toptLogLevel === \"debug\"\n\t\t\t\t\t? logger\n\t\t\t\t\t: undefined;\n\t\t\tif (options.logger?.disabled !== true) {\n\t\t\t\tif (\n\t\t\t\t\te &&\n\t\t\t\t\ttypeof e === \"object\" &&\n\t\t\t\t\t\"message\" in e &&\n\t\t\t\t\ttypeof e.message === \"string\"\n\t\t\t\t) {\n\t\t\t\t\tif (\n\t\t\t\t\t\te.message.includes(\"no column\") ||\n\t\t\t\t\t\te.message.includes(\"column\") ||\n\t\t\t\t\t\te.message.includes(\"relation\") ||\n\t\t\t\t\t\te.message.includes(\"table\") ||\n\t\t\t\t\t\te.message.includes(\"does not exist\")\n\t\t\t\t\t) {\n\t\t\t\t\t\tctx.logger?.error(e.message);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (isAPIError(e)) {\n\t\t\t\t\tif (e.status === \"INTERNAL_SERVER_ERROR\") {\n\t\t\t\t\t\tctx.logger.error(e.status, e);\n\t\t\t\t\t}\n\t\t\t\t\tlog?.error(e.message);\n\t\t\t\t} else {\n\t\t\t\t\tctx.logger?.error(\n\t\t\t\t\t\te && typeof e === \"object\" && \"name\" in e ? (e.name as string) : \"\",\n\t\t\t\t\t\te,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t});\n};\n\nexport {\n\ttype AuthEndpoint,\n\ttype AuthMiddleware,\n\tcreateAuthEndpoint,\n\tcreateAuthMiddleware,\n\toptionsMiddleware,\n} from \"@better-auth/core/api\";\nexport { APIError } from \"@better-auth/core/error\";\nexport { getIp } from \"../utils/get-request-ip\";\nexport { isAPIError } from \"../utils/is-api-error\";\nexport * from \"./middlewares\";\nexport * from \"./routes\";\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GenericEndpointContext } from "@better-auth/core";
|
|
2
|
-
import * as
|
|
2
|
+
import * as better_call800 from "better-call";
|
|
3
3
|
|
|
4
4
|
//#region src/api/middlewares/origin-check.d.ts
|
|
5
5
|
|
|
@@ -7,13 +7,13 @@ import * as better_call974 from "better-call";
|
|
|
7
7
|
* A middleware to validate callbackURL and origin against trustedOrigins.
|
|
8
8
|
* Also handles CSRF protection using Fetch Metadata for first-login scenarios.
|
|
9
9
|
*/
|
|
10
|
-
declare const originCheckMiddleware: (inputContext:
|
|
11
|
-
declare const originCheck: (getValue: (ctx: GenericEndpointContext) => string | string[]) => (inputContext:
|
|
10
|
+
declare const originCheckMiddleware: (inputContext: better_call800.MiddlewareInputContext<better_call800.MiddlewareOptions>) => Promise<void>;
|
|
11
|
+
declare const originCheck: (getValue: (ctx: GenericEndpointContext) => string | string[]) => (inputContext: better_call800.MiddlewareInputContext<better_call800.MiddlewareOptions>) => Promise<void>;
|
|
12
12
|
/**
|
|
13
13
|
* Middleware for CSRF protection using Fetch Metadata headers.
|
|
14
14
|
* This prevents cross-site navigation login attacks while supporting progressive enhancement.
|
|
15
15
|
*/
|
|
16
|
-
declare const formCsrfMiddleware: (inputContext:
|
|
16
|
+
declare const formCsrfMiddleware: (inputContext: better_call800.MiddlewareInputContext<better_call800.MiddlewareOptions>) => Promise<void>;
|
|
17
17
|
//#endregion
|
|
18
18
|
export { formCsrfMiddleware, originCheck, originCheckMiddleware };
|
|
19
19
|
//# sourceMappingURL=origin-check.d.mts.map
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { matchesOriginPattern } from "../../auth/trusted-origins.mjs";
|
|
2
|
-
import { BASE_ERROR_CODES } from "@better-auth/core/error";
|
|
3
|
-
import { APIError } from "better-call";
|
|
2
|
+
import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
|
|
4
3
|
import { createAuthMiddleware } from "@better-auth/core/api";
|
|
5
4
|
|
|
6
5
|
//#region src/api/middlewares/origin-check.ts
|
|
@@ -21,7 +20,12 @@ const originCheckMiddleware = createAuthMiddleware(async (ctx) => {
|
|
|
21
20
|
if (!ctx.context.isTrustedOrigin(url, { allowRelativePaths: label !== "origin" })) {
|
|
22
21
|
ctx.context.logger.error(`Invalid ${label}: ${url}`);
|
|
23
22
|
ctx.context.logger.info(`If it's a valid URL, please add ${url} to trustedOrigins in your auth config\n`, `Current list of trustedOrigins: ${ctx.context.trustedOrigins}`);
|
|
24
|
-
throw
|
|
23
|
+
if (label === "origin") throw APIError.from("FORBIDDEN", BASE_ERROR_CODES.INVALID_ORIGIN);
|
|
24
|
+
if (label === "callbackURL") throw APIError.from("FORBIDDEN", BASE_ERROR_CODES.INVALID_CALLBACK_URL);
|
|
25
|
+
if (label === "redirectURL") throw APIError.from("FORBIDDEN", BASE_ERROR_CODES.INVALID_REDIRECT_URL);
|
|
26
|
+
if (label === "errorCallbackURL") throw APIError.from("FORBIDDEN", BASE_ERROR_CODES.INVALID_ERROR_CALLBACK_URL);
|
|
27
|
+
if (label === "newUserCallbackURL") throw APIError.from("FORBIDDEN", BASE_ERROR_CODES.INVALID_NEW_USER_CALLBACK_URL);
|
|
28
|
+
throw APIError.fromStatus("FORBIDDEN", { message: `Invalid ${label}` });
|
|
25
29
|
}
|
|
26
30
|
};
|
|
27
31
|
callbackURL && validateURL(callbackURL, "callbackURL");
|
|
@@ -37,7 +41,12 @@ const originCheck = (getValue) => createAuthMiddleware(async (ctx) => {
|
|
|
37
41
|
if (!ctx.context.isTrustedOrigin(url, { allowRelativePaths: label !== "origin" })) {
|
|
38
42
|
ctx.context.logger.error(`Invalid ${label}: ${url}`);
|
|
39
43
|
ctx.context.logger.info(`If it's a valid URL, please add ${url} to trustedOrigins in your auth config\n`, `Current list of trustedOrigins: ${ctx.context.trustedOrigins}`);
|
|
40
|
-
throw
|
|
44
|
+
if (label === "origin") throw APIError.from("FORBIDDEN", BASE_ERROR_CODES.INVALID_ORIGIN);
|
|
45
|
+
if (label === "callbackURL") throw APIError.from("FORBIDDEN", BASE_ERROR_CODES.INVALID_CALLBACK_URL);
|
|
46
|
+
if (label === "redirectURL") throw APIError.from("FORBIDDEN", BASE_ERROR_CODES.INVALID_REDIRECT_URL);
|
|
47
|
+
if (label === "errorCallbackURL") throw APIError.from("FORBIDDEN", BASE_ERROR_CODES.INVALID_ERROR_CALLBACK_URL);
|
|
48
|
+
if (label === "newUserCallbackURL") throw APIError.from("FORBIDDEN", BASE_ERROR_CODES.INVALID_NEW_USER_CALLBACK_URL);
|
|
49
|
+
throw APIError.fromStatus("FORBIDDEN", { message: `Invalid ${label}` });
|
|
41
50
|
}
|
|
42
51
|
};
|
|
43
52
|
const callbacks = Array.isArray(callbackURL) ? callbackURL : [callbackURL];
|
|
@@ -89,7 +98,7 @@ async function validateFormCsrf(ctx) {
|
|
|
89
98
|
secFetchMode: mode,
|
|
90
99
|
secFetchDest: dest
|
|
91
100
|
});
|
|
92
|
-
throw
|
|
101
|
+
throw APIError.from("FORBIDDEN", BASE_ERROR_CODES.CROSS_SITE_NAVIGATION_LOGIN_BLOCKED);
|
|
93
102
|
}
|
|
94
103
|
return await validateOrigin(ctx, true);
|
|
95
104
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"origin-check.mjs","names":["trustedOrigins: string[]"],"sources":["../../../src/api/middlewares/origin-check.ts"],"sourcesContent":["import type { GenericEndpointContext } from \"@better-auth/core\";\nimport { createAuthMiddleware } from \"@better-auth/core/api\";\nimport { BASE_ERROR_CODES } from \"@better-auth/core/error\";\nimport { APIError } from \"better-call\";\nimport { matchesOriginPattern } from \"../../auth/trusted-origins\";\n\n/**\n * A middleware to validate callbackURL and origin against trustedOrigins.\n * Also handles CSRF protection using Fetch Metadata for first-login scenarios.\n */\nexport const originCheckMiddleware = createAuthMiddleware(async (ctx) => {\n\t// Skip origin check for GET, OPTIONS, HEAD requests - we don't mutate state here.\n\tif (\n\t\tctx.request?.method === \"GET\" ||\n\t\tctx.request?.method === \"OPTIONS\" ||\n\t\tctx.request?.method === \"HEAD\" ||\n\t\t!ctx.request\n\t) {\n\t\treturn;\n\t}\n\tawait validateOrigin(ctx);\n\n\tconst { body, query } = ctx;\n\tconst callbackURL = body?.callbackURL || query?.callbackURL;\n\tconst redirectURL = body?.redirectTo;\n\tconst errorCallbackURL = body?.errorCallbackURL;\n\tconst newUserCallbackURL = body?.newUserCallbackURL;\n\n\tconst validateURL = (url: string | undefined, label: string) => {\n\t\tif (!url) {\n\t\t\treturn;\n\t\t}\n\t\tconst isTrustedOrigin = ctx.context.isTrustedOrigin(url, {\n\t\t\tallowRelativePaths: label !== \"origin\",\n\t\t});\n\n\t\tif (!isTrustedOrigin) {\n\t\t\tctx.context.logger.error(`Invalid ${label}: ${url}`);\n\t\t\tctx.context.logger.info(\n\t\t\t\t`If it's a valid URL, please add ${url} to trustedOrigins in your auth config\\n`,\n\t\t\t\t`Current list of trustedOrigins: ${ctx.context.trustedOrigins}`,\n\t\t\t);\n\t\t\tthrow new APIError(\"FORBIDDEN\", { message: `Invalid ${label}` });\n\t\t}\n\t};\n\n\tcallbackURL && validateURL(callbackURL, \"callbackURL\");\n\tredirectURL && validateURL(redirectURL, \"redirectURL\");\n\terrorCallbackURL && validateURL(errorCallbackURL, \"errorCallbackURL\");\n\tnewUserCallbackURL && validateURL(newUserCallbackURL, \"newUserCallbackURL\");\n});\n\nexport const originCheck = (\n\tgetValue: (ctx: GenericEndpointContext) => string | string[],\n) =>\n\tcreateAuthMiddleware(async (ctx) => {\n\t\tif (!ctx.request) {\n\t\t\treturn;\n\t\t}\n\t\tconst callbackURL = getValue(ctx);\n\t\tconst validateURL = (url: string | undefined, label: string) => {\n\t\t\tif (!url) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst isTrustedOrigin = ctx.context.isTrustedOrigin(url, {\n\t\t\t\tallowRelativePaths: label !== \"origin\",\n\t\t\t});\n\n\t\t\tif (!isTrustedOrigin) {\n\t\t\t\tctx.context.logger.error(`Invalid ${label}: ${url}`);\n\t\t\t\tctx.context.logger.info(\n\t\t\t\t\t`If it's a valid URL, please add ${url} to trustedOrigins in your auth config\\n`,\n\t\t\t\t\t`Current list of trustedOrigins: ${ctx.context.trustedOrigins}`,\n\t\t\t\t);\n\t\t\t\tthrow new APIError(\"FORBIDDEN\", { message: `Invalid ${label}` });\n\t\t\t}\n\t\t};\n\t\tconst callbacks = Array.isArray(callbackURL) ? callbackURL : [callbackURL];\n\t\tfor (const url of callbacks) {\n\t\t\tvalidateURL(url, \"callbackURL\");\n\t\t}\n\t});\n\n/**\n * Validates origin header against trusted origins.\n * @param ctx - The endpoint context\n * @param forceValidate - If true, always validate origin regardless of cookies/skip flags\n */\nasync function validateOrigin(\n\tctx: GenericEndpointContext,\n\tforceValidate = false,\n): Promise<void> {\n\tconst headers = ctx.request?.headers;\n\tif (!headers || !ctx.request) {\n\t\treturn;\n\t}\n\tconst originHeader = headers.get(\"origin\") || headers.get(\"referer\") || \"\";\n\tconst useCookies = headers.has(\"cookie\");\n\n\tconst shouldValidate =\n\t\tforceValidate ||\n\t\t(useCookies && !ctx.context.skipCSRFCheck && !ctx.context.skipOriginCheck);\n\n\tif (!shouldValidate) {\n\t\treturn;\n\t}\n\n\tif (!originHeader || originHeader === \"null\") {\n\t\tthrow new APIError(\"FORBIDDEN\", { message: \"Missing or null Origin\" });\n\t}\n\n\tconst trustedOrigins: string[] = Array.isArray(\n\t\tctx.context.options.trustedOrigins,\n\t)\n\t\t? ctx.context.trustedOrigins\n\t\t: [\n\t\t\t\t...ctx.context.trustedOrigins,\n\t\t\t\t...((await ctx.context.options.trustedOrigins?.(ctx.request)) || []),\n\t\t\t];\n\n\tconst isTrustedOrigin = trustedOrigins.some((origin) =>\n\t\tmatchesOriginPattern(originHeader, origin),\n\t);\n\tif (!isTrustedOrigin) {\n\t\tctx.context.logger.error(`Invalid origin: ${originHeader}`);\n\t\tctx.context.logger.info(\n\t\t\t`If it's a valid URL, please add ${originHeader} to trustedOrigins in your auth config\\n`,\n\t\t\t`Current list of trustedOrigins: ${trustedOrigins}`,\n\t\t);\n\t\tthrow new APIError(\"FORBIDDEN\", { message: \"Invalid origin\" });\n\t}\n}\n\n/**\n * Middleware for CSRF protection using Fetch Metadata headers.\n * This prevents cross-site navigation login attacks while supporting progressive enhancement.\n */\nexport const formCsrfMiddleware = createAuthMiddleware(async (ctx) => {\n\tconst request = ctx.request;\n\tif (!request) {\n\t\treturn;\n\t}\n\n\tawait validateFormCsrf(ctx);\n});\n\n/**\n * Validates CSRF protection for first-login scenarios using Fetch Metadata headers.\n * This prevents cross-site form submission attacks while supporting progressive enhancement.\n */\nasync function validateFormCsrf(ctx: GenericEndpointContext): Promise<void> {\n\tconst req = ctx.request;\n\tif (!req) {\n\t\treturn;\n\t}\n\n\tconst headers = req.headers;\n\tconst hasAnyCookies = headers.has(\"cookie\");\n\n\tif (hasAnyCookies) {\n\t\treturn await validateOrigin(ctx);\n\t}\n\n\tconst site = headers.get(\"Sec-Fetch-Site\");\n\tconst mode = headers.get(\"Sec-Fetch-Mode\");\n\tconst dest = headers.get(\"Sec-Fetch-Dest\");\n\n\tconst hasMetadata = Boolean(\n\t\t(site && site.trim()) || (mode && mode.trim()) || (dest && dest.trim()),\n\t);\n\n\tif (hasMetadata) {\n\t\t// Block cross-site navigation requests (classic CSRF attack pattern)\n\t\tif (site === \"cross-site\" && mode === \"navigate\") {\n\t\t\tctx.context.logger.error(\n\t\t\t\t\"Blocked cross-site navigation login attempt (CSRF protection)\",\n\t\t\t\t{\n\t\t\t\t\tsecFetchSite: site,\n\t\t\t\t\tsecFetchMode: mode,\n\t\t\t\t\tsecFetchDest: dest,\n\t\t\t\t},\n\t\t\t);\n\t\t\tthrow new APIError(\"FORBIDDEN\", {\n\t\t\t\tmessage: BASE_ERROR_CODES.CROSS_SITE_NAVIGATION_LOGIN_BLOCKED,\n\t\t\t});\n\t\t}\n\n\t\treturn await validateOrigin(ctx, true);\n\t}\n\n\t// No cookies, no Fetch Metadata → fallback to old behavior (no validation)\n\treturn;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"origin-check.mjs","names":["trustedOrigins: string[]"],"sources":["../../../src/api/middlewares/origin-check.ts"],"sourcesContent":["import type { GenericEndpointContext } from \"@better-auth/core\";\nimport { createAuthMiddleware } from \"@better-auth/core/api\";\nimport { APIError, BASE_ERROR_CODES } from \"@better-auth/core/error\";\nimport { matchesOriginPattern } from \"../../auth/trusted-origins\";\n\n/**\n * A middleware to validate callbackURL and origin against trustedOrigins.\n * Also handles CSRF protection using Fetch Metadata for first-login scenarios.\n */\nexport const originCheckMiddleware = createAuthMiddleware(async (ctx) => {\n\t// Skip origin check for GET, OPTIONS, HEAD requests - we don't mutate state here.\n\tif (\n\t\tctx.request?.method === \"GET\" ||\n\t\tctx.request?.method === \"OPTIONS\" ||\n\t\tctx.request?.method === \"HEAD\" ||\n\t\t!ctx.request\n\t) {\n\t\treturn;\n\t}\n\tawait validateOrigin(ctx);\n\n\tconst { body, query } = ctx;\n\tconst callbackURL = body?.callbackURL || query?.callbackURL;\n\tconst redirectURL = body?.redirectTo;\n\tconst errorCallbackURL = body?.errorCallbackURL;\n\tconst newUserCallbackURL = body?.newUserCallbackURL;\n\n\tconst validateURL = (\n\t\turl: string | undefined,\n\t\tlabel:\n\t\t\t| \"origin\"\n\t\t\t| \"callbackURL\"\n\t\t\t| \"redirectURL\"\n\t\t\t| \"errorCallbackURL\"\n\t\t\t| \"newUserCallbackURL\",\n\t) => {\n\t\tif (!url) {\n\t\t\treturn;\n\t\t}\n\t\tconst isTrustedOrigin = ctx.context.isTrustedOrigin(url, {\n\t\t\tallowRelativePaths: label !== \"origin\",\n\t\t});\n\n\t\tif (!isTrustedOrigin) {\n\t\t\tctx.context.logger.error(`Invalid ${label}: ${url}`);\n\t\t\tctx.context.logger.info(\n\t\t\t\t`If it's a valid URL, please add ${url} to trustedOrigins in your auth config\\n`,\n\t\t\t\t`Current list of trustedOrigins: ${ctx.context.trustedOrigins}`,\n\t\t\t);\n\t\t\tif (label === \"origin\") {\n\t\t\t\tthrow APIError.from(\"FORBIDDEN\", BASE_ERROR_CODES.INVALID_ORIGIN);\n\t\t\t}\n\t\t\tif (label === \"callbackURL\") {\n\t\t\t\tthrow APIError.from(\"FORBIDDEN\", BASE_ERROR_CODES.INVALID_CALLBACK_URL);\n\t\t\t}\n\t\t\tif (label === \"redirectURL\") {\n\t\t\t\tthrow APIError.from(\"FORBIDDEN\", BASE_ERROR_CODES.INVALID_REDIRECT_URL);\n\t\t\t}\n\t\t\tif (label === \"errorCallbackURL\") {\n\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\"FORBIDDEN\",\n\t\t\t\t\tBASE_ERROR_CODES.INVALID_ERROR_CALLBACK_URL,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (label === \"newUserCallbackURL\") {\n\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\"FORBIDDEN\",\n\t\t\t\t\tBASE_ERROR_CODES.INVALID_NEW_USER_CALLBACK_URL,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow APIError.fromStatus(\"FORBIDDEN\", {\n\t\t\t\tmessage: `Invalid ${label}`,\n\t\t\t});\n\t\t}\n\t};\n\n\tcallbackURL && validateURL(callbackURL, \"callbackURL\");\n\tredirectURL && validateURL(redirectURL, \"redirectURL\");\n\terrorCallbackURL && validateURL(errorCallbackURL, \"errorCallbackURL\");\n\tnewUserCallbackURL && validateURL(newUserCallbackURL, \"newUserCallbackURL\");\n});\n\nexport const originCheck = (\n\tgetValue: (ctx: GenericEndpointContext) => string | string[],\n) =>\n\tcreateAuthMiddleware(async (ctx) => {\n\t\tif (!ctx.request) {\n\t\t\treturn;\n\t\t}\n\t\tconst callbackURL = getValue(ctx);\n\t\tconst validateURL = (url: string | undefined, label: string) => {\n\t\t\tif (!url) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst isTrustedOrigin = ctx.context.isTrustedOrigin(url, {\n\t\t\t\tallowRelativePaths: label !== \"origin\",\n\t\t\t});\n\n\t\t\tif (!isTrustedOrigin) {\n\t\t\t\tctx.context.logger.error(`Invalid ${label}: ${url}`);\n\t\t\t\tctx.context.logger.info(\n\t\t\t\t\t`If it's a valid URL, please add ${url} to trustedOrigins in your auth config\\n`,\n\t\t\t\t\t`Current list of trustedOrigins: ${ctx.context.trustedOrigins}`,\n\t\t\t\t);\n\t\t\t\tif (label === \"origin\") {\n\t\t\t\t\tthrow APIError.from(\"FORBIDDEN\", BASE_ERROR_CODES.INVALID_ORIGIN);\n\t\t\t\t}\n\t\t\t\tif (label === \"callbackURL\") {\n\t\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\t\"FORBIDDEN\",\n\t\t\t\t\t\tBASE_ERROR_CODES.INVALID_CALLBACK_URL,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (label === \"redirectURL\") {\n\t\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\t\"FORBIDDEN\",\n\t\t\t\t\t\tBASE_ERROR_CODES.INVALID_REDIRECT_URL,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (label === \"errorCallbackURL\") {\n\t\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\t\"FORBIDDEN\",\n\t\t\t\t\t\tBASE_ERROR_CODES.INVALID_ERROR_CALLBACK_URL,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (label === \"newUserCallbackURL\") {\n\t\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\t\"FORBIDDEN\",\n\t\t\t\t\t\tBASE_ERROR_CODES.INVALID_NEW_USER_CALLBACK_URL,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow APIError.fromStatus(\"FORBIDDEN\", {\n\t\t\t\t\tmessage: `Invalid ${label}`,\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\t\tconst callbacks = Array.isArray(callbackURL) ? callbackURL : [callbackURL];\n\t\tfor (const url of callbacks) {\n\t\t\tvalidateURL(url, \"callbackURL\");\n\t\t}\n\t});\n\n/**\n * Validates origin header against trusted origins.\n * @param ctx - The endpoint context\n * @param forceValidate - If true, always validate origin regardless of cookies/skip flags\n */\nasync function validateOrigin(\n\tctx: GenericEndpointContext,\n\tforceValidate = false,\n): Promise<void> {\n\tconst headers = ctx.request?.headers;\n\tif (!headers || !ctx.request) {\n\t\treturn;\n\t}\n\tconst originHeader = headers.get(\"origin\") || headers.get(\"referer\") || \"\";\n\tconst useCookies = headers.has(\"cookie\");\n\n\tconst shouldValidate =\n\t\tforceValidate ||\n\t\t(useCookies && !ctx.context.skipCSRFCheck && !ctx.context.skipOriginCheck);\n\n\tif (!shouldValidate) {\n\t\treturn;\n\t}\n\n\tif (!originHeader || originHeader === \"null\") {\n\t\tthrow new APIError(\"FORBIDDEN\", { message: \"Missing or null Origin\" });\n\t}\n\n\tconst trustedOrigins: string[] = Array.isArray(\n\t\tctx.context.options.trustedOrigins,\n\t)\n\t\t? ctx.context.trustedOrigins\n\t\t: [\n\t\t\t\t...ctx.context.trustedOrigins,\n\t\t\t\t...((await ctx.context.options.trustedOrigins?.(ctx.request)) || []),\n\t\t\t];\n\n\tconst isTrustedOrigin = trustedOrigins.some((origin) =>\n\t\tmatchesOriginPattern(originHeader, origin),\n\t);\n\tif (!isTrustedOrigin) {\n\t\tctx.context.logger.error(`Invalid origin: ${originHeader}`);\n\t\tctx.context.logger.info(\n\t\t\t`If it's a valid URL, please add ${originHeader} to trustedOrigins in your auth config\\n`,\n\t\t\t`Current list of trustedOrigins: ${trustedOrigins}`,\n\t\t);\n\t\tthrow new APIError(\"FORBIDDEN\", { message: \"Invalid origin\" });\n\t}\n}\n\n/**\n * Middleware for CSRF protection using Fetch Metadata headers.\n * This prevents cross-site navigation login attacks while supporting progressive enhancement.\n */\nexport const formCsrfMiddleware = createAuthMiddleware(async (ctx) => {\n\tconst request = ctx.request;\n\tif (!request) {\n\t\treturn;\n\t}\n\n\tawait validateFormCsrf(ctx);\n});\n\n/**\n * Validates CSRF protection for first-login scenarios using Fetch Metadata headers.\n * This prevents cross-site form submission attacks while supporting progressive enhancement.\n */\nasync function validateFormCsrf(ctx: GenericEndpointContext): Promise<void> {\n\tconst req = ctx.request;\n\tif (!req) {\n\t\treturn;\n\t}\n\n\tconst headers = req.headers;\n\tconst hasAnyCookies = headers.has(\"cookie\");\n\n\tif (hasAnyCookies) {\n\t\treturn await validateOrigin(ctx);\n\t}\n\n\tconst site = headers.get(\"Sec-Fetch-Site\");\n\tconst mode = headers.get(\"Sec-Fetch-Mode\");\n\tconst dest = headers.get(\"Sec-Fetch-Dest\");\n\n\tconst hasMetadata = Boolean(\n\t\t(site && site.trim()) || (mode && mode.trim()) || (dest && dest.trim()),\n\t);\n\n\tif (hasMetadata) {\n\t\t// Block cross-site navigation requests (classic CSRF attack pattern)\n\t\tif (site === \"cross-site\" && mode === \"navigate\") {\n\t\t\tctx.context.logger.error(\n\t\t\t\t\"Blocked cross-site navigation login attempt (CSRF protection)\",\n\t\t\t\t{\n\t\t\t\t\tsecFetchSite: site,\n\t\t\t\t\tsecFetchMode: mode,\n\t\t\t\t\tsecFetchDest: dest,\n\t\t\t\t},\n\t\t\t);\n\t\t\tthrow APIError.from(\n\t\t\t\t\"FORBIDDEN\",\n\t\t\t\tBASE_ERROR_CODES.CROSS_SITE_NAVIGATION_LOGIN_BLOCKED,\n\t\t\t);\n\t\t}\n\n\t\treturn await validateOrigin(ctx, true);\n\t}\n\n\t// No cookies, no Fetch Metadata → fallback to old behavior (no validation)\n\treturn;\n}\n"],"mappings":"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"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as _better_auth_core_oauth28 from "@better-auth/core/oauth2";
|
|
2
2
|
import * as z from "zod";
|
|
3
|
-
import * as
|
|
3
|
+
import * as better_call727 from "better-call";
|
|
4
4
|
|
|
5
5
|
//#region src/api/routes/account.d.ts
|
|
6
|
-
declare const listUserAccounts:
|
|
6
|
+
declare const listUserAccounts: better_call727.StrictEndpoint<"/list-accounts", {
|
|
7
7
|
method: "GET";
|
|
8
|
-
use: ((inputContext:
|
|
8
|
+
use: ((inputContext: better_call727.MiddlewareInputContext<better_call727.MiddlewareOptions>) => Promise<{
|
|
9
9
|
session: {
|
|
10
10
|
session: Record<string, any> & {
|
|
11
11
|
id: string;
|
|
@@ -87,7 +87,7 @@ declare const listUserAccounts: better_call950.StrictEndpoint<"/list-accounts",
|
|
|
87
87
|
userId: string;
|
|
88
88
|
scopes: string[];
|
|
89
89
|
}[]>;
|
|
90
|
-
declare const linkSocialAccount:
|
|
90
|
+
declare const linkSocialAccount: better_call727.StrictEndpoint<"/link-social", {
|
|
91
91
|
method: "POST";
|
|
92
92
|
requireHeaders: true;
|
|
93
93
|
body: z.ZodObject<{
|
|
@@ -106,7 +106,7 @@ declare const linkSocialAccount: better_call950.StrictEndpoint<"/link-social", {
|
|
|
106
106
|
disableRedirect: z.ZodOptional<z.ZodBoolean>;
|
|
107
107
|
additionalData: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
108
108
|
}, z.core.$strip>;
|
|
109
|
-
use: ((inputContext:
|
|
109
|
+
use: ((inputContext: better_call727.MiddlewareInputContext<better_call727.MiddlewareOptions>) => Promise<{
|
|
110
110
|
session: {
|
|
111
111
|
session: Record<string, any> & {
|
|
112
112
|
id: string;
|
|
@@ -165,13 +165,13 @@ declare const linkSocialAccount: better_call950.StrictEndpoint<"/link-social", {
|
|
|
165
165
|
url: string;
|
|
166
166
|
redirect: boolean;
|
|
167
167
|
}>;
|
|
168
|
-
declare const unlinkAccount:
|
|
168
|
+
declare const unlinkAccount: better_call727.StrictEndpoint<"/unlink-account", {
|
|
169
169
|
method: "POST";
|
|
170
170
|
body: z.ZodObject<{
|
|
171
171
|
providerId: z.ZodString;
|
|
172
172
|
accountId: z.ZodOptional<z.ZodString>;
|
|
173
173
|
}, z.core.$strip>;
|
|
174
|
-
use: ((inputContext:
|
|
174
|
+
use: ((inputContext: better_call727.MiddlewareInputContext<better_call727.MiddlewareOptions>) => Promise<{
|
|
175
175
|
session: {
|
|
176
176
|
session: Record<string, any> & {
|
|
177
177
|
id: string;
|
|
@@ -219,7 +219,7 @@ declare const unlinkAccount: better_call950.StrictEndpoint<"/unlink-account", {
|
|
|
219
219
|
}, {
|
|
220
220
|
status: boolean;
|
|
221
221
|
}>;
|
|
222
|
-
declare const getAccessToken:
|
|
222
|
+
declare const getAccessToken: better_call727.StrictEndpoint<"/get-access-token", {
|
|
223
223
|
method: "POST";
|
|
224
224
|
body: z.ZodObject<{
|
|
225
225
|
providerId: z.ZodString;
|
|
@@ -274,7 +274,7 @@ declare const getAccessToken: better_call950.StrictEndpoint<"/get-access-token",
|
|
|
274
274
|
scopes: string[];
|
|
275
275
|
idToken: string | undefined;
|
|
276
276
|
}>;
|
|
277
|
-
declare const refreshToken:
|
|
277
|
+
declare const refreshToken: better_call727.StrictEndpoint<"/refresh-token", {
|
|
278
278
|
method: "POST";
|
|
279
279
|
body: z.ZodObject<{
|
|
280
280
|
providerId: z.ZodString;
|
|
@@ -333,9 +333,9 @@ declare const refreshToken: better_call950.StrictEndpoint<"/refresh-token", {
|
|
|
333
333
|
providerId: string;
|
|
334
334
|
accountId: string;
|
|
335
335
|
}>;
|
|
336
|
-
declare const accountInfo:
|
|
336
|
+
declare const accountInfo: better_call727.StrictEndpoint<"/account-info", {
|
|
337
337
|
method: "GET";
|
|
338
|
-
use: ((inputContext:
|
|
338
|
+
use: ((inputContext: better_call727.MiddlewareInputContext<better_call727.MiddlewareOptions>) => Promise<{
|
|
339
339
|
session: {
|
|
340
340
|
session: Record<string, any> & {
|
|
341
341
|
id: string;
|
|
@@ -2,9 +2,8 @@ import { getAccountCookie, setAccountCookie } from "../../cookies/session-store.
|
|
|
2
2
|
import { generateState } from "../../oauth2/state.mjs";
|
|
3
3
|
import { decryptOAuthToken, setTokenUtil } from "../../oauth2/utils.mjs";
|
|
4
4
|
import { freshSessionMiddleware, getSessionFromCtx, sessionMiddleware } from "./session.mjs";
|
|
5
|
-
import { BASE_ERROR_CODES } from "@better-auth/core/error";
|
|
5
|
+
import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
|
|
6
6
|
import * as z from "zod";
|
|
7
|
-
import { APIError } from "better-call";
|
|
8
7
|
import { SocialProviderListEnum } from "@better-auth/core/social-providers";
|
|
9
8
|
import { createAuthEndpoint } from "@better-auth/core/api";
|
|
10
9
|
|
|
@@ -112,17 +111,17 @@ const linkSocialAccount = createAuthEndpoint("/link-social", {
|
|
|
112
111
|
const provider = c.context.socialProviders.find((p) => p.id === c.body.provider);
|
|
113
112
|
if (!provider) {
|
|
114
113
|
c.context.logger.error("Provider not found. Make sure to add the provider in your auth config", { provider: c.body.provider });
|
|
115
|
-
throw
|
|
114
|
+
throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.PROVIDER_NOT_FOUND);
|
|
116
115
|
}
|
|
117
116
|
if (c.body.idToken) {
|
|
118
117
|
if (!provider.verifyIdToken) {
|
|
119
118
|
c.context.logger.error("Provider does not support id token verification", { provider: c.body.provider });
|
|
120
|
-
throw
|
|
119
|
+
throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.ID_TOKEN_NOT_SUPPORTED);
|
|
121
120
|
}
|
|
122
121
|
const { token, nonce } = c.body.idToken;
|
|
123
122
|
if (!await provider.verifyIdToken(token, nonce)) {
|
|
124
123
|
c.context.logger.error("Invalid id token", { provider: c.body.provider });
|
|
125
|
-
throw
|
|
124
|
+
throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.INVALID_TOKEN);
|
|
126
125
|
}
|
|
127
126
|
const linkingUserInfo = await provider.getUserInfo({
|
|
128
127
|
idToken: token,
|
|
@@ -131,20 +130,26 @@ const linkSocialAccount = createAuthEndpoint("/link-social", {
|
|
|
131
130
|
});
|
|
132
131
|
if (!linkingUserInfo || !linkingUserInfo?.user) {
|
|
133
132
|
c.context.logger.error("Failed to get user info", { provider: c.body.provider });
|
|
134
|
-
throw
|
|
133
|
+
throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.FAILED_TO_GET_USER_INFO);
|
|
135
134
|
}
|
|
136
135
|
const linkingUserId = String(linkingUserInfo.user.id);
|
|
137
136
|
if (!linkingUserInfo.user.email) {
|
|
138
137
|
c.context.logger.error("User email not found", { provider: c.body.provider });
|
|
139
|
-
throw
|
|
138
|
+
throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.USER_EMAIL_NOT_FOUND);
|
|
140
139
|
}
|
|
141
140
|
if ((await c.context.internalAdapter.findAccounts(session.user.id)).find((a) => a.providerId === provider.id && a.accountId === linkingUserId)) return c.json({
|
|
142
141
|
url: "",
|
|
143
142
|
status: true,
|
|
144
143
|
redirect: false
|
|
145
144
|
});
|
|
146
|
-
if (!(c.context.options.account?.accountLinking?.trustedProviders)?.includes(provider.id) && !linkingUserInfo.user.emailVerified || c.context.options.account?.accountLinking?.enabled === false) throw
|
|
147
|
-
|
|
145
|
+
if (!(c.context.options.account?.accountLinking?.trustedProviders)?.includes(provider.id) && !linkingUserInfo.user.emailVerified || c.context.options.account?.accountLinking?.enabled === false) throw APIError.from("UNAUTHORIZED", {
|
|
146
|
+
message: "Account not linked - linking not allowed",
|
|
147
|
+
code: "LINKING_NOT_ALLOWED"
|
|
148
|
+
});
|
|
149
|
+
if (linkingUserInfo.user.email !== session.user.email && c.context.options.account?.accountLinking?.allowDifferentEmails !== true) throw APIError.from("UNAUTHORIZED", {
|
|
150
|
+
message: "Account not linked - different emails not allowed",
|
|
151
|
+
code: "LINKING_DIFFERENT_EMAILS_NOT_ALLOWED"
|
|
152
|
+
});
|
|
148
153
|
try {
|
|
149
154
|
await c.context.internalAdapter.createAccount({
|
|
150
155
|
userId: session.user.id,
|
|
@@ -155,8 +160,11 @@ const linkSocialAccount = createAuthEndpoint("/link-social", {
|
|
|
155
160
|
refreshToken: c.body.idToken.refreshToken,
|
|
156
161
|
scope: c.body.idToken.scopes?.join(",")
|
|
157
162
|
});
|
|
158
|
-
} catch {
|
|
159
|
-
throw
|
|
163
|
+
} catch (_e) {
|
|
164
|
+
throw APIError.from("EXPECTATION_FAILED", {
|
|
165
|
+
message: "Account not linked - unable to create account",
|
|
166
|
+
code: "LINKING_FAILED"
|
|
167
|
+
});
|
|
160
168
|
}
|
|
161
169
|
if (c.context.options.account?.accountLinking?.updateUserInfoOnLink === true) try {
|
|
162
170
|
await c.context.internalAdapter.updateUser(session.user.id, {
|
|
@@ -207,9 +215,9 @@ const unlinkAccount = createAuthEndpoint("/unlink-account", {
|
|
|
207
215
|
}, async (ctx) => {
|
|
208
216
|
const { providerId, accountId } = ctx.body;
|
|
209
217
|
const accounts = await ctx.context.internalAdapter.findAccounts(ctx.context.session.user.id);
|
|
210
|
-
if (accounts.length === 1 && !ctx.context.options.account?.accountLinking?.allowUnlinkingAll) throw
|
|
218
|
+
if (accounts.length === 1 && !ctx.context.options.account?.accountLinking?.allowUnlinkingAll) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.FAILED_TO_UNLINK_LAST_ACCOUNT);
|
|
211
219
|
const accountExist = accounts.find((account) => accountId ? account.accountId === accountId && account.providerId === providerId : account.providerId === providerId);
|
|
212
|
-
if (!accountExist) throw
|
|
220
|
+
if (!accountExist) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.ACCOUNT_NOT_FOUND);
|
|
213
221
|
await ctx.context.internalAdapter.deleteAccount(accountExist.id);
|
|
214
222
|
return ctx.json({ status: true });
|
|
215
223
|
});
|
|
@@ -253,14 +261,17 @@ const getAccessToken = createAuthEndpoint("/get-access-token", {
|
|
|
253
261
|
if (req && !session) throw ctx.error("UNAUTHORIZED");
|
|
254
262
|
let resolvedUserId = session?.user?.id || userId;
|
|
255
263
|
if (!resolvedUserId) throw ctx.error("UNAUTHORIZED");
|
|
256
|
-
if (!ctx.context.socialProviders.find((p) => p.id === providerId)) throw
|
|
264
|
+
if (!ctx.context.socialProviders.find((p) => p.id === providerId)) throw APIError.from("BAD_REQUEST", {
|
|
265
|
+
message: `Provider ${providerId} is not supported.`,
|
|
266
|
+
code: "PROVIDER_NOT_SUPPORTED"
|
|
267
|
+
});
|
|
257
268
|
const accountData = await getAccountCookie(ctx);
|
|
258
269
|
let account = void 0;
|
|
259
270
|
if (accountData && providerId === accountData.providerId && (!accountId || accountData.id === accountId)) account = accountData;
|
|
260
271
|
else account = (await ctx.context.internalAdapter.findAccounts(resolvedUserId)).find((acc) => accountId ? acc.id === accountId && acc.providerId === providerId : acc.providerId === providerId);
|
|
261
|
-
if (!account) throw
|
|
272
|
+
if (!account) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.ACCOUNT_NOT_FOUND);
|
|
262
273
|
const provider = ctx.context.socialProviders.find((p) => p.id === providerId);
|
|
263
|
-
if (!provider) throw
|
|
274
|
+
if (!provider) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PROVIDER_NOT_FOUND);
|
|
264
275
|
try {
|
|
265
276
|
let newTokens = null;
|
|
266
277
|
const accessTokenExpired = account.accessTokenExpiresAt && new Date(account.accessTokenExpiresAt).getTime() - Date.now() < 5e3;
|
|
@@ -287,10 +298,10 @@ const getAccessToken = createAuthEndpoint("/get-access-token", {
|
|
|
287
298
|
idToken: newTokens?.idToken ?? account.idToken ?? void 0
|
|
288
299
|
};
|
|
289
300
|
return ctx.json(tokens);
|
|
290
|
-
} catch (
|
|
291
|
-
throw
|
|
301
|
+
} catch (_error) {
|
|
302
|
+
throw APIError.from("BAD_REQUEST", {
|
|
292
303
|
message: "Failed to get a valid access token",
|
|
293
|
-
|
|
304
|
+
code: "FAILED_TO_GET_ACCESS_TOKEN"
|
|
294
305
|
});
|
|
295
306
|
}
|
|
296
307
|
});
|
|
@@ -333,19 +344,31 @@ const refreshToken = createAuthEndpoint("/refresh-token", {
|
|
|
333
344
|
const session = await getSessionFromCtx(ctx);
|
|
334
345
|
if (req && !session) throw ctx.error("UNAUTHORIZED");
|
|
335
346
|
let resolvedUserId = session?.user?.id || userId;
|
|
336
|
-
if (!resolvedUserId) throw
|
|
347
|
+
if (!resolvedUserId) throw APIError.from("BAD_REQUEST", {
|
|
348
|
+
message: `Either userId or session is required`,
|
|
349
|
+
code: "USER_ID_OR_SESSION_REQUIRED"
|
|
350
|
+
});
|
|
337
351
|
const provider = ctx.context.socialProviders.find((p) => p.id === providerId);
|
|
338
|
-
if (!provider) throw
|
|
339
|
-
|
|
352
|
+
if (!provider) throw APIError.from("BAD_REQUEST", {
|
|
353
|
+
message: `Provider ${providerId} not found.`,
|
|
354
|
+
code: "PROVIDER_NOT_FOUND"
|
|
355
|
+
});
|
|
356
|
+
if (!provider.refreshAccessToken) throw APIError.from("BAD_REQUEST", {
|
|
357
|
+
message: `Provider ${providerId} does not support token refreshing.`,
|
|
358
|
+
code: "TOKEN_REFRESH_NOT_SUPPORTED"
|
|
359
|
+
});
|
|
340
360
|
let account = void 0;
|
|
341
361
|
const accountData = await getAccountCookie(ctx);
|
|
342
362
|
if (accountData && (!providerId || providerId === accountData?.providerId)) account = accountData;
|
|
343
363
|
else account = (await ctx.context.internalAdapter.findAccounts(resolvedUserId)).find((acc) => accountId ? acc.id === accountId && acc.providerId === providerId : acc.providerId === providerId);
|
|
344
|
-
if (!account) throw
|
|
364
|
+
if (!account) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.ACCOUNT_NOT_FOUND);
|
|
345
365
|
let refreshToken$1 = void 0;
|
|
346
366
|
if (accountData && providerId === accountData.providerId) refreshToken$1 = accountData.refreshToken ?? void 0;
|
|
347
367
|
else refreshToken$1 = account.refreshToken ?? void 0;
|
|
348
|
-
if (!refreshToken$1) throw
|
|
368
|
+
if (!refreshToken$1) throw APIError.from("BAD_REQUEST", {
|
|
369
|
+
message: "Refresh token not found",
|
|
370
|
+
code: "REFRESH_TOKEN_NOT_FOUND"
|
|
371
|
+
});
|
|
349
372
|
try {
|
|
350
373
|
const decryptedRefreshToken = await decryptOAuthToken(refreshToken$1, ctx.context);
|
|
351
374
|
const tokens = await provider.refreshAccessToken(decryptedRefreshToken);
|
|
@@ -380,10 +403,10 @@ const refreshToken = createAuthEndpoint("/refresh-token", {
|
|
|
380
403
|
providerId: account.providerId,
|
|
381
404
|
accountId: account.accountId
|
|
382
405
|
});
|
|
383
|
-
} catch (
|
|
384
|
-
throw
|
|
406
|
+
} catch (_error) {
|
|
407
|
+
throw APIError.from("BAD_REQUEST", {
|
|
385
408
|
message: "Failed to refresh access token",
|
|
386
|
-
|
|
409
|
+
code: "FAILED_TO_REFRESH_ACCESS_TOKEN"
|
|
387
410
|
});
|
|
388
411
|
}
|
|
389
412
|
});
|
|
@@ -433,9 +456,12 @@ const accountInfo = createAuthEndpoint("/account-info", {
|
|
|
433
456
|
const accountData = await ctx.context.internalAdapter.findAccount(providedAccountId);
|
|
434
457
|
if (accountData) account = accountData;
|
|
435
458
|
}
|
|
436
|
-
if (!account || account.userId !== ctx.context.session.user.id) throw
|
|
459
|
+
if (!account || account.userId !== ctx.context.session.user.id) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.ACCOUNT_NOT_FOUND);
|
|
437
460
|
const provider = ctx.context.socialProviders.find((p) => p.id === account.providerId);
|
|
438
|
-
if (!provider) throw
|
|
461
|
+
if (!provider) throw APIError.from("INTERNAL_SERVER_ERROR", {
|
|
462
|
+
message: `Provider account provider is ${account.providerId} but it is not configured`,
|
|
463
|
+
code: "PROVIDER_NOT_CONFIGURED"
|
|
464
|
+
});
|
|
439
465
|
const tokens = await getAccessToken({
|
|
440
466
|
...ctx,
|
|
441
467
|
method: "POST",
|
|
@@ -446,7 +472,10 @@ const accountInfo = createAuthEndpoint("/account-info", {
|
|
|
446
472
|
returnHeaders: false,
|
|
447
473
|
returnStatus: false
|
|
448
474
|
});
|
|
449
|
-
if (!tokens.accessToken) throw
|
|
475
|
+
if (!tokens.accessToken) throw APIError.from("BAD_REQUEST", {
|
|
476
|
+
message: "Access token not found",
|
|
477
|
+
code: "ACCESS_TOKEN_NOT_FOUND"
|
|
478
|
+
});
|
|
450
479
|
const info = await provider.getUserInfo({
|
|
451
480
|
...tokens,
|
|
452
481
|
accessToken: tokens.accessToken
|