better-auth 1.7.0-beta.6 → 1.7.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 +8 -26
- package/dist/api/routes/account.d.mts +4 -11
- package/dist/api/routes/account.mjs +86 -66
- package/dist/api/routes/callback.mjs +43 -30
- package/dist/api/routes/sign-in.d.mts +0 -2
- package/dist/api/routes/sign-in.mjs +16 -21
- package/dist/cookies/session-store.d.mts +1 -1
- package/dist/db/get-migration.mjs +1 -34
- package/dist/db/internal-adapter.mjs +20 -0
- package/dist/db/schema.d.mts +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/oauth2/errors.mjs +1 -0
- package/dist/oauth2/index.d.mts +4 -6
- package/dist/oauth2/index.mjs +4 -6
- package/dist/oauth2/link-account.d.mts +74 -0
- package/dist/oauth2/link-account.mjs +222 -0
- package/dist/oauth2/state.d.mts +14 -12
- package/dist/oauth2/state.mjs +13 -3
- package/dist/oauth2/utils.d.mts +11 -0
- package/dist/oauth2/{token-encryption.mjs → utils.mjs} +6 -2
- package/dist/package.mjs +1 -1
- package/dist/plugins/generic-oauth/index.d.mts +2 -2
- package/dist/plugins/generic-oauth/index.mjs +11 -7
- package/dist/plugins/generic-oauth/types.d.mts +35 -1
- package/dist/plugins/oauth-popup/index.mjs +6 -2
- package/dist/plugins/oauth-proxy/index.mjs +6 -20
- package/dist/plugins/one-tap/index.mjs +6 -10
- package/dist/state.d.mts +5 -8
- package/dist/state.mjs +2 -2
- package/dist/test-utils/http-test-instance.d.mts +0 -20
- package/dist/utils/index.d.mts +1 -1
- package/package.json +8 -8
- package/dist/oauth2/persist-account.d.mts +0 -80
- package/dist/oauth2/persist-account.mjs +0 -84
- package/dist/oauth2/resolve-account.d.mts +0 -126
- package/dist/oauth2/resolve-account.mjs +0 -128
- package/dist/oauth2/sign-in-with-oauth-identity.d.mts +0 -83
- package/dist/oauth2/sign-in-with-oauth-identity.mjs +0 -133
- package/dist/oauth2/token-encryption.d.mts +0 -7
package/dist/api/index.d.mts
CHANGED
|
@@ -100,7 +100,6 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
|
|
|
100
100
|
accessToken: zod.ZodOptional<zod.ZodString>;
|
|
101
101
|
refreshToken: zod.ZodOptional<zod.ZodString>;
|
|
102
102
|
expiresAt: zod.ZodOptional<zod.ZodNumber>;
|
|
103
|
-
scopes: zod.ZodOptional<zod.ZodArray<zod.ZodString>>;
|
|
104
103
|
user: zod.ZodOptional<zod.ZodObject<{
|
|
105
104
|
name: zod.ZodOptional<zod.ZodObject<{
|
|
106
105
|
firstName: zod.ZodOptional<zod.ZodString>;
|
|
@@ -129,7 +128,6 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
|
|
|
129
128
|
accessToken: zod.ZodOptional<zod.ZodString>;
|
|
130
129
|
refreshToken: zod.ZodOptional<zod.ZodString>;
|
|
131
130
|
expiresAt: zod.ZodOptional<zod.ZodNumber>;
|
|
132
|
-
scopes: zod.ZodOptional<zod.ZodArray<zod.ZodString>>;
|
|
133
131
|
user: zod.ZodOptional<zod.ZodObject<{
|
|
134
132
|
name: zod.ZodOptional<zod.ZodObject<{
|
|
135
133
|
firstName: zod.ZodOptional<zod.ZodString>;
|
|
@@ -1584,7 +1582,6 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
|
|
|
1584
1582
|
nonce: zod.ZodOptional<zod.ZodString>;
|
|
1585
1583
|
accessToken: zod.ZodOptional<zod.ZodString>;
|
|
1586
1584
|
refreshToken: zod.ZodOptional<zod.ZodString>;
|
|
1587
|
-
scopes: zod.ZodOptional<zod.ZodArray<zod.ZodString>>;
|
|
1588
1585
|
}, zod_v4_core0.$strip>>;
|
|
1589
1586
|
requestSignUp: zod.ZodOptional<zod.ZodBoolean>;
|
|
1590
1587
|
scopes: zod.ZodOptional<zod.ZodArray<zod.ZodString>>;
|
|
@@ -1712,7 +1709,7 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
|
|
|
1712
1709
|
userId: {
|
|
1713
1710
|
type: string;
|
|
1714
1711
|
};
|
|
1715
|
-
|
|
1712
|
+
scopes: {
|
|
1716
1713
|
type: string;
|
|
1717
1714
|
items: {
|
|
1718
1715
|
type: string;
|
|
@@ -1729,7 +1726,7 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
|
|
|
1729
1726
|
};
|
|
1730
1727
|
};
|
|
1731
1728
|
}, {
|
|
1732
|
-
|
|
1729
|
+
scopes: string[];
|
|
1733
1730
|
id: string;
|
|
1734
1731
|
createdAt: Date;
|
|
1735
1732
|
updatedAt: Date;
|
|
@@ -1869,12 +1866,6 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
|
|
|
1869
1866
|
type: string;
|
|
1870
1867
|
format: string;
|
|
1871
1868
|
};
|
|
1872
|
-
grantedScopes: {
|
|
1873
|
-
type: string;
|
|
1874
|
-
items: {
|
|
1875
|
-
type: string;
|
|
1876
|
-
};
|
|
1877
|
-
};
|
|
1878
1869
|
};
|
|
1879
1870
|
};
|
|
1880
1871
|
};
|
|
@@ -1891,7 +1882,7 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
|
|
|
1891
1882
|
refreshToken: string;
|
|
1892
1883
|
accessTokenExpiresAt: Date | undefined;
|
|
1893
1884
|
refreshTokenExpiresAt: Date | null | undefined;
|
|
1894
|
-
|
|
1885
|
+
scope: string | null | undefined;
|
|
1895
1886
|
idToken: string | null | undefined;
|
|
1896
1887
|
providerId: string;
|
|
1897
1888
|
accountId: string;
|
|
@@ -1941,7 +1932,7 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
|
|
|
1941
1932
|
}, {
|
|
1942
1933
|
accessToken: string;
|
|
1943
1934
|
accessTokenExpiresAt: Date | undefined;
|
|
1944
|
-
|
|
1935
|
+
scopes: string[];
|
|
1945
1936
|
idToken: string | undefined;
|
|
1946
1937
|
}>;
|
|
1947
1938
|
readonly accountInfo: better_call0.StrictEndpoint<"/account-info", {
|
|
@@ -2084,7 +2075,6 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
|
|
|
2084
2075
|
accessToken: zod.ZodOptional<zod.ZodString>;
|
|
2085
2076
|
refreshToken: zod.ZodOptional<zod.ZodString>;
|
|
2086
2077
|
expiresAt: zod.ZodOptional<zod.ZodNumber>;
|
|
2087
|
-
scopes: zod.ZodOptional<zod.ZodArray<zod.ZodString>>;
|
|
2088
2078
|
user: zod.ZodOptional<zod.ZodObject<{
|
|
2089
2079
|
name: zod.ZodOptional<zod.ZodObject<{
|
|
2090
2080
|
firstName: zod.ZodOptional<zod.ZodString>;
|
|
@@ -2113,7 +2103,6 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
|
|
|
2113
2103
|
accessToken: zod.ZodOptional<zod.ZodString>;
|
|
2114
2104
|
refreshToken: zod.ZodOptional<zod.ZodString>;
|
|
2115
2105
|
expiresAt: zod.ZodOptional<zod.ZodNumber>;
|
|
2116
|
-
scopes: zod.ZodOptional<zod.ZodArray<zod.ZodString>>;
|
|
2117
2106
|
user: zod.ZodOptional<zod.ZodObject<{
|
|
2118
2107
|
name: zod.ZodOptional<zod.ZodObject<{
|
|
2119
2108
|
firstName: zod.ZodOptional<zod.ZodString>;
|
|
@@ -3568,7 +3557,6 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
|
|
|
3568
3557
|
nonce: zod.ZodOptional<zod.ZodString>;
|
|
3569
3558
|
accessToken: zod.ZodOptional<zod.ZodString>;
|
|
3570
3559
|
refreshToken: zod.ZodOptional<zod.ZodString>;
|
|
3571
|
-
scopes: zod.ZodOptional<zod.ZodArray<zod.ZodString>>;
|
|
3572
3560
|
}, zod_v4_core0.$strip>>;
|
|
3573
3561
|
requestSignUp: zod.ZodOptional<zod.ZodBoolean>;
|
|
3574
3562
|
scopes: zod.ZodOptional<zod.ZodArray<zod.ZodString>>;
|
|
@@ -3696,7 +3684,7 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
|
|
|
3696
3684
|
userId: {
|
|
3697
3685
|
type: string;
|
|
3698
3686
|
};
|
|
3699
|
-
|
|
3687
|
+
scopes: {
|
|
3700
3688
|
type: string;
|
|
3701
3689
|
items: {
|
|
3702
3690
|
type: string;
|
|
@@ -3713,7 +3701,7 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
|
|
|
3713
3701
|
};
|
|
3714
3702
|
};
|
|
3715
3703
|
}, {
|
|
3716
|
-
|
|
3704
|
+
scopes: string[];
|
|
3717
3705
|
id: string;
|
|
3718
3706
|
createdAt: Date;
|
|
3719
3707
|
updatedAt: Date;
|
|
@@ -3853,12 +3841,6 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
|
|
|
3853
3841
|
type: string;
|
|
3854
3842
|
format: string;
|
|
3855
3843
|
};
|
|
3856
|
-
grantedScopes: {
|
|
3857
|
-
type: string;
|
|
3858
|
-
items: {
|
|
3859
|
-
type: string;
|
|
3860
|
-
};
|
|
3861
|
-
};
|
|
3862
3844
|
};
|
|
3863
3845
|
};
|
|
3864
3846
|
};
|
|
@@ -3875,7 +3857,7 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
|
|
|
3875
3857
|
refreshToken: string;
|
|
3876
3858
|
accessTokenExpiresAt: Date | undefined;
|
|
3877
3859
|
refreshTokenExpiresAt: Date | null | undefined;
|
|
3878
|
-
|
|
3860
|
+
scope: string | null | undefined;
|
|
3879
3861
|
idToken: string | null | undefined;
|
|
3880
3862
|
providerId: string;
|
|
3881
3863
|
accountId: string;
|
|
@@ -3925,7 +3907,7 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
|
|
|
3925
3907
|
}, {
|
|
3926
3908
|
accessToken: string;
|
|
3927
3909
|
accessTokenExpiresAt: Date | undefined;
|
|
3928
|
-
|
|
3910
|
+
scopes: string[];
|
|
3929
3911
|
idToken: string | undefined;
|
|
3930
3912
|
}>;
|
|
3931
3913
|
readonly accountInfo: better_call0.StrictEndpoint<"/account-info", {
|
|
@@ -62,7 +62,7 @@ declare const listUserAccounts: better_call0.StrictEndpoint<"/list-accounts", {
|
|
|
62
62
|
userId: {
|
|
63
63
|
type: string;
|
|
64
64
|
};
|
|
65
|
-
|
|
65
|
+
scopes: {
|
|
66
66
|
type: string;
|
|
67
67
|
items: {
|
|
68
68
|
type: string;
|
|
@@ -79,7 +79,7 @@ declare const listUserAccounts: better_call0.StrictEndpoint<"/list-accounts", {
|
|
|
79
79
|
};
|
|
80
80
|
};
|
|
81
81
|
}, {
|
|
82
|
-
|
|
82
|
+
scopes: string[];
|
|
83
83
|
id: string;
|
|
84
84
|
createdAt: Date;
|
|
85
85
|
updatedAt: Date;
|
|
@@ -98,7 +98,6 @@ declare const linkSocialAccount: better_call0.StrictEndpoint<"/link-social", {
|
|
|
98
98
|
nonce: z.ZodOptional<z.ZodString>;
|
|
99
99
|
accessToken: z.ZodOptional<z.ZodString>;
|
|
100
100
|
refreshToken: z.ZodOptional<z.ZodString>;
|
|
101
|
-
scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
102
101
|
}, z.core.$strip>>;
|
|
103
102
|
requestSignUp: z.ZodOptional<z.ZodBoolean>;
|
|
104
103
|
scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -266,7 +265,7 @@ declare const getAccessToken: better_call0.StrictEndpoint<"/get-access-token", {
|
|
|
266
265
|
}, {
|
|
267
266
|
accessToken: string;
|
|
268
267
|
accessTokenExpiresAt: Date | undefined;
|
|
269
|
-
|
|
268
|
+
scopes: string[];
|
|
270
269
|
idToken: string | undefined;
|
|
271
270
|
}>;
|
|
272
271
|
declare const refreshToken: better_call0.StrictEndpoint<"/refresh-token", {
|
|
@@ -307,12 +306,6 @@ declare const refreshToken: better_call0.StrictEndpoint<"/refresh-token", {
|
|
|
307
306
|
type: string;
|
|
308
307
|
format: string;
|
|
309
308
|
};
|
|
310
|
-
grantedScopes: {
|
|
311
|
-
type: string;
|
|
312
|
-
items: {
|
|
313
|
-
type: string;
|
|
314
|
-
};
|
|
315
|
-
};
|
|
316
309
|
};
|
|
317
310
|
};
|
|
318
311
|
};
|
|
@@ -329,7 +322,7 @@ declare const refreshToken: better_call0.StrictEndpoint<"/refresh-token", {
|
|
|
329
322
|
refreshToken: string;
|
|
330
323
|
accessTokenExpiresAt: Date | undefined;
|
|
331
324
|
refreshTokenExpiresAt: Date | null | undefined;
|
|
332
|
-
|
|
325
|
+
scope: string | null | undefined;
|
|
333
326
|
idToken: string | null | undefined;
|
|
334
327
|
providerId: string;
|
|
335
328
|
accountId: string;
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import { shouldBindAccountCookieToSessionUser } from "../../context/store-capabilities.mjs";
|
|
2
2
|
import { parseAccountOutput } from "../../db/schema.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { getAccountCookie } from "../../cookies/session-store.mjs";
|
|
3
|
+
import { getAccountCookie, setAccountCookie } from "../../cookies/session-store.mjs";
|
|
5
4
|
import { getAwaitableValue } from "../../context/helpers.mjs";
|
|
6
5
|
import { missingEmailLogMessage } from "../../oauth2/errors.mjs";
|
|
7
|
-
import { decryptOAuthToken } from "../../oauth2/
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { generateState } from "../../oauth2/state.mjs";
|
|
6
|
+
import { decryptOAuthToken, getOAuthCallbackPath, setTokenUtil } from "../../oauth2/utils.mjs";
|
|
7
|
+
import { applyUpdateUserInfoOnLink } from "../../oauth2/link-account.mjs";
|
|
8
|
+
import { generateIdTokenNonce, generateState } from "../../oauth2/state.mjs";
|
|
11
9
|
import { freshSessionMiddleware, getSessionFromCtx, isStateful, sessionMiddleware } from "./session.mjs";
|
|
12
10
|
import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
|
|
13
|
-
import { additionalAuthorizationParamsSchema,
|
|
11
|
+
import { additionalAuthorizationParamsSchema, supportsIdTokenSignIn, verifyProviderIdToken } from "@better-auth/core/oauth2";
|
|
14
12
|
import { SocialProviderListEnum } from "@better-auth/core/social-providers";
|
|
15
13
|
import { createAuthEndpoint } from "@better-auth/core/api";
|
|
16
14
|
import * as z from "zod";
|
|
17
15
|
//#region src/api/routes/account.ts
|
|
16
|
+
function parseStoredScopes(scope) {
|
|
17
|
+
if (!scope) return [];
|
|
18
|
+
return scope.split(",").map((s) => s.trim()).filter(Boolean);
|
|
19
|
+
}
|
|
18
20
|
const listUserAccounts = createAuthEndpoint("/list-accounts", {
|
|
19
21
|
method: "GET",
|
|
20
22
|
use: [sessionMiddleware],
|
|
@@ -40,7 +42,7 @@ const listUserAccounts = createAuthEndpoint("/list-accounts", {
|
|
|
40
42
|
},
|
|
41
43
|
accountId: { type: "string" },
|
|
42
44
|
userId: { type: "string" },
|
|
43
|
-
|
|
45
|
+
scopes: {
|
|
44
46
|
type: "array",
|
|
45
47
|
items: { type: "string" }
|
|
46
48
|
}
|
|
@@ -52,7 +54,7 @@ const listUserAccounts = createAuthEndpoint("/list-accounts", {
|
|
|
52
54
|
"updatedAt",
|
|
53
55
|
"accountId",
|
|
54
56
|
"userId",
|
|
55
|
-
"
|
|
57
|
+
"scopes"
|
|
56
58
|
]
|
|
57
59
|
}
|
|
58
60
|
} } }
|
|
@@ -62,10 +64,10 @@ const listUserAccounts = createAuthEndpoint("/list-accounts", {
|
|
|
62
64
|
const session = c.context.session;
|
|
63
65
|
const accounts = await c.context.internalAdapter.findAccounts(session.user.id);
|
|
64
66
|
return c.json(accounts.map((a) => {
|
|
65
|
-
const {
|
|
67
|
+
const { scope, ...parsed } = parseAccountOutput(c.context.options, a);
|
|
66
68
|
return {
|
|
67
69
|
...parsed,
|
|
68
|
-
|
|
70
|
+
scopes: parseStoredScopes(scope)
|
|
69
71
|
};
|
|
70
72
|
}));
|
|
71
73
|
});
|
|
@@ -79,8 +81,7 @@ const linkSocialAccount = createAuthEndpoint("/link-social", {
|
|
|
79
81
|
token: z.string(),
|
|
80
82
|
nonce: z.string().optional(),
|
|
81
83
|
accessToken: z.string().optional(),
|
|
82
|
-
refreshToken: z.string().optional()
|
|
83
|
-
scopes: z.array(z.string()).optional()
|
|
84
|
+
refreshToken: z.string().optional()
|
|
84
85
|
}).optional(),
|
|
85
86
|
requestSignUp: z.boolean().optional(),
|
|
86
87
|
scopes: z.array(z.string()).meta({ description: "Additional scopes to request from the provider" }).optional(),
|
|
@@ -158,18 +159,15 @@ const linkSocialAccount = createAuthEndpoint("/link-social", {
|
|
|
158
159
|
code: "LINKING_DIFFERENT_EMAILS_NOT_ALLOWED"
|
|
159
160
|
});
|
|
160
161
|
try {
|
|
161
|
-
await
|
|
162
|
+
await c.context.internalAdapter.createAccount({
|
|
162
163
|
userId: session.user.id,
|
|
163
164
|
providerId: provider.id,
|
|
164
165
|
accountId: linkingUserId,
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
idToken: token
|
|
169
|
-
},
|
|
170
|
-
mode: "link"
|
|
166
|
+
accessToken: c.body.idToken.accessToken,
|
|
167
|
+
idToken: token,
|
|
168
|
+
refreshToken: c.body.idToken.refreshToken
|
|
171
169
|
});
|
|
172
|
-
} catch
|
|
170
|
+
} catch {
|
|
173
171
|
throw APIError.from("EXPECTATION_FAILED", {
|
|
174
172
|
message: "Account not linked - unable to create account",
|
|
175
173
|
code: "LINKING_FAILED"
|
|
@@ -182,25 +180,23 @@ const linkSocialAccount = createAuthEndpoint("/link-social", {
|
|
|
182
180
|
redirect: false
|
|
183
181
|
});
|
|
184
182
|
}
|
|
185
|
-
const
|
|
186
|
-
const
|
|
187
|
-
const { url, requestedScopes } = await provider.createAuthorizationURL({
|
|
188
|
-
state: stateNonce,
|
|
189
|
-
codeVerifier,
|
|
190
|
-
redirectURI: `${c.context.baseURL}${provider.callbackPath}`,
|
|
191
|
-
scopes: c.body.scopes,
|
|
192
|
-
loginHint: c.body.loginHint,
|
|
193
|
-
additionalParams: c.body.additionalParams
|
|
194
|
-
});
|
|
195
|
-
await generateState(c, {
|
|
183
|
+
const idTokenNonce = generateIdTokenNonce(provider);
|
|
184
|
+
const state = await generateState(c, {
|
|
196
185
|
link: {
|
|
197
186
|
userId: session.user.id,
|
|
198
187
|
email: session.user.email
|
|
199
188
|
},
|
|
200
189
|
additionalData: c.body.additionalData,
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
190
|
+
idTokenNonce
|
|
191
|
+
});
|
|
192
|
+
const url = await provider.createAuthorizationURL({
|
|
193
|
+
state: state.state,
|
|
194
|
+
codeVerifier: state.codeVerifier,
|
|
195
|
+
idTokenNonce,
|
|
196
|
+
redirectURI: `${c.context.baseURL}${getOAuthCallbackPath(provider)}`,
|
|
197
|
+
scopes: c.body.scopes,
|
|
198
|
+
loginHint: c.body.loginHint,
|
|
199
|
+
additionalParams: c.body.additionalParams
|
|
204
200
|
});
|
|
205
201
|
if (!c.body.disableRedirect) c.setHeader("Location", url.toString());
|
|
206
202
|
return c.json({
|
|
@@ -273,7 +269,7 @@ async function getValidAccessToken(ctx, { resolvedUserId, providerId, accountId,
|
|
|
273
269
|
const provider = await getAwaitableValue(ctx.context.socialProviders, { value: providerId });
|
|
274
270
|
if (!provider) throw APIError.from("BAD_REQUEST", {
|
|
275
271
|
message: `Provider ${providerId} is not supported.`,
|
|
276
|
-
code:
|
|
272
|
+
code: "PROVIDER_NOT_SUPPORTED"
|
|
277
273
|
});
|
|
278
274
|
let account = resolvedAccount;
|
|
279
275
|
if (!account) {
|
|
@@ -291,13 +287,19 @@ async function getValidAccessToken(ctx, { resolvedUserId, providerId, accountId,
|
|
|
291
287
|
const accessTokenExpired = account.accessTokenExpiresAt && new Date(account.accessTokenExpiresAt).getTime() - Date.now() < 5e3;
|
|
292
288
|
if (account.refreshToken && accessTokenExpired && provider.refreshAccessToken) {
|
|
293
289
|
const refreshToken = await decryptOAuthToken(account.refreshToken, ctx.context);
|
|
294
|
-
newTokens = await provider.refreshAccessToken(refreshToken);
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
290
|
+
newTokens = await provider.refreshAccessToken(refreshToken, ctx);
|
|
291
|
+
const updatedData = {
|
|
292
|
+
accessToken: await setTokenUtil(newTokens?.accessToken, ctx.context),
|
|
293
|
+
accessTokenExpiresAt: newTokens?.accessTokenExpiresAt,
|
|
294
|
+
refreshToken: newTokens?.refreshToken ? await setTokenUtil(newTokens.refreshToken, ctx.context) : account.refreshToken,
|
|
295
|
+
refreshTokenExpiresAt: newTokens?.refreshTokenExpiresAt ?? account.refreshTokenExpiresAt,
|
|
296
|
+
idToken: newTokens?.idToken || account.idToken
|
|
297
|
+
};
|
|
298
|
+
let updatedAccount = null;
|
|
299
|
+
if (account.id) updatedAccount = await ctx.context.internalAdapter.updateAccount(account.id, updatedData);
|
|
300
|
+
if (ctx.context.options.account?.storeAccountCookie) await setAccountCookie(ctx, {
|
|
301
|
+
...account,
|
|
302
|
+
...updatedAccount ?? updatedData
|
|
301
303
|
});
|
|
302
304
|
}
|
|
303
305
|
const accessTokenExpiresAt = (() => {
|
|
@@ -313,12 +315,14 @@ async function getValidAccessToken(ctx, { resolvedUserId, providerId, accountId,
|
|
|
313
315
|
return {
|
|
314
316
|
accessToken: newTokens?.accessToken ?? await decryptOAuthToken(account.accessToken ?? "", ctx.context),
|
|
315
317
|
accessTokenExpiresAt,
|
|
316
|
-
|
|
318
|
+
scopes: parseStoredScopes(account.scope),
|
|
317
319
|
idToken: newTokens?.idToken ?? account.idToken ?? void 0
|
|
318
320
|
};
|
|
319
|
-
} catch (
|
|
320
|
-
|
|
321
|
-
|
|
321
|
+
} catch (_error) {
|
|
322
|
+
throw APIError.from("BAD_REQUEST", {
|
|
323
|
+
message: "Failed to get a valid access token",
|
|
324
|
+
code: "FAILED_TO_GET_ACCESS_TOKEN"
|
|
325
|
+
});
|
|
322
326
|
}
|
|
323
327
|
}
|
|
324
328
|
const getAccessToken = createAuthEndpoint("/get-access-token", {
|
|
@@ -384,10 +388,6 @@ const refreshToken = createAuthEndpoint("/refresh-token", {
|
|
|
384
388
|
refreshTokenExpiresAt: {
|
|
385
389
|
type: "string",
|
|
386
390
|
format: "date-time"
|
|
387
|
-
},
|
|
388
|
-
grantedScopes: {
|
|
389
|
-
type: "array",
|
|
390
|
-
items: { type: "string" }
|
|
391
391
|
}
|
|
392
392
|
}
|
|
393
393
|
} } }
|
|
@@ -401,11 +401,11 @@ const refreshToken = createAuthEndpoint("/refresh-token", {
|
|
|
401
401
|
const provider = await getAwaitableValue(ctx.context.socialProviders, { value: providerId });
|
|
402
402
|
if (!provider) throw APIError.from("BAD_REQUEST", {
|
|
403
403
|
message: `Provider ${providerId} is not supported.`,
|
|
404
|
-
code:
|
|
404
|
+
code: "PROVIDER_NOT_SUPPORTED"
|
|
405
405
|
});
|
|
406
406
|
if (!provider.refreshAccessToken) throw APIError.from("BAD_REQUEST", {
|
|
407
407
|
message: `Provider ${providerId} does not support token refreshing.`,
|
|
408
|
-
code:
|
|
408
|
+
code: "TOKEN_REFRESH_NOT_SUPPORTED"
|
|
409
409
|
});
|
|
410
410
|
let account = void 0;
|
|
411
411
|
const accountData = await getAccountCookie(ctx);
|
|
@@ -417,30 +417,50 @@ const refreshToken = createAuthEndpoint("/refresh-token", {
|
|
|
417
417
|
else account = (await ctx.context.internalAdapter.findAccounts(resolvedUserId)).find((acc) => accountId ? acc.accountId === accountId && acc.providerId === providerId : acc.providerId === providerId);
|
|
418
418
|
if (!account) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.ACCOUNT_NOT_FOUND);
|
|
419
419
|
const refreshToken = account.refreshToken ?? void 0;
|
|
420
|
-
if (!refreshToken) throw APIError.from("BAD_REQUEST",
|
|
420
|
+
if (!refreshToken) throw APIError.from("BAD_REQUEST", {
|
|
421
|
+
message: "Refresh token not found",
|
|
422
|
+
code: "REFRESH_TOKEN_NOT_FOUND"
|
|
423
|
+
});
|
|
421
424
|
try {
|
|
422
425
|
const decryptedRefreshToken = await decryptOAuthToken(refreshToken, ctx.context);
|
|
423
|
-
const tokens = await provider.refreshAccessToken(decryptedRefreshToken);
|
|
424
|
-
await
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
426
|
+
const tokens = await provider.refreshAccessToken(decryptedRefreshToken, ctx);
|
|
427
|
+
const resolvedRefreshToken = tokens.refreshToken ? await setTokenUtil(tokens.refreshToken, ctx.context) : refreshToken;
|
|
428
|
+
const resolvedRefreshTokenExpiresAt = tokens.refreshTokenExpiresAt ?? account.refreshTokenExpiresAt;
|
|
429
|
+
const updatedTokenData = {
|
|
430
|
+
accessToken: await setTokenUtil(tokens.accessToken, ctx.context),
|
|
431
|
+
refreshToken: resolvedRefreshToken,
|
|
432
|
+
accessTokenExpiresAt: tokens.accessTokenExpiresAt,
|
|
433
|
+
refreshTokenExpiresAt: resolvedRefreshTokenExpiresAt,
|
|
434
|
+
idToken: tokens.idToken || account.idToken
|
|
435
|
+
};
|
|
436
|
+
let updatedAccount = null;
|
|
437
|
+
if (account.id)
|
|
438
|
+
/**
|
|
439
|
+
* `scope` intentionally omitted. Refresh response may be narrower.
|
|
440
|
+
*
|
|
441
|
+
* @see {@link Account.scope}
|
|
442
|
+
*/
|
|
443
|
+
updatedAccount = await ctx.context.internalAdapter.updateAccount(account.id, updatedTokenData);
|
|
444
|
+
if (accountData && providerId === accountData.providerId && ctx.context.options.account?.storeAccountCookie) await setAccountCookie(ctx, {
|
|
445
|
+
...accountData,
|
|
446
|
+
...updatedAccount ?? updatedTokenData
|
|
430
447
|
});
|
|
448
|
+
const responseScope = updatedAccount?.scope ?? account.scope;
|
|
431
449
|
return ctx.json({
|
|
432
450
|
accessToken: tokens.accessToken,
|
|
433
451
|
refreshToken: tokens.refreshToken ?? decryptedRefreshToken,
|
|
434
452
|
accessTokenExpiresAt: tokens.accessTokenExpiresAt,
|
|
435
|
-
refreshTokenExpiresAt:
|
|
436
|
-
|
|
453
|
+
refreshTokenExpiresAt: resolvedRefreshTokenExpiresAt,
|
|
454
|
+
scope: responseScope,
|
|
437
455
|
idToken: tokens.idToken || account.idToken,
|
|
438
456
|
providerId: account.providerId,
|
|
439
457
|
accountId: account.accountId
|
|
440
458
|
});
|
|
441
|
-
} catch (
|
|
442
|
-
|
|
443
|
-
|
|
459
|
+
} catch (_error) {
|
|
460
|
+
throw APIError.from("BAD_REQUEST", {
|
|
461
|
+
message: "Failed to refresh access token",
|
|
462
|
+
code: "FAILED_TO_REFRESH_ACCESS_TOKEN"
|
|
463
|
+
});
|
|
444
464
|
}
|
|
445
465
|
});
|
|
446
466
|
const accountInfoQuerySchema = z.optional(z.object({
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { isAPIError } from "../../utils/is-api-error.mjs";
|
|
2
|
-
import { generateRandomString } from "../../crypto/random.mjs";
|
|
3
2
|
import { setSessionCookie } from "../../cookies/index.mjs";
|
|
4
3
|
import { assertValidUserInfo } from "../../utils/validate-user-info.mjs";
|
|
5
4
|
import { getAwaitableValue } from "../../context/helpers.mjs";
|
|
6
5
|
import { OAUTH_CALLBACK_ERROR_CODES, missingEmailLogMessage } from "../../oauth2/errors.mjs";
|
|
7
|
-
import {
|
|
8
|
-
import { applyUpdateUserInfoOnLink } from "../../oauth2/
|
|
9
|
-
import { generateState, parseState } from "../../oauth2/state.mjs";
|
|
10
|
-
import { signInWithOAuthIdentity } from "../../oauth2/sign-in-with-oauth-identity.mjs";
|
|
6
|
+
import { getOAuthCallbackPath, setTokenUtil } from "../../oauth2/utils.mjs";
|
|
7
|
+
import { applyUpdateUserInfoOnLink, handleOAuthUserInfo } from "../../oauth2/link-account.mjs";
|
|
8
|
+
import { generateIdTokenNonce, generateState, parseState } from "../../oauth2/state.mjs";
|
|
11
9
|
import { HIDE_METADATA } from "../../utils/hide-metadata.mjs";
|
|
10
|
+
import { mergeScopes } from "@better-auth/core/oauth2";
|
|
12
11
|
import { safeJSONParse } from "@better-auth/core/utils/json";
|
|
13
12
|
import { createAuthEndpoint } from "@better-auth/core/api";
|
|
14
13
|
import * as z from "zod";
|
|
@@ -58,17 +57,13 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
58
57
|
if (state === void 0 && code) {
|
|
59
58
|
const provider = await getAwaitableValue(c.context.socialProviders, { value: c.params.id });
|
|
60
59
|
if (provider?.allowIdpInitiated) {
|
|
61
|
-
const
|
|
62
|
-
const codeVerifier =
|
|
63
|
-
const
|
|
64
|
-
state,
|
|
60
|
+
const idTokenNonce = generateIdTokenNonce(provider);
|
|
61
|
+
const { state: freshState, codeVerifier } = await generateState(c, { idTokenNonce });
|
|
62
|
+
const authUrl = await provider.createAuthorizationURL({
|
|
63
|
+
state: freshState,
|
|
65
64
|
codeVerifier,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
await generateState(c, {
|
|
69
|
-
requestedScopes,
|
|
70
|
-
state,
|
|
71
|
-
codeVerifier
|
|
65
|
+
idTokenNonce,
|
|
66
|
+
redirectURI: `${c.context.baseURL}${getOAuthCallbackPath(provider)}`
|
|
72
67
|
});
|
|
73
68
|
throw c.redirect(authUrl.toString());
|
|
74
69
|
}
|
|
@@ -78,7 +73,7 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
78
73
|
const url = `${defaultErrorURL}${defaultErrorURL.includes("?") ? "&" : "?"}error=state_not_found`;
|
|
79
74
|
throw c.redirect(url);
|
|
80
75
|
}
|
|
81
|
-
const { codeVerifier, callbackURL, link, errorURL, newUserURL, requestSignUp,
|
|
76
|
+
const { codeVerifier, callbackURL, link, errorURL, newUserURL, requestSignUp, idTokenNonce } = await parseState(c);
|
|
82
77
|
function redirectOnError(error, description) {
|
|
83
78
|
const baseURL = errorURL ?? defaultErrorURL;
|
|
84
79
|
const params = new URLSearchParams({ error });
|
|
@@ -103,13 +98,17 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
103
98
|
});
|
|
104
99
|
throw redirectOnError(OAUTH_CALLBACK_ERROR_CODES.ISSUER_MISMATCH);
|
|
105
100
|
}
|
|
101
|
+
if (provider.requiresIdTokenNonce && !idTokenNonce) {
|
|
102
|
+
c.context.logger.error("OAuth id_token nonce binding required but no expected nonce was found in state", { providerId: provider.id });
|
|
103
|
+
throw redirectOnError(OAUTH_CALLBACK_ERROR_CODES.NONCE_BINDING_MISSING);
|
|
104
|
+
}
|
|
106
105
|
let tokens;
|
|
107
106
|
try {
|
|
108
107
|
tokens = await provider.validateAuthorizationCode({
|
|
109
108
|
code,
|
|
110
109
|
codeVerifier,
|
|
111
110
|
deviceId: device_id,
|
|
112
|
-
redirectURI: `${c.context.baseURL}${provider
|
|
111
|
+
redirectURI: `${c.context.baseURL}${getOAuthCallbackPath(provider)}`
|
|
113
112
|
});
|
|
114
113
|
} catch (e) {
|
|
115
114
|
c.context.logger.error("", e);
|
|
@@ -119,6 +118,7 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
119
118
|
const parsedUserData = userData ? safeJSONParse(userData) : null;
|
|
120
119
|
const providerResult = await provider.getUserInfo({
|
|
121
120
|
...tokens,
|
|
121
|
+
...idTokenNonce ? { expectedIdTokenNonce: idTokenNonce } : {},
|
|
122
122
|
user: parsedUserData ?? void 0
|
|
123
123
|
});
|
|
124
124
|
if (!providerResult?.user || providerResult.user.id === void 0 || providerResult.user.id === null || providerResult.user.id === "") {
|
|
@@ -158,15 +158,26 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
158
158
|
}
|
|
159
159
|
if (userInfo.email?.toLowerCase() !== link.email.toLowerCase() && c.context.options.account?.accountLinking?.allowDifferentEmails !== true) return redirectOnError(OAUTH_CALLBACK_ERROR_CODES.EMAIL_DOES_NOT_MATCH);
|
|
160
160
|
const existingAccount = await c.context.internalAdapter.findAccountByProviderId(providerAccountId, provider.id);
|
|
161
|
-
if (existingAccount
|
|
162
|
-
|
|
161
|
+
if (existingAccount) {
|
|
162
|
+
if (existingAccount.userId.toString() !== link.userId.toString()) return redirectOnError(OAUTH_CALLBACK_ERROR_CODES.ACCOUNT_ALREADY_LINKED_TO_DIFFERENT_USER);
|
|
163
|
+
const mergedScope = mergeScopes(existingAccount.scope, tokens.scopes);
|
|
164
|
+
const updateData = Object.fromEntries(Object.entries({
|
|
165
|
+
accessToken: await setTokenUtil(tokens.accessToken, c.context),
|
|
166
|
+
refreshToken: await setTokenUtil(tokens.refreshToken, c.context),
|
|
167
|
+
idToken: tokens.idToken,
|
|
168
|
+
accessTokenExpiresAt: tokens.accessTokenExpiresAt,
|
|
169
|
+
refreshTokenExpiresAt: tokens.refreshTokenExpiresAt,
|
|
170
|
+
scope: mergedScope || void 0
|
|
171
|
+
}).filter(([_, value]) => value !== void 0));
|
|
172
|
+
await c.context.internalAdapter.updateAccount(existingAccount.id, updateData);
|
|
173
|
+
} else if (!await c.context.internalAdapter.createAccount({
|
|
163
174
|
userId: link.userId,
|
|
164
175
|
providerId: provider.id,
|
|
165
176
|
accountId: providerAccountId,
|
|
166
|
-
tokens,
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
177
|
+
...tokens,
|
|
178
|
+
accessToken: await setTokenUtil(tokens.accessToken, c.context),
|
|
179
|
+
refreshToken: await setTokenUtil(tokens.refreshToken, c.context),
|
|
180
|
+
scope: tokens.scopes?.join(",")
|
|
170
181
|
})) return redirectOnError(OAUTH_CALLBACK_ERROR_CODES.UNABLE_TO_LINK_ACCOUNT);
|
|
171
182
|
await applyUpdateUserInfoOnLink(c, link.userId, userInfo);
|
|
172
183
|
let toRedirectTo;
|
|
@@ -181,19 +192,22 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
181
192
|
c.context.logger.error(missingEmailLogMessage(provider.id));
|
|
182
193
|
return redirectOnError(OAUTH_CALLBACK_ERROR_CODES.EMAIL_NOT_FOUND);
|
|
183
194
|
}
|
|
195
|
+
const accountData = {
|
|
196
|
+
providerId: provider.id,
|
|
197
|
+
accountId: providerAccountId,
|
|
198
|
+
...tokens,
|
|
199
|
+
scope: tokens.scopes?.join(",")
|
|
200
|
+
};
|
|
184
201
|
let result;
|
|
185
202
|
try {
|
|
186
|
-
result = await
|
|
203
|
+
result = await handleOAuthUserInfo(c, {
|
|
187
204
|
userInfo: {
|
|
188
205
|
...userInfo,
|
|
189
206
|
id: providerAccountId,
|
|
190
207
|
email: userInfo.email,
|
|
191
208
|
name: userInfo.name || ""
|
|
192
209
|
},
|
|
193
|
-
|
|
194
|
-
accountId: providerAccountId,
|
|
195
|
-
tokens,
|
|
196
|
-
requestedScopes,
|
|
210
|
+
account: accountData,
|
|
197
211
|
callbackURL,
|
|
198
212
|
disableSignUp: provider.disableImplicitSignUp && !requestSignUp || provider.options?.disableSignUp,
|
|
199
213
|
overrideUserInfo: provider.options?.overrideUserInfoOnSignIn,
|
|
@@ -203,8 +217,7 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
203
217
|
providerId: provider.id,
|
|
204
218
|
profile: providerResult.data
|
|
205
219
|
}
|
|
206
|
-
}
|
|
207
|
-
grantAuthority: provider.grantAuthority
|
|
220
|
+
}
|
|
208
221
|
});
|
|
209
222
|
} catch (e) {
|
|
210
223
|
if (isAPIError(e) && e.body?.code) redirectOnError(e.body.code, e.body.message);
|
|
@@ -16,7 +16,6 @@ declare const socialSignInBodySchema: z.ZodObject<{
|
|
|
16
16
|
accessToken: z.ZodOptional<z.ZodString>;
|
|
17
17
|
refreshToken: z.ZodOptional<z.ZodString>;
|
|
18
18
|
expiresAt: z.ZodOptional<z.ZodNumber>;
|
|
19
|
-
scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
20
19
|
user: z.ZodOptional<z.ZodObject<{
|
|
21
20
|
name: z.ZodOptional<z.ZodObject<{
|
|
22
21
|
firstName: z.ZodOptional<z.ZodString>;
|
|
@@ -46,7 +45,6 @@ declare const signInSocial: <O extends BetterAuthOptions>() => better_call0.Stri
|
|
|
46
45
|
accessToken: z.ZodOptional<z.ZodString>;
|
|
47
46
|
refreshToken: z.ZodOptional<z.ZodString>;
|
|
48
47
|
expiresAt: z.ZodOptional<z.ZodNumber>;
|
|
49
|
-
scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
50
48
|
user: z.ZodOptional<z.ZodObject<{
|
|
51
49
|
name: z.ZodOptional<z.ZodObject<{
|
|
52
50
|
firstName: z.ZodOptional<z.ZodString>;
|