@better-auth/core 1.7.0-beta.5 → 1.7.0-beta.7
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 +44 -1
- package/dist/api/index.mjs +40 -1
- package/dist/context/global.mjs +1 -1
- package/dist/context/transaction.d.mts +7 -4
- package/dist/context/transaction.mjs +6 -3
- package/dist/db/adapter/factory.mjs +57 -31
- package/dist/db/adapter/index.d.mts +54 -10
- package/dist/db/adapter/types.d.mts +1 -1
- package/dist/db/type.d.mts +12 -7
- package/dist/instrumentation/tracer.mjs +1 -1
- package/dist/oauth2/create-authorization-url.d.mts +3 -1
- package/dist/oauth2/create-authorization-url.mjs +3 -1
- package/dist/oauth2/dpop.d.mts +142 -0
- package/dist/oauth2/dpop.mjs +246 -0
- package/dist/oauth2/index.d.mts +4 -3
- package/dist/oauth2/index.mjs +3 -2
- package/dist/oauth2/oauth-provider.d.mts +37 -3
- package/dist/oauth2/refresh-access-token.mjs +15 -1
- package/dist/oauth2/verify.d.mts +74 -15
- package/dist/oauth2/verify.mjs +172 -20
- package/dist/social-providers/apple.d.mts +2 -0
- package/dist/social-providers/atlassian.d.mts +2 -0
- package/dist/social-providers/cognito.d.mts +2 -0
- package/dist/social-providers/discord.d.mts +2 -0
- package/dist/social-providers/dropbox.d.mts +2 -0
- package/dist/social-providers/facebook.d.mts +2 -0
- package/dist/social-providers/figma.d.mts +2 -0
- package/dist/social-providers/github.d.mts +2 -0
- package/dist/social-providers/gitlab.d.mts +2 -0
- package/dist/social-providers/google.d.mts +2 -0
- package/dist/social-providers/huggingface.d.mts +2 -0
- package/dist/social-providers/index.d.mts +71 -0
- package/dist/social-providers/kakao.d.mts +2 -0
- package/dist/social-providers/kick.d.mts +2 -0
- package/dist/social-providers/line.d.mts +2 -0
- package/dist/social-providers/linear.d.mts +2 -0
- package/dist/social-providers/linkedin.d.mts +2 -0
- package/dist/social-providers/microsoft-entra-id.d.mts +12 -0
- package/dist/social-providers/microsoft-entra-id.mjs +17 -2
- package/dist/social-providers/naver.d.mts +2 -0
- package/dist/social-providers/notion.d.mts +2 -0
- package/dist/social-providers/paybin.d.mts +2 -0
- package/dist/social-providers/paypal.d.mts +2 -0
- package/dist/social-providers/polar.d.mts +2 -0
- package/dist/social-providers/railway.d.mts +2 -0
- package/dist/social-providers/reddit.d.mts +2 -0
- package/dist/social-providers/reddit.mjs +1 -1
- package/dist/social-providers/roblox.d.mts +2 -0
- package/dist/social-providers/salesforce.d.mts +2 -0
- package/dist/social-providers/slack.d.mts +2 -0
- package/dist/social-providers/spotify.d.mts +2 -0
- package/dist/social-providers/tiktok.d.mts +2 -0
- package/dist/social-providers/twitch.d.mts +2 -0
- package/dist/social-providers/twitter.d.mts +2 -0
- package/dist/social-providers/vercel.d.mts +2 -0
- package/dist/social-providers/vk.d.mts +2 -0
- package/dist/social-providers/wechat.d.mts +2 -0
- package/dist/social-providers/wechat.mjs +1 -1
- package/dist/social-providers/zoom.d.mts +2 -0
- package/dist/types/context.d.mts +17 -0
- package/dist/types/init-options.d.mts +45 -5
- package/dist/types/plugin-client.d.mts +12 -2
- package/dist/utils/host.d.mts +1 -1
- package/dist/utils/host.mjs +7 -0
- package/dist/utils/url.mjs +4 -3
- package/package.json +5 -5
- package/src/api/index.ts +82 -0
- package/src/context/transaction.ts +45 -12
- package/src/db/adapter/factory.ts +127 -72
- package/src/db/adapter/index.ts +54 -9
- package/src/db/adapter/types.ts +1 -0
- package/src/db/type.ts +12 -7
- package/src/oauth2/create-authorization-url.ts +4 -0
- package/src/oauth2/dpop.ts +568 -0
- package/src/oauth2/index.ts +45 -1
- package/src/oauth2/oauth-provider.ts +40 -2
- package/src/oauth2/refresh-access-token.ts +27 -3
- package/src/oauth2/verify-id-token.ts +2 -0
- package/src/oauth2/verify.ts +329 -66
- package/src/social-providers/microsoft-entra-id.ts +44 -1
- package/src/social-providers/reddit.ts +5 -1
- package/src/social-providers/wechat.ts +8 -1
- package/src/types/context.ts +18 -0
- package/src/types/init-options.ts +40 -8
- package/src/types/plugin-client.ts +16 -2
- package/src/utils/host.ts +25 -1
- package/src/utils/url.ts +10 -4
|
@@ -37,6 +37,7 @@ declare const naver: (options: NaverOptions) => {
|
|
|
37
37
|
redirectURI: string;
|
|
38
38
|
display?: string | undefined;
|
|
39
39
|
loginHint?: string | undefined;
|
|
40
|
+
idTokenNonce?: string | undefined;
|
|
40
41
|
additionalParams?: Record<string, string> | undefined;
|
|
41
42
|
}): Promise<{
|
|
42
43
|
url: URL;
|
|
@@ -53,6 +54,7 @@ declare const naver: (options: NaverOptions) => {
|
|
|
53
54
|
}) => Promise<OAuth2Tokens>;
|
|
54
55
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
55
56
|
getUserInfo(token: OAuth2Tokens & {
|
|
57
|
+
expectedIdTokenNonce?: string | undefined;
|
|
56
58
|
user?: {
|
|
57
59
|
name?: {
|
|
58
60
|
firstName?: string;
|
|
@@ -30,6 +30,7 @@ declare const notion: (options: NotionOptions) => {
|
|
|
30
30
|
redirectURI: string;
|
|
31
31
|
display?: string | undefined;
|
|
32
32
|
loginHint?: string | undefined;
|
|
33
|
+
idTokenNonce?: string | undefined;
|
|
33
34
|
additionalParams?: Record<string, string> | undefined;
|
|
34
35
|
}): Promise<{
|
|
35
36
|
url: URL;
|
|
@@ -46,6 +47,7 @@ declare const notion: (options: NotionOptions) => {
|
|
|
46
47
|
}) => Promise<OAuth2Tokens>;
|
|
47
48
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
48
49
|
getUserInfo(token: OAuth2Tokens & {
|
|
50
|
+
expectedIdTokenNonce?: string | undefined;
|
|
49
51
|
user?: {
|
|
50
52
|
name?: {
|
|
51
53
|
firstName?: string;
|
|
@@ -36,6 +36,7 @@ declare const paybin: (options: PaybinOptions) => {
|
|
|
36
36
|
redirectURI: string;
|
|
37
37
|
display?: string | undefined;
|
|
38
38
|
loginHint?: string | undefined;
|
|
39
|
+
idTokenNonce?: string | undefined;
|
|
39
40
|
additionalParams?: Record<string, string> | undefined;
|
|
40
41
|
}): Promise<{
|
|
41
42
|
url: URL;
|
|
@@ -53,6 +54,7 @@ declare const paybin: (options: PaybinOptions) => {
|
|
|
53
54
|
}) => Promise<OAuth2Tokens>;
|
|
54
55
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
55
56
|
getUserInfo(token: OAuth2Tokens & {
|
|
57
|
+
expectedIdTokenNonce?: string | undefined;
|
|
56
58
|
user?: {
|
|
57
59
|
name?: {
|
|
58
60
|
firstName?: string;
|
|
@@ -64,6 +64,7 @@ declare const paypal: (options: PayPalOptions) => {
|
|
|
64
64
|
redirectURI: string;
|
|
65
65
|
display?: string | undefined;
|
|
66
66
|
loginHint?: string | undefined;
|
|
67
|
+
idTokenNonce?: string | undefined;
|
|
67
68
|
additionalParams?: Record<string, string> | undefined;
|
|
68
69
|
}): Promise<{
|
|
69
70
|
url: URL;
|
|
@@ -89,6 +90,7 @@ declare const paypal: (options: PayPalOptions) => {
|
|
|
89
90
|
accessTokenExpiresAt: Date | undefined;
|
|
90
91
|
}>);
|
|
91
92
|
getUserInfo(token: OAuth2Tokens & {
|
|
93
|
+
expectedIdTokenNonce?: string | undefined;
|
|
92
94
|
user?: {
|
|
93
95
|
name?: {
|
|
94
96
|
firstName?: string;
|
|
@@ -39,6 +39,7 @@ declare const polar: (options: PolarOptions) => {
|
|
|
39
39
|
redirectURI: string;
|
|
40
40
|
display?: string | undefined;
|
|
41
41
|
loginHint?: string | undefined;
|
|
42
|
+
idTokenNonce?: string | undefined;
|
|
42
43
|
additionalParams?: Record<string, string> | undefined;
|
|
43
44
|
}): Promise<{
|
|
44
45
|
url: URL;
|
|
@@ -56,6 +57,7 @@ declare const polar: (options: PolarOptions) => {
|
|
|
56
57
|
}) => Promise<OAuth2Tokens>;
|
|
57
58
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
58
59
|
getUserInfo(token: OAuth2Tokens & {
|
|
60
|
+
expectedIdTokenNonce?: string | undefined;
|
|
59
61
|
user?: {
|
|
60
62
|
name?: {
|
|
61
63
|
firstName?: string;
|
|
@@ -30,6 +30,7 @@ declare const railway: (options: RailwayOptions) => {
|
|
|
30
30
|
redirectURI: string;
|
|
31
31
|
display?: string | undefined;
|
|
32
32
|
loginHint?: string | undefined;
|
|
33
|
+
idTokenNonce?: string | undefined;
|
|
33
34
|
additionalParams?: Record<string, string> | undefined;
|
|
34
35
|
}): Promise<{
|
|
35
36
|
url: URL;
|
|
@@ -47,6 +48,7 @@ declare const railway: (options: RailwayOptions) => {
|
|
|
47
48
|
}) => Promise<OAuth2Tokens>;
|
|
48
49
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
49
50
|
getUserInfo(token: OAuth2Tokens & {
|
|
51
|
+
expectedIdTokenNonce?: string | undefined;
|
|
50
52
|
user?: {
|
|
51
53
|
name?: {
|
|
52
54
|
firstName?: string;
|
|
@@ -28,6 +28,7 @@ declare const reddit: (options: RedditOptions) => {
|
|
|
28
28
|
redirectURI: string;
|
|
29
29
|
display?: string | undefined;
|
|
30
30
|
loginHint?: string | undefined;
|
|
31
|
+
idTokenNonce?: string | undefined;
|
|
31
32
|
additionalParams?: Record<string, string> | undefined;
|
|
32
33
|
}): Promise<{
|
|
33
34
|
url: URL;
|
|
@@ -44,6 +45,7 @@ declare const reddit: (options: RedditOptions) => {
|
|
|
44
45
|
}) => Promise<OAuth2Tokens>;
|
|
45
46
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
46
47
|
getUserInfo(token: OAuth2Tokens & {
|
|
48
|
+
expectedIdTokenNonce?: string | undefined;
|
|
47
49
|
user?: {
|
|
48
50
|
name?: {
|
|
49
51
|
firstName?: string;
|
|
@@ -62,7 +62,7 @@ const reddit = (options) => {
|
|
|
62
62
|
} });
|
|
63
63
|
if (error) return null;
|
|
64
64
|
const userMap = await options.mapProfileToUser?.(profile);
|
|
65
|
-
const email = userMap?.email || `${profile.id}@reddit.
|
|
65
|
+
const email = userMap?.email || `${profile.id}@reddit.invalid`;
|
|
66
66
|
return {
|
|
67
67
|
user: {
|
|
68
68
|
id: profile.id,
|
|
@@ -36,6 +36,7 @@ declare const roblox: (options: RobloxOptions) => {
|
|
|
36
36
|
redirectURI: string;
|
|
37
37
|
display?: string | undefined;
|
|
38
38
|
loginHint?: string | undefined;
|
|
39
|
+
idTokenNonce?: string | undefined;
|
|
39
40
|
additionalParams?: Record<string, string> | undefined;
|
|
40
41
|
}): Promise<{
|
|
41
42
|
url: URL;
|
|
@@ -52,6 +53,7 @@ declare const roblox: (options: RobloxOptions) => {
|
|
|
52
53
|
}) => Promise<OAuth2Tokens>;
|
|
53
54
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
54
55
|
getUserInfo(token: OAuth2Tokens & {
|
|
56
|
+
expectedIdTokenNonce?: string | undefined;
|
|
55
57
|
user?: {
|
|
56
58
|
name?: {
|
|
57
59
|
firstName?: string;
|
|
@@ -44,6 +44,7 @@ declare const salesforce: (options: SalesforceOptions) => {
|
|
|
44
44
|
redirectURI: string;
|
|
45
45
|
display?: string | undefined;
|
|
46
46
|
loginHint?: string | undefined;
|
|
47
|
+
idTokenNonce?: string | undefined;
|
|
47
48
|
additionalParams?: Record<string, string> | undefined;
|
|
48
49
|
}): Promise<{
|
|
49
50
|
url: URL;
|
|
@@ -61,6 +62,7 @@ declare const salesforce: (options: SalesforceOptions) => {
|
|
|
61
62
|
}) => Promise<OAuth2Tokens>;
|
|
62
63
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
63
64
|
getUserInfo(token: OAuth2Tokens & {
|
|
65
|
+
expectedIdTokenNonce?: string | undefined;
|
|
64
66
|
user?: {
|
|
65
67
|
name?: {
|
|
66
68
|
firstName?: string;
|
|
@@ -49,6 +49,7 @@ declare const slack: (options: SlackOptions) => {
|
|
|
49
49
|
redirectURI: string;
|
|
50
50
|
display?: string | undefined;
|
|
51
51
|
loginHint?: string | undefined;
|
|
52
|
+
idTokenNonce?: string | undefined;
|
|
52
53
|
additionalParams?: Record<string, string> | undefined;
|
|
53
54
|
}): Promise<{
|
|
54
55
|
url: URL;
|
|
@@ -65,6 +66,7 @@ declare const slack: (options: SlackOptions) => {
|
|
|
65
66
|
}) => Promise<OAuth2Tokens>;
|
|
66
67
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
67
68
|
getUserInfo(token: OAuth2Tokens & {
|
|
69
|
+
expectedIdTokenNonce?: string | undefined;
|
|
68
70
|
user?: {
|
|
69
71
|
name?: {
|
|
70
72
|
firstName?: string;
|
|
@@ -28,6 +28,7 @@ declare const spotify: (options: SpotifyOptions) => {
|
|
|
28
28
|
redirectURI: string;
|
|
29
29
|
display?: string | undefined;
|
|
30
30
|
loginHint?: string | undefined;
|
|
31
|
+
idTokenNonce?: string | undefined;
|
|
31
32
|
additionalParams?: Record<string, string> | undefined;
|
|
32
33
|
}): Promise<{
|
|
33
34
|
url: URL;
|
|
@@ -45,6 +46,7 @@ declare const spotify: (options: SpotifyOptions) => {
|
|
|
45
46
|
}) => Promise<OAuth2Tokens>;
|
|
46
47
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
47
48
|
getUserInfo(token: OAuth2Tokens & {
|
|
49
|
+
expectedIdTokenNonce?: string | undefined;
|
|
48
50
|
user?: {
|
|
49
51
|
name?: {
|
|
50
52
|
firstName?: string;
|
|
@@ -134,6 +134,7 @@ declare const tiktok: (options: TiktokOptions) => {
|
|
|
134
134
|
redirectURI: string;
|
|
135
135
|
display?: string | undefined;
|
|
136
136
|
loginHint?: string | undefined;
|
|
137
|
+
idTokenNonce?: string | undefined;
|
|
137
138
|
additionalParams?: Record<string, string> | undefined;
|
|
138
139
|
}): {
|
|
139
140
|
url: URL;
|
|
@@ -150,6 +151,7 @@ declare const tiktok: (options: TiktokOptions) => {
|
|
|
150
151
|
}) => Promise<OAuth2Tokens>;
|
|
151
152
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
152
153
|
getUserInfo(token: OAuth2Tokens & {
|
|
154
|
+
expectedIdTokenNonce?: string | undefined;
|
|
153
155
|
user?: {
|
|
154
156
|
name?: {
|
|
155
157
|
firstName?: string;
|
|
@@ -45,6 +45,7 @@ declare const twitch: (options: TwitchOptions) => {
|
|
|
45
45
|
redirectURI: string;
|
|
46
46
|
display?: string | undefined;
|
|
47
47
|
loginHint?: string | undefined;
|
|
48
|
+
idTokenNonce?: string | undefined;
|
|
48
49
|
additionalParams?: Record<string, string> | undefined;
|
|
49
50
|
}): Promise<{
|
|
50
51
|
url: URL;
|
|
@@ -61,6 +62,7 @@ declare const twitch: (options: TwitchOptions) => {
|
|
|
61
62
|
}) => Promise<OAuth2Tokens>;
|
|
62
63
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
63
64
|
getUserInfo(token: OAuth2Tokens & {
|
|
65
|
+
expectedIdTokenNonce?: string | undefined;
|
|
64
66
|
user?: {
|
|
65
67
|
name?: {
|
|
66
68
|
firstName?: string;
|
|
@@ -90,6 +90,7 @@ declare const twitter: (options: TwitterOption) => {
|
|
|
90
90
|
redirectURI: string;
|
|
91
91
|
display?: string | undefined;
|
|
92
92
|
loginHint?: string | undefined;
|
|
93
|
+
idTokenNonce?: string | undefined;
|
|
93
94
|
additionalParams?: Record<string, string> | undefined;
|
|
94
95
|
}): Promise<{
|
|
95
96
|
url: URL;
|
|
@@ -107,6 +108,7 @@ declare const twitter: (options: TwitterOption) => {
|
|
|
107
108
|
}) => Promise<OAuth2Tokens>;
|
|
108
109
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
109
110
|
getUserInfo(token: OAuth2Tokens & {
|
|
111
|
+
expectedIdTokenNonce?: string | undefined;
|
|
110
112
|
user?: {
|
|
111
113
|
name?: {
|
|
112
114
|
firstName?: string;
|
|
@@ -28,6 +28,7 @@ declare const vercel: (options: VercelOptions) => {
|
|
|
28
28
|
redirectURI: string;
|
|
29
29
|
display?: string | undefined;
|
|
30
30
|
loginHint?: string | undefined;
|
|
31
|
+
idTokenNonce?: string | undefined;
|
|
31
32
|
additionalParams?: Record<string, string> | undefined;
|
|
32
33
|
}): Promise<{
|
|
33
34
|
url: URL;
|
|
@@ -44,6 +45,7 @@ declare const vercel: (options: VercelOptions) => {
|
|
|
44
45
|
deviceId?: string | undefined;
|
|
45
46
|
}) => Promise<OAuth2Tokens>;
|
|
46
47
|
getUserInfo(token: OAuth2Tokens & {
|
|
48
|
+
expectedIdTokenNonce?: string | undefined;
|
|
47
49
|
user?: {
|
|
48
50
|
name?: {
|
|
49
51
|
firstName?: string;
|
|
@@ -34,6 +34,7 @@ declare const vk: (options: VkOption) => {
|
|
|
34
34
|
redirectURI: string;
|
|
35
35
|
display?: string | undefined;
|
|
36
36
|
loginHint?: string | undefined;
|
|
37
|
+
idTokenNonce?: string | undefined;
|
|
37
38
|
additionalParams?: Record<string, string> | undefined;
|
|
38
39
|
}): Promise<{
|
|
39
40
|
url: URL;
|
|
@@ -52,6 +53,7 @@ declare const vk: (options: VkOption) => {
|
|
|
52
53
|
}) => Promise<OAuth2Tokens>;
|
|
53
54
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
54
55
|
getUserInfo(data: OAuth2Tokens & {
|
|
56
|
+
expectedIdTokenNonce?: string | undefined;
|
|
55
57
|
user?: {
|
|
56
58
|
name?: {
|
|
57
59
|
firstName?: string;
|
|
@@ -66,6 +66,7 @@ declare const wechat: (options: WeChatOptions) => {
|
|
|
66
66
|
redirectURI: string;
|
|
67
67
|
display?: string | undefined;
|
|
68
68
|
loginHint?: string | undefined;
|
|
69
|
+
idTokenNonce?: string | undefined;
|
|
69
70
|
additionalParams?: Record<string, string> | undefined;
|
|
70
71
|
}): {
|
|
71
72
|
url: URL;
|
|
@@ -95,6 +96,7 @@ declare const wechat: (options: WeChatOptions) => {
|
|
|
95
96
|
scopes: string[];
|
|
96
97
|
}>);
|
|
97
98
|
getUserInfo(token: OAuth2Tokens & {
|
|
99
|
+
expectedIdTokenNonce?: string | undefined;
|
|
98
100
|
user?: {
|
|
99
101
|
name?: {
|
|
100
102
|
firstName?: string;
|
|
@@ -76,7 +76,7 @@ const wechat = (options) => {
|
|
|
76
76
|
user: {
|
|
77
77
|
id: profile.unionid || profile.openid || openid,
|
|
78
78
|
name: profile.nickname,
|
|
79
|
-
email: profile.email ||
|
|
79
|
+
email: profile.email || `${profile.unionid || profile.openid || openid}@wechat.invalid`,
|
|
80
80
|
image: profile.headimgurl,
|
|
81
81
|
emailVerified: false,
|
|
82
82
|
...userMap
|
|
@@ -130,6 +130,7 @@ declare const zoom: (userOptions: ZoomOptions) => {
|
|
|
130
130
|
redirectURI: string;
|
|
131
131
|
display?: string | undefined;
|
|
132
132
|
loginHint?: string | undefined;
|
|
133
|
+
idTokenNonce?: string | undefined;
|
|
133
134
|
additionalParams?: Record<string, string> | undefined;
|
|
134
135
|
}) => Promise<{
|
|
135
136
|
url: URL;
|
|
@@ -147,6 +148,7 @@ declare const zoom: (userOptions: ZoomOptions) => {
|
|
|
147
148
|
}) => Promise<OAuth2Tokens>;
|
|
148
149
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
149
150
|
getUserInfo(token: OAuth2Tokens & {
|
|
151
|
+
expectedIdTokenNonce?: string | undefined;
|
|
150
152
|
user?: {
|
|
151
153
|
name?: {
|
|
152
154
|
firstName?: string;
|
package/dist/types/context.d.mts
CHANGED
|
@@ -136,6 +136,23 @@ interface InternalAdapter<_Options extends BetterAuthOptions = BetterAuthOptions
|
|
|
136
136
|
* pair at single-use credential consumption sites.
|
|
137
137
|
*/
|
|
138
138
|
consumeVerificationValue(identifier: string): Promise<Verification | null>;
|
|
139
|
+
/**
|
|
140
|
+
* First-writer-wins create keyed by a deterministic primary key derived from
|
|
141
|
+
* `identifier`. Returns `true` when this caller created the row and `false`
|
|
142
|
+
* when a row for the same identifier already existed.
|
|
143
|
+
*
|
|
144
|
+
* The dual of `consumeVerificationValue`: reserve races to create a marker
|
|
145
|
+
* exactly once, where consume races to delete one exactly once. Use it for
|
|
146
|
+
* replay tombstones (a SAML assertion id, a JWT `jti`) where the first caller
|
|
147
|
+
* wins. The database path is atomic via the primary key. Secondary-storage-only
|
|
148
|
+
* verification is not supported for reservation and runtime implementations
|
|
149
|
+
* should fail closed unless verification is backed by the database.
|
|
150
|
+
*/
|
|
151
|
+
reserveVerificationValue(data: {
|
|
152
|
+
identifier: string;
|
|
153
|
+
value: string;
|
|
154
|
+
expiresAt: Date;
|
|
155
|
+
}): Promise<boolean>;
|
|
139
156
|
updateVerificationByIdentifier(identifier: string, data: Partial<Verification>): Promise<Verification>;
|
|
140
157
|
refreshUserSessions(user: User): Promise<void>;
|
|
141
158
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DBFieldAttribute, ModelNames, SecondaryStorage } from "../db/type.mjs";
|
|
2
2
|
import { DBAdapterDebugLogOption, DBAdapterInstance } from "../db/adapter/index.mjs";
|
|
3
|
-
import { BaseRateLimit
|
|
3
|
+
import { BaseRateLimit } from "../db/schema/rate-limit.mjs";
|
|
4
4
|
import { BaseSession, Session } from "../db/schema/session.mjs";
|
|
5
5
|
import { BaseUser, User } from "../db/schema/user.mjs";
|
|
6
6
|
import { BaseVerification, Verification } from "../db/schema/verification.mjs";
|
|
@@ -138,8 +138,30 @@ type DynamicBaseURLConfig = {
|
|
|
138
138
|
*/
|
|
139
139
|
type BaseURLConfig = string | DynamicBaseURLConfig;
|
|
140
140
|
interface BetterAuthRateLimitStorage {
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Atomically records one request against `key` within the rolling `window`
|
|
143
|
+
* (in seconds) and reports whether it is allowed.
|
|
144
|
+
*
|
|
145
|
+
* When `allowed` is true the count was incremented within the active window,
|
|
146
|
+
* or the window had elapsed and was reset to start at 1. When `allowed` is
|
|
147
|
+
* false the limit was already reached and `retryAfter` is the number of
|
|
148
|
+
* seconds until the window frees up.
|
|
149
|
+
*
|
|
150
|
+
* Performing the check and the increment in a single step closes the
|
|
151
|
+
* concurrent-bypass gap of the separate `get`/`set` path: N simultaneous
|
|
152
|
+
* requests can no longer all pass a stale read before any increment lands.
|
|
153
|
+
*
|
|
154
|
+
* Custom storages must implement this operation directly. Better Auth no
|
|
155
|
+
* longer accepts separate `get`/`set` rate-limit storage because that shape
|
|
156
|
+
* cannot enforce a distributed limit under concurrent requests.
|
|
157
|
+
*/
|
|
158
|
+
consume: (key: string, rule: {
|
|
159
|
+
window: number;
|
|
160
|
+
max: number;
|
|
161
|
+
}) => Promise<{
|
|
162
|
+
allowed: boolean;
|
|
163
|
+
retryAfter: number | null;
|
|
164
|
+
}>;
|
|
143
165
|
}
|
|
144
166
|
type BetterAuthRateLimitRule = {
|
|
145
167
|
/**
|
|
@@ -916,6 +938,20 @@ type BetterAuthOptions = {
|
|
|
916
938
|
* @default "compact"
|
|
917
939
|
*/
|
|
918
940
|
strategy?: "compact" | "jwt" | "jwe";
|
|
941
|
+
/**
|
|
942
|
+
* JWT-specific configuration for `strategy: "jwt"`.
|
|
943
|
+
*/
|
|
944
|
+
jwt?: {
|
|
945
|
+
/**
|
|
946
|
+
* Which signing key is used for cookie-cache JWTs.
|
|
947
|
+
*
|
|
948
|
+
* - `"secret"`: uses the Better Auth secret with HS256.
|
|
949
|
+
* - `"jwt-plugin"`: uses the installed `jwt()` plugin's asymmetric signing keys.
|
|
950
|
+
*
|
|
951
|
+
* @default "secret"
|
|
952
|
+
*/
|
|
953
|
+
signingKey?: "secret" | "jwt-plugin";
|
|
954
|
+
};
|
|
919
955
|
/**
|
|
920
956
|
* Controls stateless cookie cache refresh behavior.
|
|
921
957
|
*
|
|
@@ -1095,9 +1131,13 @@ type BetterAuthOptions = {
|
|
|
1095
1131
|
*/
|
|
1096
1132
|
storeStateStrategy?: "database" | "cookie";
|
|
1097
1133
|
/**
|
|
1098
|
-
* Store account data after
|
|
1134
|
+
* Store provider account data after an OAuth flow in an encrypted
|
|
1135
|
+
* cookie. This includes OAuth token material such as access tokens,
|
|
1136
|
+
* refresh tokens, ID tokens, scopes, and token expiry.
|
|
1099
1137
|
*
|
|
1100
|
-
* This is useful for database-less
|
|
1138
|
+
* This is useful for database-less flows, but large provider tokens can
|
|
1139
|
+
* still hit browser or proxy cookie/header limits even though Better Auth
|
|
1140
|
+
* chunks oversized account cookies.
|
|
1101
1141
|
*
|
|
1102
1142
|
* @default false
|
|
1103
1143
|
*
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import { LiteralString } from "./helper.mjs";
|
|
2
|
-
import { BetterAuthPlugin } from "./plugin.mjs";
|
|
3
2
|
import { BetterAuthOptions } from "./init-options.mjs";
|
|
4
3
|
import { BetterFetch, BetterFetchOption, BetterFetchPlugin } from "@better-fetch/fetch";
|
|
5
4
|
import { Atom, WritableAtom } from "nanostores";
|
|
6
5
|
|
|
7
6
|
//#region src/types/plugin-client.d.ts
|
|
7
|
+
type InferableServerPlugin = {
|
|
8
|
+
id?: LiteralString | undefined;
|
|
9
|
+
endpoints?: Record<string, unknown> | undefined;
|
|
10
|
+
schema?: Record<string, {
|
|
11
|
+
fields: Record<string, unknown>;
|
|
12
|
+
}> | undefined;
|
|
13
|
+
$ERROR_CODES?: Record<string, {
|
|
14
|
+
readonly code: string;
|
|
15
|
+
message: string;
|
|
16
|
+
}> | undefined;
|
|
17
|
+
};
|
|
8
18
|
interface ClientStore {
|
|
9
19
|
notify: (signal: string) => void;
|
|
10
20
|
listen: (signal: string, listener: () => void) => void;
|
|
@@ -71,7 +81,7 @@ interface BetterAuthClientPlugin {
|
|
|
71
81
|
* only used for type inference. don't pass the
|
|
72
82
|
* actual plugin
|
|
73
83
|
*/
|
|
74
|
-
$InferServerPlugin?:
|
|
84
|
+
$InferServerPlugin?: InferableServerPlugin | undefined;
|
|
75
85
|
/**
|
|
76
86
|
* Custom actions
|
|
77
87
|
*/
|
package/dist/utils/host.d.mts
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* The semantic kind of a host, derived from RFC 6890 special-purpose registries
|
|
27
27
|
* plus a few domain-name categories (localhost, cloud metadata FQDNs).
|
|
28
28
|
*/
|
|
29
|
-
type HostKind = /** IPv4 `127.0.0.0/8` or IPv6 `::1`. */"loopback" /** DNS name `localhost` or RFC 6761 `.localhost` TLD. */ | "localhost" /** IPv4 `0.0.0.0` or IPv6 `::` — "this host on this network", not loopback. */ | "unspecified" /** RFC 1918 `10/8`, `172.16/12`, `192.168/16`, or IPv6 ULA `fc00::/7`. */ | "private" /** IPv4 `169.254/16` or IPv6 `fe80::/10`. Includes AWS IMDS `169.254.169.254`. */ | "linkLocal" /** RFC 6598 carrier-grade NAT `100.64.0.0/10`. */ | "sharedAddressSpace" /** RFC 5737 `192.0.2/24`, `198.51.100/24`, `203.0.113/24`, or RFC 3849 `2001:db8::/32`. */ | "documentation" /** RFC 2544 `198.18.0.0/15`. */ | "benchmarking" /** IPv4 `224.0.0.0/4` or IPv6 `ff00::/8`. */ | "multicast" /** IPv4 limited broadcast `255.255.255.255`. */ | "broadcast" /** Other RFC 6890 special-purpose ranges (0/8, 192.0.0/24, 240/4, 2001::/32, etc.). */ | "reserved" /** Cloud metadata service FQDN (e.g. `metadata.google.internal`). */ | "cloudMetadata" /** Any host not matching a special-purpose range above. */ | "public";
|
|
29
|
+
type HostKind = /** IPv4 `127.0.0.0/8` or IPv6 `::1`. */"loopback" /** DNS name `localhost` or RFC 6761 `.localhost` TLD. */ | "localhost" /** IPv4 `0.0.0.0` or IPv6 `::` — "this host on this network", not loopback. */ | "unspecified" /** RFC 1918 `10/8`, `172.16/12`, `192.168/16`, or IPv6 ULA `fc00::/7`. */ | "private" /** IPv4 `169.254/16` or IPv6 `fe80::/10`. Includes AWS IMDS `169.254.169.254`. */ | "linkLocal" /** RFC 6598 carrier-grade NAT `100.64.0.0/10`. */ | "sharedAddressSpace" /** RFC 5737 `192.0.2/24`, `198.51.100/24`, `203.0.113/24`, or RFC 3849 `2001:db8::/32`. */ | "documentation" /** RFC 2544 `198.18.0.0/15`. */ | "benchmarking" /** IPv4 `224.0.0.0/4` or IPv6 `ff00::/8`. */ | "multicast" /** IPv4 limited broadcast `255.255.255.255`. */ | "broadcast" /** Other RFC 6890 special-purpose ranges (0.0.0.0/8, 192.0.0.0/24, 192.88.99.0/24, 240.0.0.0/4, 2001::/32, etc.). */ | "reserved" /** Cloud metadata service FQDN (e.g. `metadata.google.internal`). */ | "cloudMetadata" /** Any host not matching a special-purpose range above. */ | "public";
|
|
30
30
|
/**
|
|
31
31
|
* The syntactic form of the input host: an IPv4 literal, an IPv6 literal, or
|
|
32
32
|
* a domain name. IPv4-mapped IPv6 (`::ffff:192.0.2.1`) is reported as `ipv4`
|
package/dist/utils/host.mjs
CHANGED
|
@@ -86,6 +86,7 @@ function classifyIPv4(ip) {
|
|
|
86
86
|
if (inIPv4Range(n, ipv4ToUint32("224.0.0.0"), 4)) return "multicast";
|
|
87
87
|
if (inIPv4Range(n, ipv4ToUint32("0.0.0.0"), 8)) return "reserved";
|
|
88
88
|
if (inIPv4Range(n, ipv4ToUint32("192.0.0.0"), 24)) return "reserved";
|
|
89
|
+
if (inIPv4Range(n, ipv4ToUint32("192.88.99.0"), 24)) return "reserved";
|
|
89
90
|
if (inIPv4Range(n, ipv4ToUint32("240.0.0.0"), 4)) return "reserved";
|
|
90
91
|
return "public";
|
|
91
92
|
}
|
|
@@ -124,8 +125,10 @@ function classifyIPv6(expanded) {
|
|
|
124
125
|
const secondByte = Number.parseInt(expanded.slice(2, 4), 16);
|
|
125
126
|
if (firstByte === 255) return "multicast";
|
|
126
127
|
if (firstByte === 254 && (secondByte & 192) === 128) return "linkLocal";
|
|
128
|
+
if (firstByte === 254 && (secondByte & 192) === 192) return "reserved";
|
|
127
129
|
if ((firstByte & 254) === 252) return "private";
|
|
128
130
|
if (expanded.startsWith("2001:0db8:")) return "documentation";
|
|
131
|
+
if (expanded.startsWith("2001:0002:0000:")) return "benchmarking";
|
|
129
132
|
if (expanded.startsWith("2002:")) {
|
|
130
133
|
const embedded = extractEmbeddedIPv4(expanded, 1);
|
|
131
134
|
if (embedded && classifyIPv4(embedded) !== "public") return "reserved";
|
|
@@ -136,12 +139,16 @@ function classifyIPv6(expanded) {
|
|
|
136
139
|
if (embedded && classifyIPv4(embedded) !== "public") return "reserved";
|
|
137
140
|
return "reserved";
|
|
138
141
|
}
|
|
142
|
+
if (expanded.startsWith("0064:ff9b:0001:")) return "reserved";
|
|
139
143
|
if (expanded.startsWith("2001:0000:")) {
|
|
140
144
|
const embedded = extractEmbeddedIPv4(expanded, 6, { xor: true });
|
|
141
145
|
if (embedded && classifyIPv4(embedded) !== "public") return "reserved";
|
|
142
146
|
return "reserved";
|
|
143
147
|
}
|
|
144
148
|
if (expanded.startsWith("0100:0000:0000:0000:")) return "reserved";
|
|
149
|
+
if (expanded.startsWith("3fff:0")) return "documentation";
|
|
150
|
+
if (expanded.startsWith("5f00:")) return "reserved";
|
|
151
|
+
if (expanded.startsWith("0000:0000:0000:0000:0000:0000:")) return "reserved";
|
|
145
152
|
return "public";
|
|
146
153
|
}
|
|
147
154
|
/**
|
package/dist/utils/url.mjs
CHANGED
|
@@ -22,9 +22,10 @@ function normalizePathname(requestUrl, basePath) {
|
|
|
22
22
|
} catch {
|
|
23
23
|
return "/";
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
if (
|
|
27
|
-
if (pathname
|
|
25
|
+
const normalizedBasePath = basePath.replace(/\/+$/, "");
|
|
26
|
+
if (normalizedBasePath === "") return pathname;
|
|
27
|
+
if (pathname === normalizedBasePath) return "/";
|
|
28
|
+
if (pathname.startsWith(normalizedBasePath + "/")) return pathname.slice(normalizedBasePath.length).replace(/\/+$/, "") || "/";
|
|
28
29
|
return pathname;
|
|
29
30
|
}
|
|
30
31
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/core",
|
|
3
|
-
"version": "1.7.0-beta.
|
|
3
|
+
"version": "1.7.0-beta.7",
|
|
4
4
|
"description": "The most comprehensive authentication framework for TypeScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -152,8 +152,8 @@
|
|
|
152
152
|
"zod": "^4.3.6"
|
|
153
153
|
},
|
|
154
154
|
"devDependencies": {
|
|
155
|
-
"@better-auth/utils": "0.4.
|
|
156
|
-
"@better-fetch/fetch": "1.
|
|
155
|
+
"@better-auth/utils": "0.4.2",
|
|
156
|
+
"@better-fetch/fetch": "1.3.1",
|
|
157
157
|
"@opentelemetry/api": "^1.9.0",
|
|
158
158
|
"@opentelemetry/sdk-trace-base": "^1.30.0",
|
|
159
159
|
"@opentelemetry/sdk-trace-node": "^1.30.0",
|
|
@@ -165,8 +165,8 @@
|
|
|
165
165
|
"tsdown": "0.21.1"
|
|
166
166
|
},
|
|
167
167
|
"peerDependencies": {
|
|
168
|
-
"@better-auth/utils": "0.4.
|
|
169
|
-
"@better-fetch/fetch": "1.
|
|
168
|
+
"@better-auth/utils": "0.4.2",
|
|
169
|
+
"@better-fetch/fetch": "1.3.1",
|
|
170
170
|
"@opentelemetry/api": "^1.9.0",
|
|
171
171
|
"better-call": "1.3.6",
|
|
172
172
|
"@cloudflare/workers-types": ">=4",
|
package/src/api/index.ts
CHANGED
|
@@ -12,6 +12,25 @@ import { runWithEndpointContext } from "../context";
|
|
|
12
12
|
import type { AuthContext } from "../types";
|
|
13
13
|
import { isAPIError } from "../utils/is-api-error";
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Response headers that forbid any intermediary (proxy, CDN, browser) from
|
|
17
|
+
* caching a response body. Credential-bearing responses (access/refresh tokens,
|
|
18
|
+
* ID tokens, client secrets, device codes) must carry them.
|
|
19
|
+
*
|
|
20
|
+
* Set `metadata: { noStore: true }` on an endpoint and {@link createAuthEndpoint}
|
|
21
|
+
* applies these to the responses its handler produces: the success body and any
|
|
22
|
+
* error the handler throws. A request rejected by schema or media-type
|
|
23
|
+
* validation before the handler runs is not covered, and carries no credentials
|
|
24
|
+
* to protect. Spread them into a hand-built `Response` or `APIError`'s headers
|
|
25
|
+
* for the rare endpoint that constructs its own response.
|
|
26
|
+
*
|
|
27
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
|
|
28
|
+
*/
|
|
29
|
+
export const NO_STORE_HEADERS = {
|
|
30
|
+
"Cache-Control": "no-store",
|
|
31
|
+
Pragma: "no-cache",
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
15
34
|
/**
|
|
16
35
|
* Better-call's createEndpoint re-throws APIError without exposing the headers
|
|
17
36
|
* accumulated on ctx.responseHeaders (e.g. Set-Cookie from deleteSessionCookie
|
|
@@ -101,8 +120,23 @@ export function createAuthEndpoint<
|
|
|
101
120
|
const handler: EndpointHandler<Path, Opts, R> =
|
|
102
121
|
typeof handlerOrOptions === "function" ? handlerOrOptions : handlerOrNever;
|
|
103
122
|
|
|
123
|
+
// Endpoints that return credentials declare `metadata: { noStore: true }`.
|
|
124
|
+
// Emit the no-store headers at the boundary, before the handler runs, so they
|
|
125
|
+
// land on every response the handler produces: a success harvests
|
|
126
|
+
// `responseHeaders`, and a thrown error carries the same headers through
|
|
127
|
+
// `attachResponseHeadersToAPIError`. Validation that rejects the request
|
|
128
|
+
// before the handler runs is not covered (and returns no credentials).
|
|
129
|
+
const noStore =
|
|
130
|
+
(options as { metadata?: { noStore?: boolean } }).metadata?.noStore ===
|
|
131
|
+
true;
|
|
132
|
+
|
|
104
133
|
// todo: prettify the code, we want to call `runWithEndpointContext` to top level
|
|
105
134
|
const wrapped: EndpointHandler<Path, Opts, R> = async (ctx) => {
|
|
135
|
+
if (noStore) {
|
|
136
|
+
for (const [name, value] of Object.entries(NO_STORE_HEADERS)) {
|
|
137
|
+
ctx.setHeader(name, value);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
106
140
|
const runtimeCtx = ctx as unknown as { responseHeaders?: Headers };
|
|
107
141
|
try {
|
|
108
142
|
return await runWithEndpointContext(ctx as any, () => handler(ctx));
|
|
@@ -132,6 +166,54 @@ export function createAuthEndpoint<
|
|
|
132
166
|
);
|
|
133
167
|
}
|
|
134
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Set `metadata.SERVER_ONLY` while preserving any existing metadata
|
|
171
|
+
* (`$Infer`, `openapi`, ...).
|
|
172
|
+
*/
|
|
173
|
+
function withServerOnly<Options extends EndpointOptions>(
|
|
174
|
+
options: Options,
|
|
175
|
+
): Options {
|
|
176
|
+
return {
|
|
177
|
+
...options,
|
|
178
|
+
metadata: { ...options.metadata, SERVER_ONLY: true },
|
|
179
|
+
} as Options;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export namespace createAuthEndpoint {
|
|
183
|
+
/**
|
|
184
|
+
* Declare a **server-only** endpoint.
|
|
185
|
+
*
|
|
186
|
+
* The endpoint is callable through `auth.api.*` from trusted server code but is
|
|
187
|
+
* never registered on the HTTP router and never emitted into the OpenAPI
|
|
188
|
+
* schema. It takes no path because it has no URL to be reached at.
|
|
189
|
+
*
|
|
190
|
+
* Prefer this over the path-less `createAuthEndpoint({ ... }, handler)` form.
|
|
191
|
+
* Setting `metadata.SERVER_ONLY` makes the intent explicit at the call site and
|
|
192
|
+
* keeps the endpoint off the HTTP surface even if a path is later added by
|
|
193
|
+
* mistake: better-call's router skips an endpoint when its path is missing *or*
|
|
194
|
+
* when `SERVER_ONLY` is set, so the two together are defense in depth. Relying
|
|
195
|
+
* on path omission alone is invisible and one keystroke away from exposure.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```ts
|
|
199
|
+
* viewBackupCodes: createAuthEndpoint.serverOnly(
|
|
200
|
+
* { method: "POST", body: schema },
|
|
201
|
+
* async (ctx) => { ... },
|
|
202
|
+
* )
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
export function serverOnly<
|
|
206
|
+
Path extends string,
|
|
207
|
+
Options extends EndpointOptions,
|
|
208
|
+
R,
|
|
209
|
+
>(
|
|
210
|
+
options: Options,
|
|
211
|
+
handler: EndpointHandler<Path, Options, R>,
|
|
212
|
+
): StrictEndpoint<Path, Options, R> {
|
|
213
|
+
return createAuthEndpoint(withServerOnly(options), handler);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
135
217
|
export type AuthEndpoint<
|
|
136
218
|
Path extends string,
|
|
137
219
|
Opts extends EndpointOptions,
|