better-auth 1.8.0-beta.6 → 1.8.0-beta.8
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 +67 -1
- package/dist/api/index.mjs +4 -1
- package/dist/api/routes/account.mjs +2 -0
- package/dist/api/routes/id-token-nonce.d.mts +85 -0
- package/dist/api/routes/id-token-nonce.mjs +80 -0
- package/dist/api/routes/index.d.mts +2 -1
- package/dist/api/routes/sign-in.mjs +2 -0
- package/dist/client/proxy.mjs +4 -11
- package/dist/db/get-migration.mjs +9 -11
- package/dist/db/internal-adapter.mjs +37 -28
- package/dist/db/schema.mjs +17 -8
- package/dist/package.mjs +1 -1
- package/dist/plugins/admin/routes.mjs +13 -11
- package/dist/plugins/device-authorization/index.d.mts +1 -0
- package/dist/plugins/device-authorization/routes.mjs +18 -1
- package/dist/plugins/generic-oauth/index.d.mts +1 -4
- package/dist/plugins/generic-oauth/index.mjs +39 -23
- package/dist/plugins/generic-oauth/types.d.mts +10 -2
- package/dist/plugins/one-tap/client.d.mts +4 -0
- package/dist/plugins/one-tap/client.mjs +90 -77
- package/dist/plugins/one-tap/index.d.mts +40 -0
- package/dist/plugins/one-tap/index.mjs +112 -85
- package/dist/plugins/open-api/generator.mjs +2 -1
- package/dist/plugins/organization/adapter.mjs +4 -0
- package/dist/plugins/organization/routes/crud-invites.mjs +1 -1
- package/dist/plugins/siwe/index.mjs +41 -24
- package/dist/plugins/username/index.mjs +3 -2
- package/dist/state.d.mts +2 -5
- package/dist/state.mjs +2 -6
- package/dist/test-utils/http-test-instance.d.mts +8 -0
- package/dist/utils/hide-metadata.d.mts +2 -5
- package/dist/utils/hide-metadata.mjs +1 -4
- package/package.json +8 -8
package/dist/api/index.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { OverrideMerge, Prettify as Prettify$1, UnionToIntersection } from "../types/helper.mjs";
|
|
2
2
|
import { AdditionalSessionFieldsInput, AdditionalUserFieldsInput } from "../types/models.mjs";
|
|
3
3
|
import "../types/index.mjs";
|
|
4
|
+
import { generateGenericState, parseGenericState } from "../state.mjs";
|
|
4
5
|
import "../index.mjs";
|
|
5
6
|
import { isAPIError } from "../utils/is-api-error.mjs";
|
|
6
7
|
import { DispatchContext, dispatchAuthEndpoint } from "./dispatch.mjs";
|
|
@@ -11,6 +12,7 @@ import { accountInfo, getAccessToken, linkSocialAccount, listUserAccounts, refre
|
|
|
11
12
|
import { callbackOAuth } from "./routes/callback.mjs";
|
|
12
13
|
import { createEmailVerificationToken, sendVerificationEmail, sendVerificationEmailFn, verifyEmail } from "./routes/email-verification.mjs";
|
|
13
14
|
import { error } from "./routes/error.mjs";
|
|
15
|
+
import { consumeIssuedIdTokenNonce, consumeNonce, issueIdTokenNonce, issueNonce, issuedNonceResponses } from "./routes/id-token-nonce.mjs";
|
|
14
16
|
import { ok } from "./routes/ok.mjs";
|
|
15
17
|
import { requestPasswordReset, requestPasswordResetCallback, resetPassword, verifyPassword } from "./routes/password.mjs";
|
|
16
18
|
import { freshSessionMiddleware, getAuthoritativeSessionFromCtx, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware } from "./routes/session.mjs";
|
|
@@ -1664,6 +1666,38 @@ export declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awai
|
|
|
1664
1666
|
url: string;
|
|
1665
1667
|
redirect: boolean;
|
|
1666
1668
|
}>;
|
|
1669
|
+
readonly issueIdTokenNonce: import("better-call").StrictEndpoint<"/id-token/nonce", {
|
|
1670
|
+
method: "POST";
|
|
1671
|
+
body: import("zod").ZodObject<{
|
|
1672
|
+
provider: import("zod").ZodType<(string & {}) | "github" | "apple" | "atlassian" | "cloudflare" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "huggingface" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linear" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat", unknown, import("zod/v4/core").$ZodTypeInternals<(string & {}) | "github" | "apple" | "atlassian" | "cloudflare" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "huggingface" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linear" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat", unknown>>;
|
|
1673
|
+
}, import("zod/v4/core").$strip>;
|
|
1674
|
+
metadata: {
|
|
1675
|
+
noStore: boolean;
|
|
1676
|
+
openapi: {
|
|
1677
|
+
description: string;
|
|
1678
|
+
responses: {
|
|
1679
|
+
200: {
|
|
1680
|
+
description: string;
|
|
1681
|
+
content: {
|
|
1682
|
+
"application/json": {
|
|
1683
|
+
schema: {
|
|
1684
|
+
type: "object";
|
|
1685
|
+
properties: {
|
|
1686
|
+
nonce: {
|
|
1687
|
+
type: "string";
|
|
1688
|
+
};
|
|
1689
|
+
};
|
|
1690
|
+
required: string[];
|
|
1691
|
+
};
|
|
1692
|
+
};
|
|
1693
|
+
};
|
|
1694
|
+
};
|
|
1695
|
+
};
|
|
1696
|
+
};
|
|
1697
|
+
};
|
|
1698
|
+
}, {
|
|
1699
|
+
nonce: string;
|
|
1700
|
+
}>;
|
|
1667
1701
|
readonly listUserAccounts: import("better-call").StrictEndpoint<"/list-accounts", {
|
|
1668
1702
|
method: "GET";
|
|
1669
1703
|
use: Middleware<import("better-call").MiddlewareOptions, (inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<{
|
|
@@ -3678,6 +3712,38 @@ export declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext
|
|
|
3678
3712
|
url: string;
|
|
3679
3713
|
redirect: boolean;
|
|
3680
3714
|
}>;
|
|
3715
|
+
readonly issueIdTokenNonce: import("better-call").StrictEndpoint<"/id-token/nonce", {
|
|
3716
|
+
method: "POST";
|
|
3717
|
+
body: import("zod").ZodObject<{
|
|
3718
|
+
provider: import("zod").ZodType<(string & {}) | "github" | "apple" | "atlassian" | "cloudflare" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "huggingface" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linear" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat", unknown, import("zod/v4/core").$ZodTypeInternals<(string & {}) | "github" | "apple" | "atlassian" | "cloudflare" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "huggingface" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linear" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat", unknown>>;
|
|
3719
|
+
}, import("zod/v4/core").$strip>;
|
|
3720
|
+
metadata: {
|
|
3721
|
+
noStore: boolean;
|
|
3722
|
+
openapi: {
|
|
3723
|
+
description: string;
|
|
3724
|
+
responses: {
|
|
3725
|
+
200: {
|
|
3726
|
+
description: string;
|
|
3727
|
+
content: {
|
|
3728
|
+
"application/json": {
|
|
3729
|
+
schema: {
|
|
3730
|
+
type: "object";
|
|
3731
|
+
properties: {
|
|
3732
|
+
nonce: {
|
|
3733
|
+
type: "string";
|
|
3734
|
+
};
|
|
3735
|
+
};
|
|
3736
|
+
required: string[];
|
|
3737
|
+
};
|
|
3738
|
+
};
|
|
3739
|
+
};
|
|
3740
|
+
};
|
|
3741
|
+
};
|
|
3742
|
+
};
|
|
3743
|
+
};
|
|
3744
|
+
}, {
|
|
3745
|
+
nonce: string;
|
|
3746
|
+
}>;
|
|
3681
3747
|
readonly listUserAccounts: import("better-call").StrictEndpoint<"/list-accounts", {
|
|
3682
3748
|
method: "GET";
|
|
3683
3749
|
use: Middleware<import("better-call").MiddlewareOptions, (inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<{
|
|
@@ -4048,4 +4114,4 @@ export declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext
|
|
|
4048
4114
|
} extends (infer T_2) ? { [K in keyof T_2 as K extends keyof T_1 ? never : K]: T_2[K]; } : never) & T_1> : never : never : never;
|
|
4049
4115
|
};
|
|
4050
4116
|
//#endregion
|
|
4051
|
-
export { APIError, type AuthEndpoint, type AuthMiddleware, type DispatchContext, NO_STORE_HEADERS, accountInfo, addOAuthServerContext, callbackOAuth, changeEmail, changePassword, createAuthEndpoint, createAuthMiddleware, createEmailVerificationToken, deleteUser, deleteUserCallback, dispatchAuthEndpoint, error, formCsrfMiddleware, freshSessionMiddleware, getAccessToken, getAuthoritativeSessionFromCtx, getIP, getOAuthState, getSession, getSessionFromCtx, getShouldSkipSessionRefresh, isAPIError, isStateful, linkSocialAccount, listSessions, listUserAccounts, ok, optionsMiddleware, originCheck, originCheckMiddleware, refreshToken, requestOnlySessionMiddleware, requestPasswordReset, requestPasswordResetCallback, requireOrgRole, requireResourceOwnership, resetPassword, revokeOtherSessions, revokeSession, revokeSessions, sendVerificationEmail, sendVerificationEmailFn, sensitiveSessionMiddleware, sessionMiddleware, setPassword, setShouldSkipSessionRefresh, signInEmail, signInSocial, signOut, signUpEmail, unlinkAccount, updateSession, updateUser, verifyEmail, verifyPassword };
|
|
4117
|
+
export { APIError, type AuthEndpoint, type AuthMiddleware, type DispatchContext, NO_STORE_HEADERS, accountInfo, addOAuthServerContext, callbackOAuth, changeEmail, changePassword, consumeIssuedIdTokenNonce, consumeNonce, createAuthEndpoint, createAuthMiddleware, createEmailVerificationToken, deleteUser, deleteUserCallback, dispatchAuthEndpoint, error, formCsrfMiddleware, freshSessionMiddleware, generateGenericState, getAccessToken, getAuthoritativeSessionFromCtx, getIP, getOAuthState, getSession, getSessionFromCtx, getShouldSkipSessionRefresh, isAPIError, isStateful, issueIdTokenNonce, issueNonce, issuedNonceResponses, linkSocialAccount, listSessions, listUserAccounts, ok, optionsMiddleware, originCheck, originCheckMiddleware, parseGenericState, refreshToken, requestOnlySessionMiddleware, requestPasswordReset, requestPasswordResetCallback, requireOrgRole, requireResourceOwnership, resetPassword, revokeOtherSessions, revokeSession, revokeSessions, sendVerificationEmail, sendVerificationEmailFn, sensitiveSessionMiddleware, sessionMiddleware, setPassword, setShouldSkipSessionRefresh, signInEmail, signInSocial, signOut, signUpEmail, unlinkAccount, updateSession, updateUser, verifyEmail, verifyPassword };
|
package/dist/api/index.mjs
CHANGED
|
@@ -5,6 +5,8 @@ import { onRequestRateLimit } from "./rate-limiter/index.mjs";
|
|
|
5
5
|
import { getShouldSkipSessionRefresh, setShouldSkipSessionRefresh } from "./state/should-session-refresh.mjs";
|
|
6
6
|
import { freshSessionMiddleware, getAuthoritativeSessionFromCtx, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware } from "./routes/session.mjs";
|
|
7
7
|
import { addOAuthServerContext, getOAuthState } from "./state/oauth.mjs";
|
|
8
|
+
import { generateGenericState, parseGenericState } from "../state.mjs";
|
|
9
|
+
import { consumeIssuedIdTokenNonce, consumeNonce, issueIdTokenNonce, issueNonce, issuedNonceResponses } from "./routes/id-token-nonce.mjs";
|
|
8
10
|
import { accountInfo, getAccessToken, linkSocialAccount, listUserAccounts, refreshToken, unlinkAccount } from "./routes/account.mjs";
|
|
9
11
|
import { callbackOAuth } from "./routes/callback.mjs";
|
|
10
12
|
import { createEmailVerificationToken, sendVerificationEmail, sendVerificationEmailFn, verifyEmail } from "./routes/email-verification.mjs";
|
|
@@ -134,6 +136,7 @@ function getEndpoints(ctx, options) {
|
|
|
134
136
|
revokeSessions,
|
|
135
137
|
revokeOtherSessions,
|
|
136
138
|
linkSocialAccount,
|
|
139
|
+
issueIdTokenNonce,
|
|
137
140
|
listUserAccounts,
|
|
138
141
|
deleteUserCallback,
|
|
139
142
|
unlinkAccount,
|
|
@@ -222,4 +225,4 @@ const router = (ctx, options) => {
|
|
|
222
225
|
});
|
|
223
226
|
};
|
|
224
227
|
//#endregion
|
|
225
|
-
export { APIError, NO_STORE_HEADERS, accountInfo, addOAuthServerContext, callbackOAuth, changeEmail, changePassword, checkEndpointConflicts, createAuthEndpoint, createAuthMiddleware, createEmailVerificationToken, deleteUser, deleteUserCallback, dispatchAuthEndpoint, error, formCsrfMiddleware, freshSessionMiddleware, getAccessToken, getAuthoritativeSessionFromCtx, getEndpoints, getIP, getOAuthState, getSession, getSessionFromCtx, getShouldSkipSessionRefresh, isAPIError, isStateful, linkSocialAccount, listSessions, listUserAccounts, ok, optionsMiddleware, originCheck, originCheckMiddleware, refreshToken, requestOnlySessionMiddleware, requestPasswordReset, requestPasswordResetCallback, requireOrgRole, requireResourceOwnership, resetPassword, revokeOtherSessions, revokeSession, revokeSessions, router, sendVerificationEmail, sendVerificationEmailFn, sensitiveSessionMiddleware, sessionMiddleware, setPassword, setShouldSkipSessionRefresh, signInEmail, signInSocial, signOut, signUpEmail, unlinkAccount, updateSession, updateUser, verifyEmail, verifyPassword };
|
|
228
|
+
export { APIError, NO_STORE_HEADERS, accountInfo, addOAuthServerContext, callbackOAuth, changeEmail, changePassword, checkEndpointConflicts, consumeIssuedIdTokenNonce, consumeNonce, createAuthEndpoint, createAuthMiddleware, createEmailVerificationToken, deleteUser, deleteUserCallback, dispatchAuthEndpoint, error, formCsrfMiddleware, freshSessionMiddleware, generateGenericState, getAccessToken, getAuthoritativeSessionFromCtx, getEndpoints, getIP, getOAuthState, getSession, getSessionFromCtx, getShouldSkipSessionRefresh, isAPIError, isStateful, issueIdTokenNonce, issueNonce, issuedNonceResponses, linkSocialAccount, listSessions, listUserAccounts, ok, optionsMiddleware, originCheck, originCheckMiddleware, parseGenericState, refreshToken, requestOnlySessionMiddleware, requestPasswordReset, requestPasswordResetCallback, requireOrgRole, requireResourceOwnership, resetPassword, revokeOtherSessions, revokeSession, revokeSessions, router, sendVerificationEmail, sendVerificationEmailFn, sensitiveSessionMiddleware, sessionMiddleware, setPassword, setShouldSkipSessionRefresh, signInEmail, signInSocial, signOut, signUpEmail, unlinkAccount, updateSession, updateUser, verifyEmail, verifyPassword };
|
|
@@ -11,6 +11,7 @@ import { toBoolean } from "../../utils/boolean.mjs";
|
|
|
11
11
|
import { decryptOAuthToken, getOAuthCallbackPath, setTokenUtil } from "../../oauth2/utils.mjs";
|
|
12
12
|
import { applyUpdateUserInfoOnLink } from "../../oauth2/link-account.mjs";
|
|
13
13
|
import { generateIdTokenNonce, generateState } from "../../oauth2/state.mjs";
|
|
14
|
+
import { consumeIssuedIdTokenNonce } from "./id-token-nonce.mjs";
|
|
14
15
|
import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
|
|
15
16
|
import { additionalAuthorizationParamsSchema, supportsIdTokenSignIn, verifyProviderIdToken } from "@better-auth/core/oauth2";
|
|
16
17
|
import { SocialProviderListEnum } from "@better-auth/core/social-providers";
|
|
@@ -168,6 +169,7 @@ const linkSocialAccount = createAuthEndpoint("/link-social", {
|
|
|
168
169
|
throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.ID_TOKEN_NOT_SUPPORTED);
|
|
169
170
|
}
|
|
170
171
|
const { token, nonce } = c.body.idToken;
|
|
172
|
+
await consumeIssuedIdTokenNonce(c, provider, nonce);
|
|
171
173
|
if (!await verifyProviderIdToken(provider, token, nonce, c)) {
|
|
172
174
|
c.context.logger.warn("Invalid id token", { provider: c.body.provider });
|
|
173
175
|
throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.INVALID_TOKEN);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { GenericEndpointContext } from "@better-auth/core";
|
|
2
|
+
import { OAuthProvider } from "@better-auth/core/oauth2";
|
|
3
|
+
import * as z from "zod";
|
|
4
|
+
//#region src/api/routes/id-token-nonce.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Store a new single-use nonce under `scope` and return it. It expires after
|
|
7
|
+
* five minutes.
|
|
8
|
+
*/
|
|
9
|
+
export declare function issueNonce(ctx: GenericEndpointContext, scope: string): Promise<string>;
|
|
10
|
+
/**
|
|
11
|
+
* Consume a nonce that {@link issueNonce} stored under `scope`. Throws
|
|
12
|
+
* `INVALID_ID_TOKEN_NONCE` when it is missing, malformed, unknown, expired, or
|
|
13
|
+
* already used. Call it before verifying the token, so a failed attempt burns
|
|
14
|
+
* the nonce too.
|
|
15
|
+
*/
|
|
16
|
+
export declare function consumeNonce(ctx: GenericEndpointContext, scope: string, nonce: string | undefined): Promise<void>;
|
|
17
|
+
/** OpenAPI responses for an endpoint that returns `{ nonce }` from {@link issueNonce}. */
|
|
18
|
+
export declare const issuedNonceResponses: {
|
|
19
|
+
200: {
|
|
20
|
+
description: string;
|
|
21
|
+
content: {
|
|
22
|
+
"application/json": {
|
|
23
|
+
schema: {
|
|
24
|
+
type: "object";
|
|
25
|
+
properties: {
|
|
26
|
+
nonce: {
|
|
27
|
+
type: "string";
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
required: string[];
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
type ProviderWithOptions = Pick<OAuthProvider, "id" | "options">;
|
|
37
|
+
/**
|
|
38
|
+
* Consume the issued nonce presented with a client-submitted ID token, from
|
|
39
|
+
* `/sign-in/social` or `/link-social`. A no-op for providers without
|
|
40
|
+
* `requireIssuedIdTokenNonce`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function consumeIssuedIdTokenNonce(ctx: GenericEndpointContext, provider: ProviderWithOptions, nonce: string | undefined): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* ### Endpoint
|
|
45
|
+
*
|
|
46
|
+
* POST `/id-token/nonce`
|
|
47
|
+
*
|
|
48
|
+
* Issues a single-use nonce for a client to pass to the provider's SDK before
|
|
49
|
+
* submitting the resulting ID token to `/sign-in/social` or `/link-social`.
|
|
50
|
+
* Only available for providers that set `requireIssuedIdTokenNonce`.
|
|
51
|
+
*/
|
|
52
|
+
export declare const issueIdTokenNonce: import("better-call").StrictEndpoint<"/id-token/nonce", {
|
|
53
|
+
method: "POST";
|
|
54
|
+
body: z.ZodObject<{
|
|
55
|
+
provider: z.ZodType<(string & {}) | "github" | "apple" | "atlassian" | "cloudflare" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "huggingface" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linear" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat", unknown, z.core.$ZodTypeInternals<(string & {}) | "github" | "apple" | "atlassian" | "cloudflare" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "huggingface" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linear" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat", unknown>>;
|
|
56
|
+
}, z.core.$strip>;
|
|
57
|
+
metadata: {
|
|
58
|
+
noStore: boolean;
|
|
59
|
+
openapi: {
|
|
60
|
+
description: string;
|
|
61
|
+
responses: {
|
|
62
|
+
200: {
|
|
63
|
+
description: string;
|
|
64
|
+
content: {
|
|
65
|
+
"application/json": {
|
|
66
|
+
schema: {
|
|
67
|
+
type: "object";
|
|
68
|
+
properties: {
|
|
69
|
+
nonce: {
|
|
70
|
+
type: "string";
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
required: string[];
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
}, {
|
|
82
|
+
nonce: string;
|
|
83
|
+
}>;
|
|
84
|
+
//#endregion
|
|
85
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { generateRandomString } from "../../crypto/random.mjs";
|
|
2
|
+
import { getAwaitableValue } from "../../context/helpers.mjs";
|
|
3
|
+
import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
|
|
4
|
+
import { SocialProviderListEnum } from "@better-auth/core/social-providers";
|
|
5
|
+
import { createAuthEndpoint } from "@better-auth/core/api";
|
|
6
|
+
import * as z from "zod";
|
|
7
|
+
//#region src/api/routes/id-token-nonce.ts
|
|
8
|
+
const ISSUED_NONCE_EXPIRES_IN_MS = 3e5;
|
|
9
|
+
const ISSUED_NONCE_LENGTH = 32;
|
|
10
|
+
const ISSUED_NONCE_PATTERN = new RegExp(`^[a-zA-Z0-9]{${ISSUED_NONCE_LENGTH}}$`);
|
|
11
|
+
/**
|
|
12
|
+
* Store a new single-use nonce under `scope` and return it. It expires after
|
|
13
|
+
* five minutes.
|
|
14
|
+
*/
|
|
15
|
+
async function issueNonce(ctx, scope) {
|
|
16
|
+
const nonce = generateRandomString(ISSUED_NONCE_LENGTH, "a-z", "A-Z", "0-9");
|
|
17
|
+
await ctx.context.internalAdapter.createVerificationValue({
|
|
18
|
+
identifier: `${scope}:${nonce}`,
|
|
19
|
+
value: scope,
|
|
20
|
+
expiresAt: new Date(Date.now() + ISSUED_NONCE_EXPIRES_IN_MS)
|
|
21
|
+
});
|
|
22
|
+
return nonce;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Consume a nonce that {@link issueNonce} stored under `scope`. Throws
|
|
26
|
+
* `INVALID_ID_TOKEN_NONCE` when it is missing, malformed, unknown, expired, or
|
|
27
|
+
* already used. Call it before verifying the token, so a failed attempt burns
|
|
28
|
+
* the nonce too.
|
|
29
|
+
*/
|
|
30
|
+
async function consumeNonce(ctx, scope, nonce) {
|
|
31
|
+
if (!(nonce && ISSUED_NONCE_PATTERN.test(nonce) ? await ctx.context.internalAdapter.consumeVerificationValue(`${scope}:${nonce}`) : null)) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.INVALID_ID_TOKEN_NONCE);
|
|
32
|
+
}
|
|
33
|
+
/** OpenAPI responses for an endpoint that returns `{ nonce }` from {@link issueNonce}. */
|
|
34
|
+
const issuedNonceResponses = { 200: {
|
|
35
|
+
description: "Success",
|
|
36
|
+
content: { "application/json": { schema: {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: { nonce: { type: "string" } },
|
|
39
|
+
required: ["nonce"]
|
|
40
|
+
} } }
|
|
41
|
+
} };
|
|
42
|
+
const idTokenNonceScope = (providerId) => `id-token-nonce:${providerId}`;
|
|
43
|
+
const requiresIssuedNonce = (provider) => provider.options?.requireIssuedIdTokenNonce === true;
|
|
44
|
+
/**
|
|
45
|
+
* Consume the issued nonce presented with a client-submitted ID token, from
|
|
46
|
+
* `/sign-in/social` or `/link-social`. A no-op for providers without
|
|
47
|
+
* `requireIssuedIdTokenNonce`.
|
|
48
|
+
*/
|
|
49
|
+
async function consumeIssuedIdTokenNonce(ctx, provider, nonce) {
|
|
50
|
+
if (!requiresIssuedNonce(provider)) return;
|
|
51
|
+
await consumeNonce(ctx, idTokenNonceScope(provider.id), nonce);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* ### Endpoint
|
|
55
|
+
*
|
|
56
|
+
* POST `/id-token/nonce`
|
|
57
|
+
*
|
|
58
|
+
* Issues a single-use nonce for a client to pass to the provider's SDK before
|
|
59
|
+
* submitting the resulting ID token to `/sign-in/social` or `/link-social`.
|
|
60
|
+
* Only available for providers that set `requireIssuedIdTokenNonce`.
|
|
61
|
+
*/
|
|
62
|
+
const issueIdTokenNonce = createAuthEndpoint("/id-token/nonce", {
|
|
63
|
+
method: "POST",
|
|
64
|
+
body: z.object({ provider: SocialProviderListEnum }),
|
|
65
|
+
metadata: {
|
|
66
|
+
noStore: true,
|
|
67
|
+
openapi: {
|
|
68
|
+
description: "Issue a single-use nonce to bind a provider ID token to one sign-in or account-linking request",
|
|
69
|
+
responses: issuedNonceResponses
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}, async (ctx) => {
|
|
73
|
+
const provider = await getAwaitableValue(ctx.context.socialProviders, { value: ctx.body.provider });
|
|
74
|
+
if (!provider) throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.PROVIDER_NOT_FOUND);
|
|
75
|
+
if (!requiresIssuedNonce(provider)) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.ID_TOKEN_NONCE_NOT_ENABLED);
|
|
76
|
+
const nonce = await issueNonce(ctx, idTokenNonceScope(provider.id));
|
|
77
|
+
return ctx.json({ nonce });
|
|
78
|
+
});
|
|
79
|
+
//#endregion
|
|
80
|
+
export { consumeIssuedIdTokenNonce, consumeNonce, issueIdTokenNonce, issueNonce, issuedNonceResponses };
|
|
@@ -2,6 +2,7 @@ import { accountInfo, getAccessToken, linkSocialAccount, listUserAccounts, refre
|
|
|
2
2
|
import { callbackOAuth } from "./callback.mjs";
|
|
3
3
|
import { createEmailVerificationToken, sendVerificationEmail, sendVerificationEmailFn, verifyEmail } from "./email-verification.mjs";
|
|
4
4
|
import { error } from "./error.mjs";
|
|
5
|
+
import { consumeIssuedIdTokenNonce, consumeNonce, issueIdTokenNonce, issueNonce, issuedNonceResponses } from "./id-token-nonce.mjs";
|
|
5
6
|
import { ok } from "./ok.mjs";
|
|
6
7
|
import { requestPasswordReset, requestPasswordResetCallback, resetPassword, verifyPassword } from "./password.mjs";
|
|
7
8
|
import { freshSessionMiddleware, getAuthoritativeSessionFromCtx, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware } from "./session.mjs";
|
|
@@ -10,4 +11,4 @@ import { signOut } from "./sign-out.mjs";
|
|
|
10
11
|
import { signUpEmail } from "./sign-up.mjs";
|
|
11
12
|
import { updateSession } from "./update-session.mjs";
|
|
12
13
|
import { changeEmail, changePassword, deleteUser, deleteUserCallback, setPassword, updateUser } from "./update-user.mjs";
|
|
13
|
-
export { accountInfo, callbackOAuth, changeEmail, changePassword, createEmailVerificationToken, deleteUser, deleteUserCallback, error, freshSessionMiddleware, getAccessToken, getAuthoritativeSessionFromCtx, getSession, getSessionFromCtx, isStateful, linkSocialAccount, listSessions, listUserAccounts, ok, refreshToken, requestOnlySessionMiddleware, requestPasswordReset, requestPasswordResetCallback, resetPassword, revokeOtherSessions, revokeSession, revokeSessions, sendVerificationEmail, sendVerificationEmailFn, sensitiveSessionMiddleware, sessionMiddleware, setPassword, signInEmail, signInSocial, signOut, signUpEmail, unlinkAccount, updateSession, updateUser, verifyEmail, verifyPassword };
|
|
14
|
+
export { accountInfo, callbackOAuth, changeEmail, changePassword, consumeIssuedIdTokenNonce, consumeNonce, createEmailVerificationToken, deleteUser, deleteUserCallback, error, freshSessionMiddleware, getAccessToken, getAuthoritativeSessionFromCtx, getSession, getSessionFromCtx, isStateful, issueIdTokenNonce, issueNonce, issuedNonceResponses, linkSocialAccount, listSessions, listUserAccounts, ok, refreshToken, requestOnlySessionMiddleware, requestPasswordReset, requestPasswordResetCallback, resetPassword, revokeOtherSessions, revokeSession, revokeSessions, sendVerificationEmail, sendVerificationEmailFn, sensitiveSessionMiddleware, sessionMiddleware, setPassword, signInEmail, signInSocial, signOut, signUpEmail, unlinkAccount, updateSession, updateUser, verifyEmail, verifyPassword };
|
|
@@ -7,6 +7,7 @@ import { OAUTH_CALLBACK_ERROR_CODES, missingEmailLogMessage } from "../../oauth2
|
|
|
7
7
|
import { getOAuthCallbackPath } from "../../oauth2/utils.mjs";
|
|
8
8
|
import { handleOAuthUserInfo } from "../../oauth2/link-account.mjs";
|
|
9
9
|
import { generateIdTokenNonce, generateState } from "../../oauth2/state.mjs";
|
|
10
|
+
import { consumeIssuedIdTokenNonce } from "./id-token-nonce.mjs";
|
|
10
11
|
import { safeCloneRequest } from "../../utils/request.mjs";
|
|
11
12
|
import { createEmailVerificationToken } from "./email-verification.mjs";
|
|
12
13
|
import { assertPasswordNotTooLong } from "../../utils/password.mjs";
|
|
@@ -157,6 +158,7 @@ const signInSocial = () => createAuthEndpoint("/sign-in/social", {
|
|
|
157
158
|
throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.ID_TOKEN_NOT_SUPPORTED);
|
|
158
159
|
}
|
|
159
160
|
const { token, nonce } = c.body.idToken;
|
|
161
|
+
await consumeIssuedIdTokenNonce(c, provider, nonce);
|
|
160
162
|
if (!await verifyProviderIdToken(provider, token, nonce, c)) {
|
|
161
163
|
c.context.logger.warn("Invalid id token", { provider: c.body.provider });
|
|
162
164
|
throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.INVALID_TOKEN);
|
package/dist/client/proxy.mjs
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import { isAtom } from "../utils/is-atom.mjs";
|
|
2
2
|
import { toKebabCase } from "@better-auth/core/utils/string";
|
|
3
|
+
import { isPayloadMethod } from "@better-fetch/fetch";
|
|
3
4
|
//#region src/client/proxy.ts
|
|
4
|
-
function getMethod(path, knownPathMethods, args) {
|
|
5
|
-
const method = knownPathMethods[path];
|
|
6
|
-
const { fetchOptions, query: _query, ...body } = args || {};
|
|
7
|
-
if (method) return method;
|
|
8
|
-
if (fetchOptions?.method) return fetchOptions.method;
|
|
9
|
-
if (body && Object.keys(body).length > 0) return "POST";
|
|
10
|
-
return "GET";
|
|
11
|
-
}
|
|
12
5
|
function createDynamicPathProxy(routes, client, knownPathMethods, atoms, atomListeners) {
|
|
13
6
|
function createProxy(path = []) {
|
|
14
7
|
return new Proxy(function() {}, {
|
|
@@ -35,13 +28,13 @@ function createDynamicPathProxy(routes, client, knownPathMethods, atoms, atomLis
|
|
|
35
28
|
...fetchOptions,
|
|
36
29
|
...argFetchOptions
|
|
37
30
|
};
|
|
38
|
-
const method =
|
|
31
|
+
const method = knownPathMethods[routePath] || options.method || (Object.keys(body).length > 0 ? "POST" : "GET");
|
|
39
32
|
return await client(routePath, {
|
|
40
33
|
...options,
|
|
41
|
-
body: method
|
|
34
|
+
body: isPayloadMethod(method) ? {
|
|
42
35
|
...body,
|
|
43
36
|
...options?.body || {}
|
|
44
|
-
},
|
|
37
|
+
} : void 0,
|
|
45
38
|
query: query || options?.query,
|
|
46
39
|
method,
|
|
47
40
|
async onSuccess(context) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { buildSchema } from "./get-schema.mjs";
|
|
2
2
|
import { createLogger } from "@better-auth/core/env";
|
|
3
3
|
import { BetterAuthError } from "@better-auth/core/error";
|
|
4
|
-
import { createKyselyAdapter, getMssqlSchema, getPostgresSchema, toIntrospectedTables, toPhysicalSchema } from "@better-auth/kysely-adapter";
|
|
4
|
+
import { createKyselyAdapter, getMssqlSchema, getPostgresSchema, introspectPostgresTables, toIntrospectedTables, toPhysicalSchema } from "@better-auth/kysely-adapter";
|
|
5
5
|
import { initGetFieldName, initGetModelName } from "@better-auth/core/db/adapter";
|
|
6
6
|
import { diffSchema, formatSchemaFinding, getDatabaseFieldIndexName, getDatabaseIndexStringLength, getPortableDatabaseIdentifierKey, invalidateSchemaChecks } from "@better-auth/core/db/internal";
|
|
7
7
|
import { sql } from "kysely";
|
|
@@ -354,9 +354,8 @@ async function getMigrations(config, { throwOnUnsafe = true } = {}) {
|
|
|
354
354
|
logger.error("Only kysely adapter is supported for migrations. You can use `generate` command to generate the schema, if you're using a different adapter.");
|
|
355
355
|
process.exit(1);
|
|
356
356
|
}
|
|
357
|
-
const allTableMetadata = await db.introspection.getTables();
|
|
358
357
|
let target;
|
|
359
|
-
let tableMetadata
|
|
358
|
+
let tableMetadata;
|
|
360
359
|
switch (dbType) {
|
|
361
360
|
case "postgres": {
|
|
362
361
|
const schema = schemaName ?? await getPostgresSchema(db);
|
|
@@ -373,11 +372,7 @@ async function getMigrations(config, { throwOnUnsafe = true } = {}) {
|
|
|
373
372
|
} catch (error) {
|
|
374
373
|
logger.debug(`Could not verify schema existence: ${error instanceof Error ? error.message : String(error)}`);
|
|
375
374
|
}
|
|
376
|
-
|
|
377
|
-
* Kysely 0.28 does not expose `isForeign`, while 0.29 adds foreign table metadata.
|
|
378
|
-
* @see https://github.com/kysely-org/kysely/pull/1494
|
|
379
|
-
*/
|
|
380
|
-
tableMetadata = allTableMetadata.filter((table) => table.schema === schema && !table.isView && !("isForeign" in table && table.isForeign));
|
|
375
|
+
tableMetadata = (await introspectPostgresTables(db, [schema])).filter((table) => !table.isView && !table.isForeign);
|
|
381
376
|
logger.debug(`Found ${tableMetadata.length} table(s) in schema '${schema}': ${tableMetadata.map((table) => table.name).join(", ") || "(none)"}`);
|
|
382
377
|
break;
|
|
383
378
|
}
|
|
@@ -388,15 +383,18 @@ async function getMigrations(config, { throwOnUnsafe = true } = {}) {
|
|
|
388
383
|
schema
|
|
389
384
|
};
|
|
390
385
|
logger.debug(`SQL Server migration: Using schema '${schema}' (from the current user's default schema)`);
|
|
391
|
-
tableMetadata =
|
|
386
|
+
tableMetadata = (await db.introspection.getTables()).filter((table) => table.schema === schema);
|
|
392
387
|
break;
|
|
393
388
|
}
|
|
394
389
|
case "mysql":
|
|
395
390
|
target = { type: "mysql" };
|
|
391
|
+
tableMetadata = await db.introspection.getTables();
|
|
396
392
|
break;
|
|
397
|
-
case "sqlite":
|
|
393
|
+
case "sqlite":
|
|
394
|
+
target = { type: "sqlite" };
|
|
395
|
+
tableMetadata = await db.introspection.getTables();
|
|
398
396
|
}
|
|
399
|
-
const databaseIndexMap = await getDatabaseIndexMap(db, target,
|
|
397
|
+
const databaseIndexMap = await getDatabaseIndexMap(db, target, tableMetadata.map((table) => table.name), introspectIndexes);
|
|
400
398
|
const databaseColumnBounds = await getDatabaseColumnBounds(db, target);
|
|
401
399
|
const schemaProblems = diffSchema(toPhysicalSchema(db, betterAuthSchema), toIntrospectedTables(tableMetadata)).filter((finding) => finding.kind === "unexpected-required-column").map((finding) => formatSchemaFinding(finding, "database"));
|
|
402
400
|
const toBeCreated = [];
|
|
@@ -79,6 +79,16 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
79
79
|
const activeSessions = await secondaryStorage.get(`active-sessions-${userId}`);
|
|
80
80
|
return activeSessions ? safeJSONParse(activeSessions) || [] : [];
|
|
81
81
|
}
|
|
82
|
+
async function setActiveSessionReferences(userId, sessionReferences, now) {
|
|
83
|
+
if (!secondaryStorage) return;
|
|
84
|
+
const sorted = [...sessionReferences].sort((a, b) => a.expiresAt - b.expiresAt);
|
|
85
|
+
const ttl = getTTLSeconds(sorted.at(-1)?.expiresAt ?? 0, now);
|
|
86
|
+
if (ttl > 0) {
|
|
87
|
+
await secondaryStorage.set(`active-sessions-${userId}`, JSON.stringify(sorted), ttl);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
await secondaryStorage.delete(`active-sessions-${userId}`);
|
|
91
|
+
}
|
|
82
92
|
const revokedBeforeKey = (userId) => `revoked-sessions-before-${userId}`;
|
|
83
93
|
const revokedBeforeTTL = Math.max(sessionExpiration, 86400, Number(options.plugins?.find((plugin) => plugin.id === "admin")?.options?.impersonationSessionDuration) || 0);
|
|
84
94
|
async function isRevokedSession(session, revokedBeforeByUser) {
|
|
@@ -93,17 +103,9 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
93
103
|
await secondaryStorage.set(revokedBeforeKey(userId), String(Math.max(revokedAt, previousRevokedBefore || 0)), revokedBeforeTTL);
|
|
94
104
|
const deletedTokens = new Set(sessionReferences.map((session) => session.token));
|
|
95
105
|
for (const token of deletedTokens) await secondaryStorage.delete(token);
|
|
96
|
-
const activeSessionsKey = `active-sessions-${userId}`;
|
|
97
106
|
const currentSessionReferences = await getActiveSessionReferences(userId);
|
|
98
107
|
const now = Date.now();
|
|
99
|
-
|
|
100
|
-
remainingSessionReferences.sort((a, b) => a.expiresAt - b.expiresAt);
|
|
101
|
-
const furthestExpiration = remainingSessionReferences.at(-1)?.expiresAt;
|
|
102
|
-
if (furthestExpiration) {
|
|
103
|
-
await secondaryStorage.set(activeSessionsKey, JSON.stringify(remainingSessionReferences), getTTLSeconds(furthestExpiration, now));
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
await secondaryStorage.delete(activeSessionsKey);
|
|
108
|
+
await setActiveSessionReferences(userId, currentSessionReferences.filter((session) => session.expiresAt > now && !deletedTokens.has(session.token)), now);
|
|
107
109
|
}
|
|
108
110
|
async function queueCachedUserSessionDeletion(userId, sessionReferences) {
|
|
109
111
|
if (!secondaryStorage) return;
|
|
@@ -129,6 +131,20 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
129
131
|
if (verificationConsumeLocks.get(key) === next) verificationConsumeLocks.delete(key);
|
|
130
132
|
}
|
|
131
133
|
}
|
|
134
|
+
async function deleteExpiredVerifications() {
|
|
135
|
+
if (options.verification?.disableCleanup) return;
|
|
136
|
+
await queueAfterTransactionHook(async () => {
|
|
137
|
+
try {
|
|
138
|
+
await deleteManyWithHooks([{
|
|
139
|
+
field: "expiresAt",
|
|
140
|
+
value: /* @__PURE__ */ new Date(),
|
|
141
|
+
operator: "lt"
|
|
142
|
+
}], "verification", void 0);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
logger.error("Failed to delete expired verification values", error);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
132
148
|
return {
|
|
133
149
|
createOAuthUser: async (user, account) => {
|
|
134
150
|
return runWithTransaction(adapter, async () => {
|
|
@@ -310,12 +326,10 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
310
326
|
list = safeJSONParse(currentList) || [];
|
|
311
327
|
list = list.filter((session) => session.expiresAt > now && session.token !== data.token);
|
|
312
328
|
}
|
|
313
|
-
|
|
329
|
+
await setActiveSessionReferences(userId, [...list, {
|
|
314
330
|
token: data.token,
|
|
315
331
|
expiresAt: data.expiresAt.getTime()
|
|
316
|
-
}]
|
|
317
|
-
const furthestSessionTTL = getTTLSeconds(sorted.at(-1)?.expiresAt ?? data.expiresAt.getTime(), now);
|
|
318
|
-
if (furthestSessionTTL > 0) await secondaryStorage.set(`active-sessions-${userId}`, JSON.stringify(sorted), furthestSessionTTL);
|
|
332
|
+
}], now);
|
|
319
333
|
const user = await (await getCurrentAdapter(adapter)).findOne({
|
|
320
334
|
model: "user",
|
|
321
335
|
where: [{
|
|
@@ -467,13 +481,11 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
467
481
|
}), sessionTTL);
|
|
468
482
|
const listKey = `active-sessions-${updatedSession.userId}`;
|
|
469
483
|
const listRaw = await secondaryStorage.get(listKey);
|
|
470
|
-
const
|
|
484
|
+
const filtered = (listRaw ? safeJSONParse(listRaw) || [] : []).filter((s) => s.token !== sessionToken && s.expiresAt > now).concat([{
|
|
471
485
|
token: sessionToken,
|
|
472
486
|
expiresAt: expiresMs
|
|
473
|
-
}])
|
|
474
|
-
|
|
475
|
-
if (furthestSessionExp && furthestSessionExp > now) await secondaryStorage.set(listKey, JSON.stringify(sorted), getTTLSeconds(furthestSessionExp, now));
|
|
476
|
-
else await secondaryStorage.delete(listKey);
|
|
487
|
+
}]);
|
|
488
|
+
await setActiveSessionReferences(updatedSession.userId, filtered, now);
|
|
477
489
|
}
|
|
478
490
|
return updatedSession;
|
|
479
491
|
},
|
|
@@ -494,10 +506,7 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
494
506
|
if (currentList) {
|
|
495
507
|
const list = safeJSONParse(currentList) || [];
|
|
496
508
|
const now = Date.now();
|
|
497
|
-
|
|
498
|
-
const furthestSessionExp = filtered.sort((a, b) => a.expiresAt - b.expiresAt).at(-1)?.expiresAt;
|
|
499
|
-
if (filtered.length > 0 && furthestSessionExp && furthestSessionExp > Date.now()) await secondaryStorage.set(`active-sessions-${userId}`, JSON.stringify(filtered), getTTLSeconds(furthestSessionExp, now));
|
|
500
|
-
else await secondaryStorage.delete(`active-sessions-${userId}`);
|
|
509
|
+
await setActiveSessionReferences(userId, list.filter((session) => session.expiresAt > now && session.token !== token), now);
|
|
501
510
|
} else logger.error("Active sessions list not found in secondary storage");
|
|
502
511
|
}
|
|
503
512
|
await secondaryStorage.delete(token);
|
|
@@ -800,11 +809,7 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
800
809
|
}
|
|
801
810
|
let verification = await findByIdentifier(storedIdentifier);
|
|
802
811
|
if (!verification.length && storageOption && storageOption !== "plain") verification = await findByIdentifier(identifier);
|
|
803
|
-
|
|
804
|
-
field: "expiresAt",
|
|
805
|
-
value: /* @__PURE__ */ new Date(),
|
|
806
|
-
operator: "lt"
|
|
807
|
-
}], "verification", void 0);
|
|
812
|
+
await deleteExpiredVerifications();
|
|
808
813
|
return verification[0] || null;
|
|
809
814
|
},
|
|
810
815
|
deleteVerificationByIdentifier: async (identifier) => {
|
|
@@ -903,6 +908,7 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
903
908
|
if (consumed) break;
|
|
904
909
|
}
|
|
905
910
|
if (consumed && secondaryStorage) await Promise.all(identifiersToTry.map((stored) => secondaryStorage.delete(`verification:${stored}`)));
|
|
911
|
+
if (consumed) await deleteExpiredVerifications();
|
|
906
912
|
}
|
|
907
913
|
if (!consumed || consumed.expiresAt < /* @__PURE__ */ new Date()) return null;
|
|
908
914
|
return consumed;
|
|
@@ -935,7 +941,10 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
935
941
|
* is best-effort there (it is intended for development and tests).
|
|
936
942
|
*/
|
|
937
943
|
reserveVerificationValue: async (data) => {
|
|
938
|
-
const reservationId = encodeDeterministicId(new Uint8Array(await createHash("SHA-256").digest(new TextEncoder().encode("reserve:" + data.identifier))), options
|
|
944
|
+
const reservationId = encodeDeterministicId(new Uint8Array(await createHash("SHA-256").digest(new TextEncoder().encode("reserve:" + data.identifier))), options, {
|
|
945
|
+
adapter,
|
|
946
|
+
model: "verification"
|
|
947
|
+
});
|
|
939
948
|
const storageOption = getStorageOption(data.identifier, options.verification?.storeIdentifier);
|
|
940
949
|
const storedIdentifier = await processIdentifier(data.identifier, storageOption);
|
|
941
950
|
if (secondaryStorage && !options.verification?.storeInDatabase) throw new BetterAuthError("reserveVerificationValue requires database-backed verification storage. Set verification.storeInDatabase to true for flows that reserve verification values.");
|
package/dist/db/schema.mjs
CHANGED
|
@@ -149,16 +149,25 @@ function getSessionDefaultFields(options) {
|
|
|
149
149
|
}
|
|
150
150
|
function mergeSchema(schema, newSchema) {
|
|
151
151
|
if (!newSchema) return schema;
|
|
152
|
+
const merged = { ...schema };
|
|
152
153
|
for (const table in newSchema) {
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
154
|
+
const base = schema[table];
|
|
155
|
+
const override = newSchema[table];
|
|
156
|
+
if (!base || !override) continue;
|
|
157
|
+
const fields = Object.fromEntries(Object.entries(base.fields).map(([key, field]) => {
|
|
158
|
+
const fieldName = override.fields?.[key];
|
|
159
|
+
return [key, fieldName ? {
|
|
160
|
+
...field,
|
|
161
|
+
fieldName
|
|
162
|
+
} : field];
|
|
163
|
+
}));
|
|
164
|
+
merged[table] = {
|
|
165
|
+
...base,
|
|
166
|
+
...override.modelName ? { modelName: override.modelName } : {},
|
|
167
|
+
fields
|
|
168
|
+
};
|
|
160
169
|
}
|
|
161
|
-
return
|
|
170
|
+
return merged;
|
|
162
171
|
}
|
|
163
172
|
//#endregion
|
|
164
173
|
export { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseAdditionalUserInputFromProviderProfile, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput };
|
package/dist/package.mjs
CHANGED