@supabase/auth-js 2.110.9 → 2.111.0
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/main/GoTrueClient.d.ts +35 -2
- package/dist/main/GoTrueClient.d.ts.map +1 -1
- package/dist/main/GoTrueClient.js +129 -47
- package/dist/main/GoTrueClient.js.map +1 -1
- package/dist/main/lib/constants.d.ts +13 -0
- package/dist/main/lib/constants.d.ts.map +1 -1
- package/dist/main/lib/constants.js +14 -1
- package/dist/main/lib/constants.js.map +1 -1
- package/dist/main/lib/helpers.d.ts +50 -1
- package/dist/main/lib/helpers.d.ts.map +1 -1
- package/dist/main/lib/helpers.js +160 -4
- package/dist/main/lib/helpers.js.map +1 -1
- package/dist/main/lib/types.d.ts +38 -0
- package/dist/main/lib/types.d.ts.map +1 -1
- package/dist/main/lib/types.js.map +1 -1
- package/dist/main/lib/version.d.ts +1 -1
- package/dist/main/lib/version.js +1 -1
- package/dist/module/GoTrueClient.d.ts +35 -2
- package/dist/module/GoTrueClient.d.ts.map +1 -1
- package/dist/module/GoTrueClient.js +131 -49
- package/dist/module/GoTrueClient.js.map +1 -1
- package/dist/module/lib/constants.d.ts +13 -0
- package/dist/module/lib/constants.d.ts.map +1 -1
- package/dist/module/lib/constants.js +13 -0
- package/dist/module/lib/constants.js.map +1 -1
- package/dist/module/lib/helpers.d.ts +50 -1
- package/dist/module/lib/helpers.d.ts.map +1 -1
- package/dist/module/lib/helpers.js +152 -4
- package/dist/module/lib/helpers.js.map +1 -1
- package/dist/module/lib/types.d.ts +38 -0
- package/dist/module/lib/types.d.ts.map +1 -1
- package/dist/module/lib/types.js.map +1 -1
- package/dist/module/lib/version.d.ts +1 -1
- package/dist/module/lib/version.js +1 -1
- package/dist/tsconfig.module.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/GoTrueClient.ts +169 -67
- package/src/lib/constants.ts +15 -0
- package/src/lib/helpers.ts +197 -5
- package/src/lib/types.ts +38 -0
- package/src/lib/version.ts +1 -1
package/package.json
CHANGED
package/src/GoTrueClient.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
EXPIRY_MARGIN_MS,
|
|
7
7
|
GOTRUE_URL,
|
|
8
8
|
JWKS_TTL,
|
|
9
|
+
PKCE_FLOW_ID_PARAM,
|
|
9
10
|
REFRESH_FAILURE_COOLDOWN_MS,
|
|
10
11
|
STORAGE_KEY,
|
|
11
12
|
} from './lib/constants'
|
|
@@ -36,6 +37,7 @@ import {
|
|
|
36
37
|
_userResponse,
|
|
37
38
|
} from './lib/fetch'
|
|
38
39
|
import {
|
|
40
|
+
appendFlowIdToRedirectTo,
|
|
39
41
|
assertPasskeyExperimentalEnabled,
|
|
40
42
|
decodeJWT,
|
|
41
43
|
deepClone,
|
|
@@ -47,14 +49,19 @@ import {
|
|
|
47
49
|
insecureUserWarningProxy,
|
|
48
50
|
isBrowser,
|
|
49
51
|
parseParametersFromURL,
|
|
52
|
+
pkceVerifierSlotKey,
|
|
53
|
+
removeAllPKCEVerifiers,
|
|
50
54
|
removeItemAsync,
|
|
55
|
+
removePKCEVerifier,
|
|
51
56
|
resolveFetch,
|
|
57
|
+
retrievePKCEVerifier,
|
|
52
58
|
retryable,
|
|
53
59
|
setItemAsync,
|
|
54
60
|
sleep,
|
|
55
61
|
supportsLocalStorage,
|
|
56
62
|
userNotAvailableProxy,
|
|
57
63
|
validateExp,
|
|
64
|
+
validatePKCEFlowId,
|
|
58
65
|
} from './lib/helpers'
|
|
59
66
|
import { memoryLocalStorageAdapter } from './lib/local-storage'
|
|
60
67
|
import { LockAcquireTimeoutError, navigatorLock } from './lib/locks'
|
|
@@ -1005,6 +1012,7 @@ export default class GoTrueClient {
|
|
|
1005
1012
|
* ```
|
|
1006
1013
|
*/
|
|
1007
1014
|
async signUp(credentials: SignUpWithPasswordCredentials): Promise<AuthResponse> {
|
|
1015
|
+
let flowId: string | null = null
|
|
1008
1016
|
try {
|
|
1009
1017
|
let res: AuthResponse
|
|
1010
1018
|
if ('email' in credentials) {
|
|
@@ -1012,14 +1020,11 @@ export default class GoTrueClient {
|
|
|
1012
1020
|
let codeChallenge: string | null = null
|
|
1013
1021
|
let codeChallengeMethod: string | null = null
|
|
1014
1022
|
if (this.flowType === 'pkce') {
|
|
1015
|
-
;[codeChallenge, codeChallengeMethod] = await
|
|
1016
|
-
this.storage,
|
|
1017
|
-
this.storageKey
|
|
1018
|
-
)
|
|
1023
|
+
;[codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod()
|
|
1019
1024
|
}
|
|
1020
1025
|
res = await _request(this.fetch, 'POST', `${this.url}/signup`, {
|
|
1021
1026
|
headers: this.headers,
|
|
1022
|
-
redirectTo: options?.emailRedirectTo,
|
|
1027
|
+
redirectTo: this._maybeAppendFlowIdToRedirect(options?.emailRedirectTo, flowId),
|
|
1023
1028
|
body: {
|
|
1024
1029
|
email,
|
|
1025
1030
|
password,
|
|
@@ -1052,7 +1057,7 @@ export default class GoTrueClient {
|
|
|
1052
1057
|
const { data, error } = res
|
|
1053
1058
|
|
|
1054
1059
|
if (error || !data) {
|
|
1055
|
-
await
|
|
1060
|
+
await removePKCEVerifier(this.storage, this.storageKey, flowId)
|
|
1056
1061
|
return this._returnResult({ data: { user: null, session: null }, error: error })
|
|
1057
1062
|
}
|
|
1058
1063
|
|
|
@@ -1066,7 +1071,7 @@ export default class GoTrueClient {
|
|
|
1066
1071
|
|
|
1067
1072
|
return this._returnResult({ data: { user, session }, error: null })
|
|
1068
1073
|
} catch (error) {
|
|
1069
|
-
await
|
|
1074
|
+
await removePKCEVerifier(this.storage, this.storageKey, flowId)
|
|
1070
1075
|
if (isAuthError(error)) {
|
|
1071
1076
|
return this._returnResult({ data: { user: null, session: null }, error })
|
|
1072
1077
|
}
|
|
@@ -1289,7 +1294,8 @@ export default class GoTrueClient {
|
|
|
1289
1294
|
* {
|
|
1290
1295
|
* data: {
|
|
1291
1296
|
* provider: 'github',
|
|
1292
|
-
* url: <PROVIDER_URL_TO_REDIRECT_TO
|
|
1297
|
+
* url: <PROVIDER_URL_TO_REDIRECT_TO>,
|
|
1298
|
+
* flowId: <PKCE_FLOW_ID_OR_NULL>
|
|
1293
1299
|
* },
|
|
1294
1300
|
* error: null
|
|
1295
1301
|
* }
|
|
@@ -1362,12 +1368,29 @@ export default class GoTrueClient {
|
|
|
1362
1368
|
*
|
|
1363
1369
|
* @remarks
|
|
1364
1370
|
* - Used when `flowType` is set to `pkce` in client options.
|
|
1371
|
+
* - When several PKCE flows are in flight at once, pass `options.flowId` so
|
|
1372
|
+
* the code is exchanged with the verifier created by that specific flow.
|
|
1373
|
+
* The flow id is returned by `signInWithOAuth`, and with
|
|
1374
|
+
* `experimental.appendPkceFlowIdToRedirects` enabled it also arrives on
|
|
1375
|
+
* your callback URL as the reserved `sb_flow_id` query parameter (read
|
|
1376
|
+
* automatically in a browser).
|
|
1377
|
+
* - When a flow id is present but its stored verifier is gone (evicted,
|
|
1378
|
+
* already used, or from another device), the call fails with a verifier
|
|
1379
|
+
* missing error instead of trying another flow's verifier — a mismatched
|
|
1380
|
+
* verifier would consume the single-use code. Without any flow id the
|
|
1381
|
+
* most recently stored verifier is used, as before.
|
|
1365
1382
|
*
|
|
1366
1383
|
* @example Exchange Auth Code
|
|
1367
1384
|
* ```js
|
|
1368
1385
|
* supabase.auth.exchangeCodeForSession('34e770dd-9ff9-416c-87fa-43b31d7ef225')
|
|
1369
1386
|
* ```
|
|
1370
1387
|
*
|
|
1388
|
+
* @example Exchange Auth Code for a specific flow (e.g. in a server-side callback handler)
|
|
1389
|
+
* ```js
|
|
1390
|
+
* const flowId = requestUrl.searchParams.get('sb_flow_id')
|
|
1391
|
+
* supabase.auth.exchangeCodeForSession(code, flowId ? { flowId } : undefined)
|
|
1392
|
+
* ```
|
|
1393
|
+
*
|
|
1371
1394
|
* @exampleResponse Exchange Auth Code
|
|
1372
1395
|
* ```json
|
|
1373
1396
|
* {
|
|
@@ -1525,17 +1548,20 @@ export default class GoTrueClient {
|
|
|
1525
1548
|
* }
|
|
1526
1549
|
* ```
|
|
1527
1550
|
*/
|
|
1528
|
-
async exchangeCodeForSession(
|
|
1551
|
+
async exchangeCodeForSession(
|
|
1552
|
+
authCode: string,
|
|
1553
|
+
options?: { flowId?: string }
|
|
1554
|
+
): Promise<AuthTokenResponse> {
|
|
1529
1555
|
await this.initializePromise
|
|
1530
1556
|
|
|
1531
1557
|
if (this.lock != null) {
|
|
1532
1558
|
// TODO(v3): remove legacy lock path
|
|
1533
1559
|
return this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
1534
|
-
return this._exchangeCodeForSession(authCode)
|
|
1560
|
+
return this._exchangeCodeForSession(authCode, options)
|
|
1535
1561
|
})
|
|
1536
1562
|
}
|
|
1537
1563
|
|
|
1538
|
-
return this._exchangeCodeForSession(authCode)
|
|
1564
|
+
return this._exchangeCodeForSession(authCode, options)
|
|
1539
1565
|
}
|
|
1540
1566
|
|
|
1541
1567
|
/**
|
|
@@ -1968,15 +1994,40 @@ export default class GoTrueClient {
|
|
|
1968
1994
|
}
|
|
1969
1995
|
}
|
|
1970
1996
|
|
|
1971
|
-
private async _exchangeCodeForSession(
|
|
1997
|
+
private async _exchangeCodeForSession(
|
|
1998
|
+
authCode: string,
|
|
1999
|
+
options?: { flowId?: string }
|
|
2000
|
+
): Promise<
|
|
1972
2001
|
| {
|
|
1973
2002
|
data: { session: Session; user: User; redirectType: string | null }
|
|
1974
2003
|
error: null
|
|
1975
2004
|
}
|
|
1976
2005
|
| { data: { session: null; user: null; redirectType: null }; error: AuthError }
|
|
1977
2006
|
> {
|
|
1978
|
-
const
|
|
1979
|
-
const
|
|
2007
|
+
const hasExplicitFlowId = options?.flowId != null
|
|
2008
|
+
const requestedFlowId = hasExplicitFlowId
|
|
2009
|
+
? validatePKCEFlowId(options?.flowId)
|
|
2010
|
+
: isBrowser()
|
|
2011
|
+
? validatePKCEFlowId(parseParametersFromURL(window.location.href)[PKCE_FLOW_ID_PARAM])
|
|
2012
|
+
: null
|
|
2013
|
+
|
|
2014
|
+
if (hasExplicitFlowId && !requestedFlowId) {
|
|
2015
|
+
this._debug(
|
|
2016
|
+
'#_exchangeCodeForSession()',
|
|
2017
|
+
'provided flowId is not a valid flow id',
|
|
2018
|
+
options?.flowId
|
|
2019
|
+
)
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
// With a flow id (explicit or from the callback URL) the lookup is
|
|
2023
|
+
// slot-only and a miss fails fast — see retrievePKCEVerifier. An invalid
|
|
2024
|
+
// explicit flow id also fails fast rather than borrowing another flow's
|
|
2025
|
+
// verifier.
|
|
2026
|
+
const { verifier: storageItem, flowId } =
|
|
2027
|
+
hasExplicitFlowId && !requestedFlowId
|
|
2028
|
+
? { verifier: null, flowId: null }
|
|
2029
|
+
: await retrievePKCEVerifier(this.storage, this.storageKey, requestedFlowId)
|
|
2030
|
+
const [codeVerifier, redirectType] = (storageItem ?? '').split('/')
|
|
1980
2031
|
|
|
1981
2032
|
try {
|
|
1982
2033
|
if (!codeVerifier && this.flowType === 'pkce') {
|
|
@@ -1996,7 +2047,7 @@ export default class GoTrueClient {
|
|
|
1996
2047
|
xform: _sessionResponse,
|
|
1997
2048
|
}
|
|
1998
2049
|
)
|
|
1999
|
-
await
|
|
2050
|
+
await removePKCEVerifier(this.storage, this.storageKey, flowId)
|
|
2000
2051
|
if (error) {
|
|
2001
2052
|
throw error
|
|
2002
2053
|
}
|
|
@@ -2016,7 +2067,7 @@ export default class GoTrueClient {
|
|
|
2016
2067
|
}
|
|
2017
2068
|
return this._returnResult({ data: { ...data, redirectType: redirectType ?? null }, error })
|
|
2018
2069
|
} catch (error) {
|
|
2019
|
-
await
|
|
2070
|
+
await removePKCEVerifier(this.storage, this.storageKey, flowId)
|
|
2020
2071
|
if (isAuthError(error)) {
|
|
2021
2072
|
return this._returnResult({
|
|
2022
2073
|
data: { user: null, session: null, redirectType: null },
|
|
@@ -2216,16 +2267,14 @@ export default class GoTrueClient {
|
|
|
2216
2267
|
* ```
|
|
2217
2268
|
*/
|
|
2218
2269
|
async signInWithOtp(credentials: SignInWithPasswordlessCredentials): Promise<AuthOtpResponse> {
|
|
2270
|
+
let flowId: string | null = null
|
|
2219
2271
|
try {
|
|
2220
2272
|
if ('email' in credentials) {
|
|
2221
2273
|
const { email, options } = credentials
|
|
2222
2274
|
let codeChallenge: string | null = null
|
|
2223
2275
|
let codeChallengeMethod: string | null = null
|
|
2224
2276
|
if (this.flowType === 'pkce') {
|
|
2225
|
-
;[codeChallenge, codeChallengeMethod] = await
|
|
2226
|
-
this.storage,
|
|
2227
|
-
this.storageKey
|
|
2228
|
-
)
|
|
2277
|
+
;[codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod()
|
|
2229
2278
|
}
|
|
2230
2279
|
const { error } = await _request(this.fetch, 'POST', `${this.url}/otp`, {
|
|
2231
2280
|
headers: this.headers,
|
|
@@ -2237,7 +2286,7 @@ export default class GoTrueClient {
|
|
|
2237
2286
|
code_challenge: codeChallenge,
|
|
2238
2287
|
code_challenge_method: codeChallengeMethod,
|
|
2239
2288
|
},
|
|
2240
|
-
redirectTo: options?.emailRedirectTo,
|
|
2289
|
+
redirectTo: this._maybeAppendFlowIdToRedirect(options?.emailRedirectTo, flowId),
|
|
2241
2290
|
})
|
|
2242
2291
|
return this._returnResult({ data: { user: null, session: null }, error })
|
|
2243
2292
|
}
|
|
@@ -2260,7 +2309,7 @@ export default class GoTrueClient {
|
|
|
2260
2309
|
}
|
|
2261
2310
|
throw new AuthInvalidCredentialsError('You must provide either an email or phone number.')
|
|
2262
2311
|
} catch (error) {
|
|
2263
|
-
await
|
|
2312
|
+
await removePKCEVerifier(this.storage, this.storageKey, flowId)
|
|
2264
2313
|
if (isAuthError(error)) {
|
|
2265
2314
|
return this._returnResult({ data: { user: null, session: null }, error })
|
|
2266
2315
|
}
|
|
@@ -2507,21 +2556,19 @@ export default class GoTrueClient {
|
|
|
2507
2556
|
* ```
|
|
2508
2557
|
*/
|
|
2509
2558
|
async signInWithSSO(params: SignInWithSSO): Promise<SSOResponse> {
|
|
2559
|
+
let flowId: string | null = null
|
|
2510
2560
|
try {
|
|
2511
2561
|
let codeChallenge: string | null = null
|
|
2512
2562
|
let codeChallengeMethod: string | null = null
|
|
2513
2563
|
if (this.flowType === 'pkce') {
|
|
2514
|
-
;[codeChallenge, codeChallengeMethod] = await
|
|
2515
|
-
this.storage,
|
|
2516
|
-
this.storageKey
|
|
2517
|
-
)
|
|
2564
|
+
;[codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod()
|
|
2518
2565
|
}
|
|
2519
2566
|
|
|
2520
2567
|
const result = await _request(this.fetch, 'POST', `${this.url}/sso`, {
|
|
2521
2568
|
body: {
|
|
2522
2569
|
...('providerId' in params ? { provider_id: params.providerId } : null),
|
|
2523
2570
|
...('domain' in params ? { domain: params.domain } : null),
|
|
2524
|
-
redirect_to: params.options?.redirectTo
|
|
2571
|
+
redirect_to: this._maybeAppendFlowIdToRedirect(params.options?.redirectTo, flowId),
|
|
2525
2572
|
...(params?.options?.captchaToken
|
|
2526
2573
|
? { gotrue_meta_security: { captcha_token: params.options.captchaToken } }
|
|
2527
2574
|
: null),
|
|
@@ -2540,7 +2587,7 @@ export default class GoTrueClient {
|
|
|
2540
2587
|
|
|
2541
2588
|
return this._returnResult(result)
|
|
2542
2589
|
} catch (error) {
|
|
2543
|
-
await
|
|
2590
|
+
await removePKCEVerifier(this.storage, this.storageKey, flowId)
|
|
2544
2591
|
if (isAuthError(error)) {
|
|
2545
2592
|
return this._returnResult({ data: null, error })
|
|
2546
2593
|
}
|
|
@@ -2666,6 +2713,7 @@ export default class GoTrueClient {
|
|
|
2666
2713
|
* ```
|
|
2667
2714
|
*/
|
|
2668
2715
|
async resend(credentials: ResendParams): Promise<AuthOtpResponse> {
|
|
2716
|
+
let flowId: string | null = null
|
|
2669
2717
|
try {
|
|
2670
2718
|
const endpoint = `${this.url}/resend`
|
|
2671
2719
|
if ('email' in credentials) {
|
|
@@ -2673,10 +2721,7 @@ export default class GoTrueClient {
|
|
|
2673
2721
|
let codeChallenge: string | null = null
|
|
2674
2722
|
let codeChallengeMethod: string | null = null
|
|
2675
2723
|
if (this.flowType === 'pkce') {
|
|
2676
|
-
;[codeChallenge, codeChallengeMethod] = await
|
|
2677
|
-
this.storage,
|
|
2678
|
-
this.storageKey
|
|
2679
|
-
)
|
|
2724
|
+
;[codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod()
|
|
2680
2725
|
}
|
|
2681
2726
|
const { error } = await _request(this.fetch, 'POST', endpoint, {
|
|
2682
2727
|
headers: this.headers,
|
|
@@ -2687,10 +2732,10 @@ export default class GoTrueClient {
|
|
|
2687
2732
|
code_challenge: codeChallenge,
|
|
2688
2733
|
code_challenge_method: codeChallengeMethod,
|
|
2689
2734
|
},
|
|
2690
|
-
redirectTo: options?.emailRedirectTo,
|
|
2735
|
+
redirectTo: this._maybeAppendFlowIdToRedirect(options?.emailRedirectTo, flowId),
|
|
2691
2736
|
})
|
|
2692
2737
|
if (error) {
|
|
2693
|
-
await
|
|
2738
|
+
await removePKCEVerifier(this.storage, this.storageKey, flowId)
|
|
2694
2739
|
}
|
|
2695
2740
|
return this._returnResult({ data: { user: null, session: null }, error })
|
|
2696
2741
|
} else if ('phone' in credentials) {
|
|
@@ -2712,7 +2757,7 @@ export default class GoTrueClient {
|
|
|
2712
2757
|
'You must provide either an email or phone number and a type'
|
|
2713
2758
|
)
|
|
2714
2759
|
} catch (error) {
|
|
2715
|
-
await
|
|
2760
|
+
await removePKCEVerifier(this.storage, this.storageKey, flowId)
|
|
2716
2761
|
if (isAuthError(error)) {
|
|
2717
2762
|
return this._returnResult({ data: { user: null, session: null }, error })
|
|
2718
2763
|
}
|
|
@@ -3207,7 +3252,6 @@ export default class GoTrueClient {
|
|
|
3207
3252
|
// session in the database, indicating the user is signed out.
|
|
3208
3253
|
|
|
3209
3254
|
await this._removeSession()
|
|
3210
|
-
await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`)
|
|
3211
3255
|
}
|
|
3212
3256
|
|
|
3213
3257
|
return this._returnResult({ data: { user: null }, error })
|
|
@@ -3355,6 +3399,7 @@ export default class GoTrueClient {
|
|
|
3355
3399
|
emailRedirectTo?: string | undefined
|
|
3356
3400
|
} = {}
|
|
3357
3401
|
): Promise<UserResponse> {
|
|
3402
|
+
let flowId: string | null = null
|
|
3358
3403
|
try {
|
|
3359
3404
|
return await this._useSession(async (result) => {
|
|
3360
3405
|
const { data: sessionData, error: sessionError } = result
|
|
@@ -3368,15 +3413,12 @@ export default class GoTrueClient {
|
|
|
3368
3413
|
let codeChallenge: string | null = null
|
|
3369
3414
|
let codeChallengeMethod: string | null = null
|
|
3370
3415
|
if (this.flowType === 'pkce' && attributes.email != null) {
|
|
3371
|
-
;[codeChallenge, codeChallengeMethod] = await
|
|
3372
|
-
this.storage,
|
|
3373
|
-
this.storageKey
|
|
3374
|
-
)
|
|
3416
|
+
;[codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod()
|
|
3375
3417
|
}
|
|
3376
3418
|
|
|
3377
3419
|
const { data, error: userError } = await _request(this.fetch, 'PUT', `${this.url}/user`, {
|
|
3378
3420
|
headers: this.headers,
|
|
3379
|
-
redirectTo: options?.emailRedirectTo,
|
|
3421
|
+
redirectTo: this._maybeAppendFlowIdToRedirect(options?.emailRedirectTo, flowId),
|
|
3380
3422
|
body: {
|
|
3381
3423
|
...attributes,
|
|
3382
3424
|
code_challenge: codeChallenge,
|
|
@@ -3394,7 +3436,7 @@ export default class GoTrueClient {
|
|
|
3394
3436
|
return this._returnResult({ data: { user: session.user }, error: null })
|
|
3395
3437
|
})
|
|
3396
3438
|
} catch (error) {
|
|
3397
|
-
await
|
|
3439
|
+
await removePKCEVerifier(this.storage, this.storageKey, flowId)
|
|
3398
3440
|
if (isAuthError(error)) {
|
|
3399
3441
|
return this._returnResult({ data: { user: null }, error })
|
|
3400
3442
|
}
|
|
@@ -3826,11 +3868,14 @@ export default class GoTrueClient {
|
|
|
3826
3868
|
if (callbackUrlType === 'pkce') {
|
|
3827
3869
|
this._debug('#_initialize()', 'begin', 'is PKCE flow', true)
|
|
3828
3870
|
if (!params.code) throw new AuthPKCEGrantCodeExchangeError('No code detected.')
|
|
3829
|
-
const { data, error } = await this._exchangeCodeForSession(params.code
|
|
3871
|
+
const { data, error } = await this._exchangeCodeForSession(params.code, {
|
|
3872
|
+
flowId: params[PKCE_FLOW_ID_PARAM],
|
|
3873
|
+
})
|
|
3830
3874
|
if (error) throw error
|
|
3831
3875
|
|
|
3832
3876
|
const url = new URL(window.location.href)
|
|
3833
3877
|
url.searchParams.delete('code')
|
|
3878
|
+
url.searchParams.delete(PKCE_FLOW_ID_PARAM)
|
|
3834
3879
|
|
|
3835
3880
|
window.history.replaceState(window.history.state, '', url.toString())
|
|
3836
3881
|
|
|
@@ -3934,12 +3979,24 @@ export default class GoTrueClient {
|
|
|
3934
3979
|
* Checks if the current URL and backing storage contain parameters given by a PKCE flow
|
|
3935
3980
|
*/
|
|
3936
3981
|
private async _isPKCECallback(params: { [parameter: string]: string }): Promise<boolean> {
|
|
3982
|
+
if (!params.code) {
|
|
3983
|
+
return false
|
|
3984
|
+
}
|
|
3985
|
+
|
|
3986
|
+
const flowId = validatePKCEFlowId(params[PKCE_FLOW_ID_PARAM])
|
|
3987
|
+
if (
|
|
3988
|
+
flowId &&
|
|
3989
|
+
(await getItemAsync(this.storage, pkceVerifierSlotKey(this.storageKey, flowId)))
|
|
3990
|
+
) {
|
|
3991
|
+
return true
|
|
3992
|
+
}
|
|
3993
|
+
|
|
3937
3994
|
const currentStorageContent = await getItemAsync(
|
|
3938
3995
|
this.storage,
|
|
3939
3996
|
`${this.storageKey}-code-verifier`
|
|
3940
3997
|
)
|
|
3941
3998
|
|
|
3942
|
-
return !!
|
|
3999
|
+
return !!currentStorageContent
|
|
3943
4000
|
}
|
|
3944
4001
|
|
|
3945
4002
|
/**
|
|
@@ -4002,7 +4059,6 @@ export default class GoTrueClient {
|
|
|
4002
4059
|
return await this._useSession(async (result) => {
|
|
4003
4060
|
const removeCurrentSession = async () => {
|
|
4004
4061
|
await this._removeSession()
|
|
4005
|
-
await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`)
|
|
4006
4062
|
}
|
|
4007
4063
|
const { data, error: sessionError } = result
|
|
4008
4064
|
if (sessionError && !isAuthSessionMissingError(sessionError)) {
|
|
@@ -4392,11 +4448,10 @@ export default class GoTrueClient {
|
|
|
4392
4448
|
> {
|
|
4393
4449
|
let codeChallenge: string | null = null
|
|
4394
4450
|
let codeChallengeMethod: string | null = null
|
|
4451
|
+
let flowId: string | null = null
|
|
4395
4452
|
|
|
4396
4453
|
if (this.flowType === 'pkce') {
|
|
4397
|
-
;[codeChallenge, codeChallengeMethod] = await
|
|
4398
|
-
this.storage,
|
|
4399
|
-
this.storageKey,
|
|
4454
|
+
;[codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod(
|
|
4400
4455
|
true // isPasswordRecovery
|
|
4401
4456
|
)
|
|
4402
4457
|
}
|
|
@@ -4409,10 +4464,10 @@ export default class GoTrueClient {
|
|
|
4409
4464
|
gotrue_meta_security: { captcha_token: options.captchaToken },
|
|
4410
4465
|
},
|
|
4411
4466
|
headers: this.headers,
|
|
4412
|
-
redirectTo: options.redirectTo,
|
|
4467
|
+
redirectTo: this._maybeAppendFlowIdToRedirect(options.redirectTo, flowId),
|
|
4413
4468
|
})
|
|
4414
4469
|
} catch (error) {
|
|
4415
|
-
await
|
|
4470
|
+
await removePKCEVerifier(this.storage, this.storageKey, flowId)
|
|
4416
4471
|
if (isAuthError(error)) {
|
|
4417
4472
|
return this._returnResult({ data: null, error })
|
|
4418
4473
|
}
|
|
@@ -4514,7 +4569,8 @@ export default class GoTrueClient {
|
|
|
4514
4569
|
* {
|
|
4515
4570
|
* data: {
|
|
4516
4571
|
* provider: 'github',
|
|
4517
|
-
* url: <PROVIDER_URL_TO_REDIRECT_TO
|
|
4572
|
+
* url: <PROVIDER_URL_TO_REDIRECT_TO>,
|
|
4573
|
+
* flowId: <PKCE_FLOW_ID_OR_NULL>
|
|
4518
4574
|
* },
|
|
4519
4575
|
* error: null
|
|
4520
4576
|
* }
|
|
@@ -4529,11 +4585,12 @@ export default class GoTrueClient {
|
|
|
4529
4585
|
}
|
|
4530
4586
|
|
|
4531
4587
|
private async linkIdentityOAuth(credentials: SignInWithOAuthCredentials): Promise<OAuthResponse> {
|
|
4588
|
+
let flowId: string | null = null
|
|
4532
4589
|
try {
|
|
4533
4590
|
const { data, error } = await this._useSession(async (result) => {
|
|
4534
4591
|
const { data, error } = result
|
|
4535
4592
|
if (error) throw error
|
|
4536
|
-
const url:
|
|
4593
|
+
const { url, flowId: urlFlowId } = await this._getUrlForProvider(
|
|
4537
4594
|
`${this.url}/user/identities/authorize`,
|
|
4538
4595
|
credentials.provider,
|
|
4539
4596
|
{
|
|
@@ -4543,6 +4600,7 @@ export default class GoTrueClient {
|
|
|
4543
4600
|
skipBrowserRedirect: true,
|
|
4544
4601
|
}
|
|
4545
4602
|
)
|
|
4603
|
+
flowId = urlFlowId
|
|
4546
4604
|
return await _request(this.fetch, 'GET', url, {
|
|
4547
4605
|
headers: this.headers,
|
|
4548
4606
|
jwt: data.session?.access_token ?? undefined,
|
|
@@ -4553,12 +4611,15 @@ export default class GoTrueClient {
|
|
|
4553
4611
|
window.location.assign(data?.url)
|
|
4554
4612
|
}
|
|
4555
4613
|
return this._returnResult({
|
|
4556
|
-
data: { provider: credentials.provider, url: data?.url },
|
|
4614
|
+
data: { provider: credentials.provider, url: data?.url, flowId },
|
|
4557
4615
|
error: null,
|
|
4558
4616
|
})
|
|
4559
4617
|
} catch (error) {
|
|
4560
4618
|
if (isAuthError(error)) {
|
|
4561
|
-
return this._returnResult({
|
|
4619
|
+
return this._returnResult({
|
|
4620
|
+
data: { provider: credentials.provider, url: null, flowId },
|
|
4621
|
+
error,
|
|
4622
|
+
})
|
|
4562
4623
|
}
|
|
4563
4624
|
throw error
|
|
4564
4625
|
}
|
|
@@ -4606,7 +4667,7 @@ export default class GoTrueClient {
|
|
|
4606
4667
|
}
|
|
4607
4668
|
return this._returnResult({ data, error })
|
|
4608
4669
|
} catch (error) {
|
|
4609
|
-
await
|
|
4670
|
+
await removePKCEVerifier(this.storage, this.storageKey, null)
|
|
4610
4671
|
if (isAuthError(error)) {
|
|
4611
4672
|
return this._returnResult({ data: { user: null, session: null }, error })
|
|
4612
4673
|
}
|
|
@@ -4742,7 +4803,7 @@ export default class GoTrueClient {
|
|
|
4742
4803
|
skipBrowserRedirect?: boolean
|
|
4743
4804
|
}
|
|
4744
4805
|
) {
|
|
4745
|
-
const url
|
|
4806
|
+
const { url, flowId } = await this._getUrlForProvider(`${this.url}/authorize`, provider, {
|
|
4746
4807
|
redirectTo: options.redirectTo,
|
|
4747
4808
|
scopes: options.scopes,
|
|
4748
4809
|
queryParams: options.queryParams,
|
|
@@ -4755,7 +4816,7 @@ export default class GoTrueClient {
|
|
|
4755
4816
|
window.location.assign(url)
|
|
4756
4817
|
}
|
|
4757
4818
|
|
|
4758
|
-
return { data: { provider, url }, error: null }
|
|
4819
|
+
return { data: { provider, url, flowId }, error: null }
|
|
4759
4820
|
}
|
|
4760
4821
|
|
|
4761
4822
|
/**
|
|
@@ -5176,7 +5237,7 @@ export default class GoTrueClient {
|
|
|
5176
5237
|
this.suppressGetSessionWarning = false
|
|
5177
5238
|
|
|
5178
5239
|
await removeItemAsync(this.storage, this.storageKey)
|
|
5179
|
-
await
|
|
5240
|
+
await removeAllPKCEVerifiers(this.storage, this.storageKey)
|
|
5180
5241
|
await removeItemAsync(this.storage, this.storageKey + '-user')
|
|
5181
5242
|
|
|
5182
5243
|
if (this.userStorage) {
|
|
@@ -5600,19 +5661,23 @@ export default class GoTrueClient {
|
|
|
5600
5661
|
skipBrowserRedirect?: boolean
|
|
5601
5662
|
}
|
|
5602
5663
|
) {
|
|
5664
|
+
let redirectTo = options?.redirectTo
|
|
5665
|
+
let codeChallenge: string | null = null
|
|
5666
|
+
let codeChallengeMethod: string | null = null
|
|
5667
|
+
let flowId: string | null = null
|
|
5668
|
+
if (this.flowType === 'pkce') {
|
|
5669
|
+
;[codeChallenge, codeChallengeMethod, flowId] = await this._getCodeChallengeAndMethod()
|
|
5670
|
+
redirectTo = this._maybeAppendFlowIdToRedirect(redirectTo, flowId)
|
|
5671
|
+
}
|
|
5672
|
+
|
|
5603
5673
|
const urlParams: string[] = [`provider=${encodeURIComponent(provider)}`]
|
|
5604
|
-
if (
|
|
5605
|
-
urlParams.push(`redirect_to=${encodeURIComponent(
|
|
5674
|
+
if (redirectTo) {
|
|
5675
|
+
urlParams.push(`redirect_to=${encodeURIComponent(redirectTo)}`)
|
|
5606
5676
|
}
|
|
5607
5677
|
if (options?.scopes) {
|
|
5608
5678
|
urlParams.push(`scopes=${encodeURIComponent(options.scopes)}`)
|
|
5609
5679
|
}
|
|
5610
|
-
if (
|
|
5611
|
-
const [codeChallenge, codeChallengeMethod] = await getCodeChallengeAndMethod(
|
|
5612
|
-
this.storage,
|
|
5613
|
-
this.storageKey
|
|
5614
|
-
)
|
|
5615
|
-
|
|
5680
|
+
if (codeChallenge != null && codeChallengeMethod != null) {
|
|
5616
5681
|
const flowParams = new URLSearchParams({
|
|
5617
5682
|
code_challenge: `${encodeURIComponent(codeChallenge)}`,
|
|
5618
5683
|
code_challenge_method: `${encodeURIComponent(codeChallengeMethod)}`,
|
|
@@ -5627,7 +5692,44 @@ export default class GoTrueClient {
|
|
|
5627
5692
|
urlParams.push(`skip_http_redirect=${options.skipBrowserRedirect}`)
|
|
5628
5693
|
}
|
|
5629
5694
|
|
|
5630
|
-
return `${url}?${urlParams.join('&')}
|
|
5695
|
+
return { url: `${url}?${urlParams.join('&')}`, flowId }
|
|
5696
|
+
}
|
|
5697
|
+
|
|
5698
|
+
/**
|
|
5699
|
+
* Appends the reserved flow id parameter to a redirect URL so the callback
|
|
5700
|
+
* can be matched to the verifier stored for its flow. Opt-in via
|
|
5701
|
+
* `experimental.appendPkceFlowIdToRedirects`: redirect URLs are validated
|
|
5702
|
+
* against the project's allow list including the query string, so an extra
|
|
5703
|
+
* parameter can stop exact (non-wildcard) entries from matching.
|
|
5704
|
+
*/
|
|
5705
|
+
private _maybeAppendFlowIdToRedirect(
|
|
5706
|
+
redirectTo: string | undefined,
|
|
5707
|
+
flowId: string | null
|
|
5708
|
+
): string | undefined {
|
|
5709
|
+
if (!redirectTo || !flowId || !this.experimental.appendPkceFlowIdToRedirects) {
|
|
5710
|
+
return redirectTo ?? undefined
|
|
5711
|
+
}
|
|
5712
|
+
return appendFlowIdToRedirectTo(redirectTo, flowId)
|
|
5713
|
+
}
|
|
5714
|
+
|
|
5715
|
+
/**
|
|
5716
|
+
* Generates and stores a PKCE challenge/verifier pair for a new flow,
|
|
5717
|
+
* logging any pending verifier the bounded slot ring evicts.
|
|
5718
|
+
*/
|
|
5719
|
+
private async _getCodeChallengeAndMethod(
|
|
5720
|
+
isPasswordRecovery = false
|
|
5721
|
+
): Promise<[string, string, string]> {
|
|
5722
|
+
return getCodeChallengeAndMethod(
|
|
5723
|
+
this.storage,
|
|
5724
|
+
this.storageKey,
|
|
5725
|
+
isPasswordRecovery,
|
|
5726
|
+
(evictedFlowId) =>
|
|
5727
|
+
this._debug(
|
|
5728
|
+
'#_getCodeChallengeAndMethod()',
|
|
5729
|
+
'evicted oldest pending PKCE verifier slot',
|
|
5730
|
+
evictedFlowId
|
|
5731
|
+
)
|
|
5732
|
+
)
|
|
5631
5733
|
}
|
|
5632
5734
|
|
|
5633
5735
|
private async _unenroll(params: MFAUnenrollParams): Promise<AuthMFAUnenrollResponse> {
|
package/src/lib/constants.ts
CHANGED
|
@@ -40,4 +40,19 @@ export const API_VERSIONS = {
|
|
|
40
40
|
|
|
41
41
|
export const BASE64URL_REGEX = /^([a-z0-9_-]{4})*($|[a-z0-9_-]{3}$|[a-z0-9_-]{2}$)$/i
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Reserved query parameter appended to `redirectTo` URLs (behind
|
|
45
|
+
* `experimental.appendPkceFlowIdToRedirects`) that correlates a PKCE callback
|
|
46
|
+
* with the code verifier stored when its flow started. It round-trips through
|
|
47
|
+
* the auth server untouched and identifies a verifier slot in storage — the
|
|
48
|
+
* verifier itself never appears in a URL.
|
|
49
|
+
*/
|
|
50
|
+
export const PKCE_FLOW_ID_PARAM = 'sb_flow_id'
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Maximum number of PKCE code verifiers kept in storage at once. Starting a
|
|
54
|
+
* new flow beyond this evicts the oldest pending verifier.
|
|
55
|
+
*/
|
|
56
|
+
export const PKCE_MAX_CONCURRENT_FLOWS = 5
|
|
57
|
+
|
|
43
58
|
export const JWKS_TTL = 10 * 60 * 1000 // 10 minutes
|