better-auth 1.7.0-beta.7 → 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 +81 -64
- package/dist/api/routes/callback.mjs +35 -30
- package/dist/api/routes/sign-in.d.mts +0 -2
- package/dist/api/routes/sign-in.mjs +13 -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/oauth2/index.d.mts +3 -5
- package/dist/oauth2/index.mjs +3 -5
- package/dist/oauth2/link-account.d.mts +74 -0
- package/dist/oauth2/link-account.mjs +222 -0
- package/dist/oauth2/state.d.mts +0 -11
- package/dist/oauth2/state.mjs +1 -2
- 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 +0 -1
- package/dist/plugins/oauth-popup/index.mjs +2 -2
- package/dist/plugins/oauth-proxy/index.mjs +6 -20
- package/dist/plugins/one-tap/index.mjs +6 -10
- package/dist/state.d.mts +1 -10
- package/dist/state.mjs +1 -2
- package/dist/test-utils/http-test-instance.d.mts +0 -20
- 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 { applyUpdateUserInfoOnLink } from "../../oauth2/resolve-account.mjs";
|
|
6
|
+
import { decryptOAuthToken, getOAuthCallbackPath, setTokenUtil } from "../../oauth2/utils.mjs";
|
|
7
|
+
import { applyUpdateUserInfoOnLink } from "../../oauth2/link-account.mjs";
|
|
10
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,29 +180,24 @@ const linkSocialAccount = createAuthEndpoint("/link-social", {
|
|
|
182
180
|
redirect: false
|
|
183
181
|
});
|
|
184
182
|
}
|
|
185
|
-
const stateNonce = generateRandomString(32);
|
|
186
|
-
const codeVerifier = generateRandomString(128);
|
|
187
183
|
const idTokenNonce = generateIdTokenNonce(provider);
|
|
188
|
-
const
|
|
189
|
-
state: stateNonce,
|
|
190
|
-
codeVerifier,
|
|
191
|
-
idTokenNonce,
|
|
192
|
-
redirectURI: `${c.context.baseURL}${provider.callbackPath}`,
|
|
193
|
-
scopes: c.body.scopes,
|
|
194
|
-
loginHint: c.body.loginHint,
|
|
195
|
-
additionalParams: c.body.additionalParams
|
|
196
|
-
});
|
|
197
|
-
await generateState(c, {
|
|
184
|
+
const state = await generateState(c, {
|
|
198
185
|
link: {
|
|
199
186
|
userId: session.user.id,
|
|
200
187
|
email: session.user.email
|
|
201
188
|
},
|
|
202
189
|
additionalData: c.body.additionalData,
|
|
203
|
-
requestedScopes,
|
|
204
|
-
state: stateNonce,
|
|
205
|
-
codeVerifier,
|
|
206
190
|
idTokenNonce
|
|
207
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
|
|
200
|
+
});
|
|
208
201
|
if (!c.body.disableRedirect) c.setHeader("Location", url.toString());
|
|
209
202
|
return c.json({
|
|
210
203
|
url: url.toString(),
|
|
@@ -276,7 +269,7 @@ async function getValidAccessToken(ctx, { resolvedUserId, providerId, accountId,
|
|
|
276
269
|
const provider = await getAwaitableValue(ctx.context.socialProviders, { value: providerId });
|
|
277
270
|
if (!provider) throw APIError.from("BAD_REQUEST", {
|
|
278
271
|
message: `Provider ${providerId} is not supported.`,
|
|
279
|
-
code:
|
|
272
|
+
code: "PROVIDER_NOT_SUPPORTED"
|
|
280
273
|
});
|
|
281
274
|
let account = resolvedAccount;
|
|
282
275
|
if (!account) {
|
|
@@ -295,12 +288,18 @@ async function getValidAccessToken(ctx, { resolvedUserId, providerId, accountId,
|
|
|
295
288
|
if (account.refreshToken && accessTokenExpired && provider.refreshAccessToken) {
|
|
296
289
|
const refreshToken = await decryptOAuthToken(account.refreshToken, ctx.context);
|
|
297
290
|
newTokens = await provider.refreshAccessToken(refreshToken, ctx);
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
|
304
303
|
});
|
|
305
304
|
}
|
|
306
305
|
const accessTokenExpiresAt = (() => {
|
|
@@ -316,12 +315,14 @@ async function getValidAccessToken(ctx, { resolvedUserId, providerId, accountId,
|
|
|
316
315
|
return {
|
|
317
316
|
accessToken: newTokens?.accessToken ?? await decryptOAuthToken(account.accessToken ?? "", ctx.context),
|
|
318
317
|
accessTokenExpiresAt,
|
|
319
|
-
|
|
318
|
+
scopes: parseStoredScopes(account.scope),
|
|
320
319
|
idToken: newTokens?.idToken ?? account.idToken ?? void 0
|
|
321
320
|
};
|
|
322
|
-
} catch (
|
|
323
|
-
|
|
324
|
-
|
|
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
|
+
});
|
|
325
326
|
}
|
|
326
327
|
}
|
|
327
328
|
const getAccessToken = createAuthEndpoint("/get-access-token", {
|
|
@@ -387,10 +388,6 @@ const refreshToken = createAuthEndpoint("/refresh-token", {
|
|
|
387
388
|
refreshTokenExpiresAt: {
|
|
388
389
|
type: "string",
|
|
389
390
|
format: "date-time"
|
|
390
|
-
},
|
|
391
|
-
grantedScopes: {
|
|
392
|
-
type: "array",
|
|
393
|
-
items: { type: "string" }
|
|
394
391
|
}
|
|
395
392
|
}
|
|
396
393
|
} } }
|
|
@@ -404,11 +401,11 @@ const refreshToken = createAuthEndpoint("/refresh-token", {
|
|
|
404
401
|
const provider = await getAwaitableValue(ctx.context.socialProviders, { value: providerId });
|
|
405
402
|
if (!provider) throw APIError.from("BAD_REQUEST", {
|
|
406
403
|
message: `Provider ${providerId} is not supported.`,
|
|
407
|
-
code:
|
|
404
|
+
code: "PROVIDER_NOT_SUPPORTED"
|
|
408
405
|
});
|
|
409
406
|
if (!provider.refreshAccessToken) throw APIError.from("BAD_REQUEST", {
|
|
410
407
|
message: `Provider ${providerId} does not support token refreshing.`,
|
|
411
|
-
code:
|
|
408
|
+
code: "TOKEN_REFRESH_NOT_SUPPORTED"
|
|
412
409
|
});
|
|
413
410
|
let account = void 0;
|
|
414
411
|
const accountData = await getAccountCookie(ctx);
|
|
@@ -420,30 +417,50 @@ const refreshToken = createAuthEndpoint("/refresh-token", {
|
|
|
420
417
|
else account = (await ctx.context.internalAdapter.findAccounts(resolvedUserId)).find((acc) => accountId ? acc.accountId === accountId && acc.providerId === providerId : acc.providerId === providerId);
|
|
421
418
|
if (!account) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.ACCOUNT_NOT_FOUND);
|
|
422
419
|
const refreshToken = account.refreshToken ?? void 0;
|
|
423
|
-
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
|
+
});
|
|
424
424
|
try {
|
|
425
425
|
const decryptedRefreshToken = await decryptOAuthToken(refreshToken, ctx.context);
|
|
426
426
|
const tokens = await provider.refreshAccessToken(decryptedRefreshToken, ctx);
|
|
427
|
-
await
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
|
433
447
|
});
|
|
448
|
+
const responseScope = updatedAccount?.scope ?? account.scope;
|
|
434
449
|
return ctx.json({
|
|
435
450
|
accessToken: tokens.accessToken,
|
|
436
451
|
refreshToken: tokens.refreshToken ?? decryptedRefreshToken,
|
|
437
452
|
accessTokenExpiresAt: tokens.accessTokenExpiresAt,
|
|
438
|
-
refreshTokenExpiresAt:
|
|
439
|
-
|
|
453
|
+
refreshTokenExpiresAt: resolvedRefreshTokenExpiresAt,
|
|
454
|
+
scope: responseScope,
|
|
440
455
|
idToken: tokens.idToken || account.idToken,
|
|
441
456
|
providerId: account.providerId,
|
|
442
457
|
accountId: account.accountId
|
|
443
458
|
});
|
|
444
|
-
} catch (
|
|
445
|
-
|
|
446
|
-
|
|
459
|
+
} catch (_error) {
|
|
460
|
+
throw APIError.from("BAD_REQUEST", {
|
|
461
|
+
message: "Failed to refresh access token",
|
|
462
|
+
code: "FAILED_TO_REFRESH_ACCESS_TOKEN"
|
|
463
|
+
});
|
|
447
464
|
}
|
|
448
465
|
});
|
|
449
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/
|
|
6
|
+
import { getOAuthCallbackPath, setTokenUtil } from "../../oauth2/utils.mjs";
|
|
7
|
+
import { applyUpdateUserInfoOnLink, handleOAuthUserInfo } from "../../oauth2/link-account.mjs";
|
|
9
8
|
import { generateIdTokenNonce, generateState, parseState } from "../../oauth2/state.mjs";
|
|
10
|
-
import { signInWithOAuthIdentity } from "../../oauth2/sign-in-with-oauth-identity.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,20 +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 state = generateRandomString(32);
|
|
62
|
-
const codeVerifier = generateRandomString(128);
|
|
63
60
|
const idTokenNonce = generateIdTokenNonce(provider);
|
|
64
|
-
const {
|
|
65
|
-
|
|
61
|
+
const { state: freshState, codeVerifier } = await generateState(c, { idTokenNonce });
|
|
62
|
+
const authUrl = await provider.createAuthorizationURL({
|
|
63
|
+
state: freshState,
|
|
66
64
|
codeVerifier,
|
|
67
65
|
idTokenNonce,
|
|
68
|
-
redirectURI: `${c.context.baseURL}${provider
|
|
69
|
-
});
|
|
70
|
-
await generateState(c, {
|
|
71
|
-
requestedScopes,
|
|
72
|
-
state,
|
|
73
|
-
codeVerifier,
|
|
74
|
-
idTokenNonce
|
|
66
|
+
redirectURI: `${c.context.baseURL}${getOAuthCallbackPath(provider)}`
|
|
75
67
|
});
|
|
76
68
|
throw c.redirect(authUrl.toString());
|
|
77
69
|
}
|
|
@@ -81,7 +73,7 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
81
73
|
const url = `${defaultErrorURL}${defaultErrorURL.includes("?") ? "&" : "?"}error=state_not_found`;
|
|
82
74
|
throw c.redirect(url);
|
|
83
75
|
}
|
|
84
|
-
const { codeVerifier, callbackURL, link, errorURL, newUserURL, requestSignUp,
|
|
76
|
+
const { codeVerifier, callbackURL, link, errorURL, newUserURL, requestSignUp, idTokenNonce } = await parseState(c);
|
|
85
77
|
function redirectOnError(error, description) {
|
|
86
78
|
const baseURL = errorURL ?? defaultErrorURL;
|
|
87
79
|
const params = new URLSearchParams({ error });
|
|
@@ -116,7 +108,7 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
116
108
|
code,
|
|
117
109
|
codeVerifier,
|
|
118
110
|
deviceId: device_id,
|
|
119
|
-
redirectURI: `${c.context.baseURL}${provider
|
|
111
|
+
redirectURI: `${c.context.baseURL}${getOAuthCallbackPath(provider)}`
|
|
120
112
|
});
|
|
121
113
|
} catch (e) {
|
|
122
114
|
c.context.logger.error("", e);
|
|
@@ -166,15 +158,26 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
166
158
|
}
|
|
167
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);
|
|
168
160
|
const existingAccount = await c.context.internalAdapter.findAccountByProviderId(providerAccountId, provider.id);
|
|
169
|
-
if (existingAccount
|
|
170
|
-
|
|
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({
|
|
171
174
|
userId: link.userId,
|
|
172
175
|
providerId: provider.id,
|
|
173
176
|
accountId: providerAccountId,
|
|
174
|
-
tokens,
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
177
|
+
...tokens,
|
|
178
|
+
accessToken: await setTokenUtil(tokens.accessToken, c.context),
|
|
179
|
+
refreshToken: await setTokenUtil(tokens.refreshToken, c.context),
|
|
180
|
+
scope: tokens.scopes?.join(",")
|
|
178
181
|
})) return redirectOnError(OAUTH_CALLBACK_ERROR_CODES.UNABLE_TO_LINK_ACCOUNT);
|
|
179
182
|
await applyUpdateUserInfoOnLink(c, link.userId, userInfo);
|
|
180
183
|
let toRedirectTo;
|
|
@@ -189,19 +192,22 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
189
192
|
c.context.logger.error(missingEmailLogMessage(provider.id));
|
|
190
193
|
return redirectOnError(OAUTH_CALLBACK_ERROR_CODES.EMAIL_NOT_FOUND);
|
|
191
194
|
}
|
|
195
|
+
const accountData = {
|
|
196
|
+
providerId: provider.id,
|
|
197
|
+
accountId: providerAccountId,
|
|
198
|
+
...tokens,
|
|
199
|
+
scope: tokens.scopes?.join(",")
|
|
200
|
+
};
|
|
192
201
|
let result;
|
|
193
202
|
try {
|
|
194
|
-
result = await
|
|
203
|
+
result = await handleOAuthUserInfo(c, {
|
|
195
204
|
userInfo: {
|
|
196
205
|
...userInfo,
|
|
197
206
|
id: providerAccountId,
|
|
198
207
|
email: userInfo.email,
|
|
199
208
|
name: userInfo.name || ""
|
|
200
209
|
},
|
|
201
|
-
|
|
202
|
-
accountId: providerAccountId,
|
|
203
|
-
tokens,
|
|
204
|
-
requestedScopes,
|
|
210
|
+
account: accountData,
|
|
205
211
|
callbackURL,
|
|
206
212
|
disableSignUp: provider.disableImplicitSignUp && !requestSignUp || provider.options?.disableSignUp,
|
|
207
213
|
overrideUserInfo: provider.options?.overrideUserInfoOnSignIn,
|
|
@@ -211,8 +217,7 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
|
|
|
211
217
|
providerId: provider.id,
|
|
212
218
|
profile: providerResult.data
|
|
213
219
|
}
|
|
214
|
-
}
|
|
215
|
-
grantAuthority: provider.grantAuthority
|
|
220
|
+
}
|
|
216
221
|
});
|
|
217
222
|
} catch (e) {
|
|
218
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>;
|