@supabase/gotrue-js 3.0.0-next.2 → 3.0.0-next.21
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/GoTrueAdminApi.d.ts +28 -4
- package/dist/main/GoTrueAdminApi.d.ts.map +1 -1
- package/dist/main/GoTrueAdminApi.js +50 -3
- package/dist/main/GoTrueAdminApi.js.map +1 -1
- package/dist/main/GoTrueClient.d.ts +80 -7
- package/dist/main/GoTrueClient.d.ts.map +1 -1
- package/dist/main/GoTrueClient.js +387 -35
- package/dist/main/GoTrueClient.js.map +1 -1
- package/dist/main/lib/fetch.d.ts +28 -8
- package/dist/main/lib/fetch.d.ts.map +1 -1
- package/dist/main/lib/fetch.js +15 -2
- package/dist/main/lib/fetch.js.map +1 -1
- package/dist/main/lib/helpers.d.ts +4 -1
- package/dist/main/lib/helpers.d.ts.map +1 -1
- package/dist/main/lib/helpers.js +9 -3
- package/dist/main/lib/helpers.js.map +1 -1
- package/dist/main/lib/locks.d.ts.map +1 -1
- package/dist/main/lib/locks.js +8 -3
- package/dist/main/lib/locks.js.map +1 -1
- package/dist/main/lib/types.d.ts +138 -11
- 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.d.ts.map +1 -1
- package/dist/main/lib/version.js +1 -1
- package/dist/main/lib/version.js.map +1 -1
- package/dist/main/lib/webauthn.d.ts +8 -0
- package/dist/main/lib/webauthn.d.ts.map +1 -1
- package/dist/main/lib/webauthn.dom.d.ts +3 -3
- package/dist/main/lib/webauthn.dom.d.ts.map +1 -1
- package/dist/main/lib/webauthn.errors.d.ts +5 -0
- package/dist/main/lib/webauthn.errors.d.ts.map +1 -1
- package/dist/main/lib/webauthn.errors.js +7 -0
- package/dist/main/lib/webauthn.errors.js.map +1 -1
- package/dist/main/lib/webauthn.js +1 -0
- package/dist/main/lib/webauthn.js.map +1 -1
- package/dist/module/GoTrueAdminApi.d.ts +28 -4
- package/dist/module/GoTrueAdminApi.d.ts.map +1 -1
- package/dist/module/GoTrueAdminApi.js +51 -4
- package/dist/module/GoTrueAdminApi.js.map +1 -1
- package/dist/module/GoTrueClient.d.ts +80 -7
- package/dist/module/GoTrueClient.d.ts.map +1 -1
- package/dist/module/GoTrueClient.js +389 -37
- package/dist/module/GoTrueClient.js.map +1 -1
- package/dist/module/lib/fetch.d.ts +28 -8
- package/dist/module/lib/fetch.d.ts.map +1 -1
- package/dist/module/lib/fetch.js +15 -2
- package/dist/module/lib/fetch.js.map +1 -1
- package/dist/module/lib/helpers.d.ts +4 -1
- package/dist/module/lib/helpers.d.ts.map +1 -1
- package/dist/module/lib/helpers.js +8 -3
- package/dist/module/lib/helpers.js.map +1 -1
- package/dist/module/lib/locks.d.ts.map +1 -1
- package/dist/module/lib/locks.js +8 -3
- package/dist/module/lib/locks.js.map +1 -1
- package/dist/module/lib/types.d.ts +138 -11
- 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.d.ts.map +1 -1
- package/dist/module/lib/version.js +1 -1
- package/dist/module/lib/version.js.map +1 -1
- package/dist/module/lib/webauthn.d.ts +8 -0
- package/dist/module/lib/webauthn.d.ts.map +1 -1
- package/dist/module/lib/webauthn.dom.d.ts +3 -3
- package/dist/module/lib/webauthn.dom.d.ts.map +1 -1
- package/dist/module/lib/webauthn.errors.d.ts +5 -0
- package/dist/module/lib/webauthn.errors.d.ts.map +1 -1
- package/dist/module/lib/webauthn.errors.js +7 -0
- package/dist/module/lib/webauthn.errors.js.map +1 -1
- package/dist/module/lib/webauthn.js +1 -1
- package/dist/module/lib/webauthn.js.map +1 -1
- package/dist/tsconfig.module.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/GoTrueAdminApi.ts +82 -3
- package/src/GoTrueClient.ts +462 -15
- package/src/lib/fetch.ts +53 -19
- package/src/lib/helpers.ts +13 -5
- package/src/lib/locks.ts +14 -7
- package/src/lib/types.ts +195 -10
- package/src/lib/version.ts +1 -1
- package/src/lib/webauthn.dom.ts +3 -3
- package/src/lib/webauthn.errors.ts +12 -0
- package/src/lib/webauthn.ts +1 -1
package/src/GoTrueClient.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
_userResponse,
|
|
34
34
|
} from './lib/fetch'
|
|
35
35
|
import {
|
|
36
|
+
assertPasskeyExperimentalEnabled,
|
|
36
37
|
decodeJWT,
|
|
37
38
|
deepClone,
|
|
38
39
|
Deferred,
|
|
@@ -136,6 +137,22 @@ import type {
|
|
|
136
137
|
UserResponse,
|
|
137
138
|
VerifyOtpParams,
|
|
138
139
|
Web3Credentials,
|
|
140
|
+
AuthPasskeyApi,
|
|
141
|
+
ExperimentalFeatureFlags,
|
|
142
|
+
SignInWithPasskeyCredentials,
|
|
143
|
+
RegisterPasskeyCredentials,
|
|
144
|
+
VerifyPasskeyRegistrationParams,
|
|
145
|
+
StartPasskeyAuthenticationParams,
|
|
146
|
+
VerifyPasskeyAuthenticationParams,
|
|
147
|
+
PasskeyUpdateParams,
|
|
148
|
+
PasskeyDeleteParams,
|
|
149
|
+
AuthPasskeyRegistrationOptionsResponse,
|
|
150
|
+
AuthPasskeyRegistrationVerifyResponse,
|
|
151
|
+
AuthPasskeyAuthenticationOptionsResponse,
|
|
152
|
+
AuthPasskeyAuthenticationVerifyResponse,
|
|
153
|
+
AuthPasskeyListResponse,
|
|
154
|
+
AuthPasskeyUpdateResponse,
|
|
155
|
+
AuthPasskeyDeleteResponse,
|
|
139
156
|
} from './lib/types'
|
|
140
157
|
import {
|
|
141
158
|
createSiweMessage,
|
|
@@ -146,10 +163,14 @@ import {
|
|
|
146
163
|
toHex,
|
|
147
164
|
} from './lib/web3/ethereum'
|
|
148
165
|
import {
|
|
166
|
+
createCredential,
|
|
149
167
|
deserializeCredentialCreationOptions,
|
|
150
168
|
deserializeCredentialRequestOptions,
|
|
169
|
+
getCredential,
|
|
151
170
|
serializeCredentialCreationResponse,
|
|
152
171
|
serializeCredentialRequestResponse,
|
|
172
|
+
browserSupportsWebAuthn,
|
|
173
|
+
webAuthnAbortService,
|
|
153
174
|
WebAuthnApi,
|
|
154
175
|
} from './lib/webauthn'
|
|
155
176
|
import {
|
|
@@ -176,6 +197,7 @@ const DEFAULT_OPTIONS: Omit<
|
|
|
176
197
|
throwOnError: false,
|
|
177
198
|
lockAcquireTimeout: 5000, // 5 seconds
|
|
178
199
|
skipAutoInitialize: false,
|
|
200
|
+
experimental: {},
|
|
179
201
|
}
|
|
180
202
|
|
|
181
203
|
async function lockNoOp<R>(name: string, acquireTimeout: number, fn: () => Promise<R>): Promise<R> {
|
|
@@ -212,6 +234,13 @@ export default class GoTrueClient {
|
|
|
212
234
|
* Used to implement the authorization code flow on the consent page.
|
|
213
235
|
*/
|
|
214
236
|
oauth: AuthOAuthServerApi
|
|
237
|
+
/**
|
|
238
|
+
* Namespace for passkey methods.
|
|
239
|
+
* Includes lower-level two-step registration/authentication and passkey management.
|
|
240
|
+
*
|
|
241
|
+
* Requires `auth.experimental.passkey: true`; otherwise all methods throw.
|
|
242
|
+
*/
|
|
243
|
+
passkey: AuthPasskeyApi
|
|
215
244
|
/**
|
|
216
245
|
* The storage key used to identify the values saved in localStorage
|
|
217
246
|
*/
|
|
@@ -273,6 +302,11 @@ export default class GoTrueClient {
|
|
|
273
302
|
protected pendingInLock: Promise<any>[] = []
|
|
274
303
|
protected throwOnError: boolean
|
|
275
304
|
protected lockAcquireTimeout: number
|
|
305
|
+
/**
|
|
306
|
+
* Opt-in flags for experimental features. Defaults to an empty object.
|
|
307
|
+
* See `GoTrueClientOptions.experimental`.
|
|
308
|
+
*/
|
|
309
|
+
protected experimental: ExperimentalFeatureFlags
|
|
276
310
|
|
|
277
311
|
/**
|
|
278
312
|
* Used to broadcast state change events to other tabs listening.
|
|
@@ -289,7 +323,7 @@ export default class GoTrueClient {
|
|
|
289
323
|
* ```ts
|
|
290
324
|
* import { createClient } from '@supabase/supabase-js'
|
|
291
325
|
*
|
|
292
|
-
* const supabase = createClient('https://xyzcompany.supabase.co', 'publishable-
|
|
326
|
+
* const supabase = createClient('https://xyzcompany.supabase.co', 'your-publishable-key')
|
|
293
327
|
* const { data, error } = await supabase.auth.getUser()
|
|
294
328
|
* ```
|
|
295
329
|
*
|
|
@@ -299,7 +333,7 @@ export default class GoTrueClient {
|
|
|
299
333
|
*
|
|
300
334
|
* const auth = new GoTrueClient({
|
|
301
335
|
* url: 'https://xyzcompany.supabase.co/auth/v1',
|
|
302
|
-
* headers: { apikey: 'publishable-
|
|
336
|
+
* headers: { apikey: 'your-publishable-key' },
|
|
303
337
|
* storageKey: 'supabase-auth',
|
|
304
338
|
* })
|
|
305
339
|
* ```
|
|
@@ -326,10 +360,12 @@ export default class GoTrueClient {
|
|
|
326
360
|
|
|
327
361
|
this.persistSession = settings.persistSession
|
|
328
362
|
this.autoRefreshToken = settings.autoRefreshToken
|
|
363
|
+
this.experimental = settings.experimental ?? {}
|
|
329
364
|
this.admin = new GoTrueAdminApi({
|
|
330
365
|
url: settings.url,
|
|
331
366
|
headers: settings.headers,
|
|
332
367
|
fetch: settings.fetch,
|
|
368
|
+
experimental: this.experimental,
|
|
333
369
|
})
|
|
334
370
|
|
|
335
371
|
this.url = settings.url
|
|
@@ -374,6 +410,16 @@ export default class GoTrueClient {
|
|
|
374
410
|
revokeGrant: this._revokeOAuthGrant.bind(this),
|
|
375
411
|
}
|
|
376
412
|
|
|
413
|
+
this.passkey = {
|
|
414
|
+
startRegistration: this._startPasskeyRegistration.bind(this),
|
|
415
|
+
verifyRegistration: this._verifyPasskeyRegistration.bind(this),
|
|
416
|
+
startAuthentication: this._startPasskeyAuthentication.bind(this),
|
|
417
|
+
verifyAuthentication: this._verifyPasskeyAuthentication.bind(this),
|
|
418
|
+
list: this._listPasskeys.bind(this),
|
|
419
|
+
update: this._updatePasskey.bind(this),
|
|
420
|
+
delete: this._deletePasskey.bind(this),
|
|
421
|
+
}
|
|
422
|
+
|
|
377
423
|
if (this.persistSession) {
|
|
378
424
|
if (settings.storage) {
|
|
379
425
|
this.storage = settings.storage
|
|
@@ -397,7 +443,7 @@ export default class GoTrueClient {
|
|
|
397
443
|
if (isBrowser() && globalThis.BroadcastChannel && this.persistSession && this.storageKey) {
|
|
398
444
|
try {
|
|
399
445
|
this.broadcastChannel = new globalThis.BroadcastChannel(this.storageKey)
|
|
400
|
-
} catch (e
|
|
446
|
+
} catch (e) {
|
|
401
447
|
console.error(
|
|
402
448
|
'Failed to create a new BroadcastChannel, multi-tab state changes will not be available',
|
|
403
449
|
e
|
|
@@ -1835,7 +1881,10 @@ export default class GoTrueClient {
|
|
|
1835
1881
|
}
|
|
1836
1882
|
if (data.session) {
|
|
1837
1883
|
await this._saveSession(data.session)
|
|
1838
|
-
await this._notifyAllSubscribers(
|
|
1884
|
+
await this._notifyAllSubscribers(
|
|
1885
|
+
redirectType === 'recovery' ? 'PASSWORD_RECOVERY' : 'SIGNED_IN',
|
|
1886
|
+
data.session
|
|
1887
|
+
)
|
|
1839
1888
|
}
|
|
1840
1889
|
return this._returnResult({ data: { ...data, redirectType: redirectType ?? null }, error })
|
|
1841
1890
|
} catch (error) {
|
|
@@ -2642,7 +2691,7 @@ export default class GoTrueClient {
|
|
|
2642
2691
|
(async () => {
|
|
2643
2692
|
try {
|
|
2644
2693
|
await result
|
|
2645
|
-
} catch (
|
|
2694
|
+
} catch (_e) {
|
|
2646
2695
|
// we just care if it finished
|
|
2647
2696
|
}
|
|
2648
2697
|
})()
|
|
@@ -3584,7 +3633,10 @@ export default class GoTrueClient {
|
|
|
3584
3633
|
|
|
3585
3634
|
window.history.replaceState(window.history.state, '', url.toString())
|
|
3586
3635
|
|
|
3587
|
-
return {
|
|
3636
|
+
return {
|
|
3637
|
+
data: { session: data.session, redirectType: data.redirectType ?? null },
|
|
3638
|
+
error: null,
|
|
3639
|
+
}
|
|
3588
3640
|
}
|
|
3589
3641
|
|
|
3590
3642
|
const {
|
|
@@ -3695,24 +3747,35 @@ export default class GoTrueClient {
|
|
|
3695
3747
|
*
|
|
3696
3748
|
* If using `others` scope, no `SIGNED_OUT` event is fired!
|
|
3697
3749
|
*
|
|
3750
|
+
* **Warning:** the default `scope` is `'global'`. This signs the user out of
|
|
3751
|
+
* **every device they are currently signed in on**, not just the current
|
|
3752
|
+
* tab/session. If you only want to sign the user out of the current session
|
|
3753
|
+
* (the behavior most other auth libraries default to), pass
|
|
3754
|
+
* `{ scope: 'local' }` explicitly.
|
|
3755
|
+
*
|
|
3698
3756
|
* @category Auth
|
|
3699
3757
|
*
|
|
3700
3758
|
* @remarks
|
|
3701
3759
|
* - In order to use the `signOut()` method, the user needs to be signed in first.
|
|
3702
|
-
* - By default, `signOut()` uses the global scope, which signs out
|
|
3760
|
+
* - By default, `signOut()` uses the **global** scope, which signs out the user
|
|
3761
|
+
* on every device they are signed in on (not just the current one). Pass
|
|
3762
|
+
* `{ scope: 'local' }` to only sign out the current session. This is
|
|
3763
|
+
* usually what apps want on a "Sign out" button, especially when users
|
|
3764
|
+
* sign in from multiple devices and do not expect signing out of one to
|
|
3765
|
+
* terminate the others.
|
|
3703
3766
|
* - Since Supabase Auth uses JWTs for authentication, the access token JWT will be valid until it's expired. When the user signs out, Supabase revokes the refresh token and deletes the JWT from the client-side. This does not revoke the JWT and it will still be valid until it expires.
|
|
3704
3767
|
*
|
|
3705
|
-
* @example Sign out (
|
|
3768
|
+
* @example Sign out of every device (global – default)
|
|
3706
3769
|
* ```js
|
|
3707
3770
|
* const { error } = await supabase.auth.signOut()
|
|
3708
3771
|
* ```
|
|
3709
3772
|
*
|
|
3710
|
-
* @example Sign out
|
|
3773
|
+
* @example Sign out only the current session (recommended for most apps)
|
|
3711
3774
|
* ```js
|
|
3712
3775
|
* const { error } = await supabase.auth.signOut({ scope: 'local' })
|
|
3713
3776
|
* ```
|
|
3714
3777
|
*
|
|
3715
|
-
* @example Sign out
|
|
3778
|
+
* @example Sign out of all other sessions, keep the current one
|
|
3716
3779
|
* ```js
|
|
3717
3780
|
* const { error } = await supabase.auth.signOut({ scope: 'others' })
|
|
3718
3781
|
* ```
|
|
@@ -4659,11 +4722,11 @@ export default class GoTrueClient {
|
|
|
4659
4722
|
this.broadcastChannel.postMessage({ event, session })
|
|
4660
4723
|
}
|
|
4661
4724
|
|
|
4662
|
-
const errors:
|
|
4725
|
+
const errors: unknown[] = []
|
|
4663
4726
|
const promises = Array.from(this.stateChangeEmitters.values()).map(async (x) => {
|
|
4664
4727
|
try {
|
|
4665
4728
|
await x.callback(event, session)
|
|
4666
|
-
} catch (e
|
|
4729
|
+
} catch (e) {
|
|
4667
4730
|
errors.push(e)
|
|
4668
4731
|
}
|
|
4669
4732
|
})
|
|
@@ -4951,7 +5014,7 @@ export default class GoTrueClient {
|
|
|
4951
5014
|
await this._callRefreshToken(session.refresh_token)
|
|
4952
5015
|
}
|
|
4953
5016
|
})
|
|
4954
|
-
} catch (e
|
|
5017
|
+
} catch (e) {
|
|
4955
5018
|
console.error(
|
|
4956
5019
|
'Auto refresh tick failed with error. This is likely a transient error.',
|
|
4957
5020
|
e
|
|
@@ -4961,8 +5024,8 @@ export default class GoTrueClient {
|
|
|
4961
5024
|
this._debug('#_autoRefreshTokenTick()', 'end')
|
|
4962
5025
|
}
|
|
4963
5026
|
})
|
|
4964
|
-
} catch (e
|
|
4965
|
-
if (e
|
|
5027
|
+
} catch (e) {
|
|
5028
|
+
if (e instanceof LockAcquireTimeoutError) {
|
|
4966
5029
|
this._debug('auto refresh token tick lock not available')
|
|
4967
5030
|
} else {
|
|
4968
5031
|
throw e
|
|
@@ -5906,4 +5969,388 @@ export default class GoTrueClient {
|
|
|
5906
5969
|
throw error
|
|
5907
5970
|
}
|
|
5908
5971
|
}
|
|
5972
|
+
|
|
5973
|
+
// --- Passkey Methods ---
|
|
5974
|
+
|
|
5975
|
+
/**
|
|
5976
|
+
* Sign in with a passkey. Handles the full WebAuthn ceremony:
|
|
5977
|
+
* 1. Fetches authentication challenge from server
|
|
5978
|
+
* 2. Prompts user via navigator.credentials.get()
|
|
5979
|
+
* 3. Verifies credential with server and creates session
|
|
5980
|
+
*
|
|
5981
|
+
* Requires `auth.experimental.passkey: true`.
|
|
5982
|
+
*/
|
|
5983
|
+
async signInWithPasskey(
|
|
5984
|
+
credentials?: SignInWithPasskeyCredentials
|
|
5985
|
+
): Promise<AuthPasskeyAuthenticationVerifyResponse> {
|
|
5986
|
+
assertPasskeyExperimentalEnabled(this.experimental)
|
|
5987
|
+
try {
|
|
5988
|
+
if (!browserSupportsWebAuthn()) {
|
|
5989
|
+
return this._returnResult({
|
|
5990
|
+
data: null,
|
|
5991
|
+
error: new AuthUnknownError('Browser does not support WebAuthn', null),
|
|
5992
|
+
})
|
|
5993
|
+
}
|
|
5994
|
+
|
|
5995
|
+
// 1. Get challenge options from server
|
|
5996
|
+
const { data: options, error: optionsError } = await this._startPasskeyAuthentication({
|
|
5997
|
+
options: { captchaToken: credentials?.options?.captchaToken },
|
|
5998
|
+
})
|
|
5999
|
+
if (optionsError || !options) {
|
|
6000
|
+
return this._returnResult({ data: null, error: optionsError })
|
|
6001
|
+
}
|
|
6002
|
+
|
|
6003
|
+
// 2. Deserialize and prompt user via browser WebAuthn API
|
|
6004
|
+
const publicKeyOptions = deserializeCredentialRequestOptions(options.options)
|
|
6005
|
+
const signal = credentials?.options?.signal ?? webAuthnAbortService.createNewAbortSignal()
|
|
6006
|
+
const { data: credential, error: credentialError } = await getCredential({
|
|
6007
|
+
publicKey: publicKeyOptions,
|
|
6008
|
+
signal,
|
|
6009
|
+
})
|
|
6010
|
+
if (credentialError || !credential) {
|
|
6011
|
+
return this._returnResult({
|
|
6012
|
+
data: null,
|
|
6013
|
+
error: credentialError ?? new AuthUnknownError('WebAuthn ceremony failed', null),
|
|
6014
|
+
})
|
|
6015
|
+
}
|
|
6016
|
+
|
|
6017
|
+
// 3. Serialize and verify with server
|
|
6018
|
+
const serialized = serializeCredentialRequestResponse(credential)
|
|
6019
|
+
return this._verifyPasskeyAuthentication({
|
|
6020
|
+
challengeId: options.challenge_id,
|
|
6021
|
+
credential: serialized,
|
|
6022
|
+
})
|
|
6023
|
+
} catch (error) {
|
|
6024
|
+
if (isAuthError(error)) {
|
|
6025
|
+
return this._returnResult({ data: null, error })
|
|
6026
|
+
}
|
|
6027
|
+
throw error
|
|
6028
|
+
}
|
|
6029
|
+
}
|
|
6030
|
+
|
|
6031
|
+
/**
|
|
6032
|
+
* Register a passkey for the current authenticated user. Handles the full WebAuthn ceremony:
|
|
6033
|
+
* 1. Fetches registration challenge from server
|
|
6034
|
+
* 2. Prompts user via navigator.credentials.create()
|
|
6035
|
+
* 3. Verifies credential with server
|
|
6036
|
+
*
|
|
6037
|
+
* Requires an active session. Requires `auth.experimental.passkey: true`.
|
|
6038
|
+
*/
|
|
6039
|
+
async registerPasskey(
|
|
6040
|
+
credentials?: RegisterPasskeyCredentials
|
|
6041
|
+
): Promise<AuthPasskeyRegistrationVerifyResponse> {
|
|
6042
|
+
assertPasskeyExperimentalEnabled(this.experimental)
|
|
6043
|
+
try {
|
|
6044
|
+
if (!browserSupportsWebAuthn()) {
|
|
6045
|
+
return this._returnResult({
|
|
6046
|
+
data: null,
|
|
6047
|
+
error: new AuthUnknownError('Browser does not support WebAuthn', null),
|
|
6048
|
+
})
|
|
6049
|
+
}
|
|
6050
|
+
|
|
6051
|
+
// 1. Get challenge options from server
|
|
6052
|
+
const { data: options, error: optionsError } = await this._startPasskeyRegistration()
|
|
6053
|
+
if (optionsError || !options) {
|
|
6054
|
+
return this._returnResult({ data: null, error: optionsError })
|
|
6055
|
+
}
|
|
6056
|
+
|
|
6057
|
+
// 2. Deserialize and prompt user via browser WebAuthn API
|
|
6058
|
+
const publicKeyOptions = deserializeCredentialCreationOptions(options.options)
|
|
6059
|
+
const signal = credentials?.options?.signal ?? webAuthnAbortService.createNewAbortSignal()
|
|
6060
|
+
const { data: credential, error: credentialError } = await createCredential({
|
|
6061
|
+
publicKey: publicKeyOptions,
|
|
6062
|
+
signal,
|
|
6063
|
+
})
|
|
6064
|
+
if (credentialError || !credential) {
|
|
6065
|
+
return this._returnResult({
|
|
6066
|
+
data: null,
|
|
6067
|
+
error: credentialError ?? new AuthUnknownError('WebAuthn ceremony failed', null),
|
|
6068
|
+
})
|
|
6069
|
+
}
|
|
6070
|
+
|
|
6071
|
+
// 3. Serialize and verify with server
|
|
6072
|
+
const serialized = serializeCredentialCreationResponse(credential)
|
|
6073
|
+
return this._verifyPasskeyRegistration({
|
|
6074
|
+
challengeId: options.challenge_id,
|
|
6075
|
+
credential: serialized,
|
|
6076
|
+
})
|
|
6077
|
+
} catch (error) {
|
|
6078
|
+
if (isAuthError(error)) {
|
|
6079
|
+
return this._returnResult({ data: null, error })
|
|
6080
|
+
}
|
|
6081
|
+
throw error
|
|
6082
|
+
}
|
|
6083
|
+
}
|
|
6084
|
+
|
|
6085
|
+
/**
|
|
6086
|
+
* Start passkey registration for the current authenticated user.
|
|
6087
|
+
* Returns WebAuthn credential creation options to pass to navigator.credentials.create().
|
|
6088
|
+
*/
|
|
6089
|
+
private async _startPasskeyRegistration(): Promise<AuthPasskeyRegistrationOptionsResponse> {
|
|
6090
|
+
assertPasskeyExperimentalEnabled(this.experimental)
|
|
6091
|
+
try {
|
|
6092
|
+
return await this._useSession(async (result) => {
|
|
6093
|
+
const {
|
|
6094
|
+
data: { session },
|
|
6095
|
+
error: sessionError,
|
|
6096
|
+
} = result
|
|
6097
|
+
if (sessionError) {
|
|
6098
|
+
return this._returnResult({ data: null, error: sessionError })
|
|
6099
|
+
}
|
|
6100
|
+
if (!session) {
|
|
6101
|
+
return this._returnResult({ data: null, error: new AuthSessionMissingError() })
|
|
6102
|
+
}
|
|
6103
|
+
const { data, error } = await _request(
|
|
6104
|
+
this.fetch,
|
|
6105
|
+
'POST',
|
|
6106
|
+
`${this.url}/passkeys/registration/options`,
|
|
6107
|
+
{
|
|
6108
|
+
headers: this.headers,
|
|
6109
|
+
jwt: session.access_token,
|
|
6110
|
+
body: {},
|
|
6111
|
+
}
|
|
6112
|
+
)
|
|
6113
|
+
if (error) {
|
|
6114
|
+
return this._returnResult({ data: null, error })
|
|
6115
|
+
}
|
|
6116
|
+
return this._returnResult({ data, error: null })
|
|
6117
|
+
})
|
|
6118
|
+
} catch (error) {
|
|
6119
|
+
if (isAuthError(error)) {
|
|
6120
|
+
return this._returnResult({ data: null, error })
|
|
6121
|
+
}
|
|
6122
|
+
throw error
|
|
6123
|
+
}
|
|
6124
|
+
}
|
|
6125
|
+
|
|
6126
|
+
/**
|
|
6127
|
+
* Verify passkey registration with the credential response.
|
|
6128
|
+
* The credentialResponse should be the serialized output of navigator.credentials.create().
|
|
6129
|
+
*/
|
|
6130
|
+
private async _verifyPasskeyRegistration(
|
|
6131
|
+
params: VerifyPasskeyRegistrationParams
|
|
6132
|
+
): Promise<AuthPasskeyRegistrationVerifyResponse> {
|
|
6133
|
+
assertPasskeyExperimentalEnabled(this.experimental)
|
|
6134
|
+
try {
|
|
6135
|
+
return await this._useSession(async (result) => {
|
|
6136
|
+
const {
|
|
6137
|
+
data: { session },
|
|
6138
|
+
error: sessionError,
|
|
6139
|
+
} = result
|
|
6140
|
+
if (sessionError) {
|
|
6141
|
+
return this._returnResult({ data: null, error: sessionError })
|
|
6142
|
+
}
|
|
6143
|
+
if (!session) {
|
|
6144
|
+
return this._returnResult({ data: null, error: new AuthSessionMissingError() })
|
|
6145
|
+
}
|
|
6146
|
+
const { data, error } = await _request(
|
|
6147
|
+
this.fetch,
|
|
6148
|
+
'POST',
|
|
6149
|
+
`${this.url}/passkeys/registration/verify`,
|
|
6150
|
+
{
|
|
6151
|
+
headers: this.headers,
|
|
6152
|
+
jwt: session.access_token,
|
|
6153
|
+
body: {
|
|
6154
|
+
challenge_id: params.challengeId,
|
|
6155
|
+
credential: params.credential,
|
|
6156
|
+
},
|
|
6157
|
+
}
|
|
6158
|
+
)
|
|
6159
|
+
if (error) {
|
|
6160
|
+
return this._returnResult({ data: null, error })
|
|
6161
|
+
}
|
|
6162
|
+
return this._returnResult({ data, error: null })
|
|
6163
|
+
})
|
|
6164
|
+
} catch (error) {
|
|
6165
|
+
if (isAuthError(error)) {
|
|
6166
|
+
return this._returnResult({ data: null, error })
|
|
6167
|
+
}
|
|
6168
|
+
throw error
|
|
6169
|
+
}
|
|
6170
|
+
}
|
|
6171
|
+
|
|
6172
|
+
/**
|
|
6173
|
+
* Start passkey authentication.
|
|
6174
|
+
* Returns WebAuthn credential request options to pass to navigator.credentials.get().
|
|
6175
|
+
*/
|
|
6176
|
+
private async _startPasskeyAuthentication(
|
|
6177
|
+
params?: StartPasskeyAuthenticationParams
|
|
6178
|
+
): Promise<AuthPasskeyAuthenticationOptionsResponse> {
|
|
6179
|
+
assertPasskeyExperimentalEnabled(this.experimental)
|
|
6180
|
+
try {
|
|
6181
|
+
const { data, error } = await _request(
|
|
6182
|
+
this.fetch,
|
|
6183
|
+
'POST',
|
|
6184
|
+
`${this.url}/passkeys/authentication/options`,
|
|
6185
|
+
{
|
|
6186
|
+
headers: this.headers,
|
|
6187
|
+
body: {
|
|
6188
|
+
gotrue_meta_security: { captcha_token: params?.options?.captchaToken },
|
|
6189
|
+
},
|
|
6190
|
+
}
|
|
6191
|
+
)
|
|
6192
|
+
if (error) {
|
|
6193
|
+
return this._returnResult({ data: null, error })
|
|
6194
|
+
}
|
|
6195
|
+
return this._returnResult({ data, error: null })
|
|
6196
|
+
} catch (error) {
|
|
6197
|
+
if (isAuthError(error)) {
|
|
6198
|
+
return this._returnResult({ data: null, error })
|
|
6199
|
+
}
|
|
6200
|
+
throw error
|
|
6201
|
+
}
|
|
6202
|
+
}
|
|
6203
|
+
|
|
6204
|
+
/**
|
|
6205
|
+
* Verify passkey authentication and create a session.
|
|
6206
|
+
* The credential should be the serialized output of navigator.credentials.get().
|
|
6207
|
+
*/
|
|
6208
|
+
private async _verifyPasskeyAuthentication(
|
|
6209
|
+
params: VerifyPasskeyAuthenticationParams
|
|
6210
|
+
): Promise<AuthPasskeyAuthenticationVerifyResponse> {
|
|
6211
|
+
assertPasskeyExperimentalEnabled(this.experimental)
|
|
6212
|
+
try {
|
|
6213
|
+
const { data, error } = await _request(
|
|
6214
|
+
this.fetch,
|
|
6215
|
+
'POST',
|
|
6216
|
+
`${this.url}/passkeys/authentication/verify`,
|
|
6217
|
+
{
|
|
6218
|
+
headers: this.headers,
|
|
6219
|
+
body: {
|
|
6220
|
+
challenge_id: params.challengeId,
|
|
6221
|
+
credential: params.credential,
|
|
6222
|
+
},
|
|
6223
|
+
xform: _sessionResponse,
|
|
6224
|
+
}
|
|
6225
|
+
)
|
|
6226
|
+
if (error) {
|
|
6227
|
+
return this._returnResult({ data: null, error })
|
|
6228
|
+
}
|
|
6229
|
+
if (data.session) {
|
|
6230
|
+
await this._saveSession(data.session)
|
|
6231
|
+
await this._notifyAllSubscribers('SIGNED_IN', data.session)
|
|
6232
|
+
}
|
|
6233
|
+
return this._returnResult({ data, error: null })
|
|
6234
|
+
} catch (error) {
|
|
6235
|
+
if (isAuthError(error)) {
|
|
6236
|
+
return this._returnResult({ data: null, error })
|
|
6237
|
+
}
|
|
6238
|
+
throw error
|
|
6239
|
+
}
|
|
6240
|
+
}
|
|
6241
|
+
|
|
6242
|
+
/**
|
|
6243
|
+
* List all passkeys for the current user.
|
|
6244
|
+
*/
|
|
6245
|
+
private async _listPasskeys(): Promise<AuthPasskeyListResponse> {
|
|
6246
|
+
assertPasskeyExperimentalEnabled(this.experimental)
|
|
6247
|
+
try {
|
|
6248
|
+
return await this._useSession(async (result) => {
|
|
6249
|
+
const {
|
|
6250
|
+
data: { session },
|
|
6251
|
+
error: sessionError,
|
|
6252
|
+
} = result
|
|
6253
|
+
if (sessionError) {
|
|
6254
|
+
return this._returnResult({ data: null, error: sessionError })
|
|
6255
|
+
}
|
|
6256
|
+
if (!session) {
|
|
6257
|
+
return this._returnResult({ data: null, error: new AuthSessionMissingError() })
|
|
6258
|
+
}
|
|
6259
|
+
const { data, error } = await _request(this.fetch, 'GET', `${this.url}/passkeys`, {
|
|
6260
|
+
headers: this.headers,
|
|
6261
|
+
jwt: session.access_token,
|
|
6262
|
+
xform: (data: any) => ({ data, error: null }),
|
|
6263
|
+
})
|
|
6264
|
+
if (error) {
|
|
6265
|
+
return this._returnResult({ data: null, error })
|
|
6266
|
+
}
|
|
6267
|
+
return this._returnResult({ data, error: null })
|
|
6268
|
+
})
|
|
6269
|
+
} catch (error) {
|
|
6270
|
+
if (isAuthError(error)) {
|
|
6271
|
+
return this._returnResult({ data: null, error })
|
|
6272
|
+
}
|
|
6273
|
+
throw error
|
|
6274
|
+
}
|
|
6275
|
+
}
|
|
6276
|
+
|
|
6277
|
+
/**
|
|
6278
|
+
* Update a passkey.
|
|
6279
|
+
*/
|
|
6280
|
+
private async _updatePasskey(params: PasskeyUpdateParams): Promise<AuthPasskeyUpdateResponse> {
|
|
6281
|
+
assertPasskeyExperimentalEnabled(this.experimental)
|
|
6282
|
+
try {
|
|
6283
|
+
return await this._useSession(async (result) => {
|
|
6284
|
+
const {
|
|
6285
|
+
data: { session },
|
|
6286
|
+
error: sessionError,
|
|
6287
|
+
} = result
|
|
6288
|
+
if (sessionError) {
|
|
6289
|
+
return this._returnResult({ data: null, error: sessionError })
|
|
6290
|
+
}
|
|
6291
|
+
if (!session) {
|
|
6292
|
+
return this._returnResult({ data: null, error: new AuthSessionMissingError() })
|
|
6293
|
+
}
|
|
6294
|
+
const { data, error } = await _request(
|
|
6295
|
+
this.fetch,
|
|
6296
|
+
'PATCH',
|
|
6297
|
+
`${this.url}/passkeys/${params.passkeyId}`,
|
|
6298
|
+
{
|
|
6299
|
+
headers: this.headers,
|
|
6300
|
+
jwt: session.access_token,
|
|
6301
|
+
body: { friendly_name: params.friendlyName },
|
|
6302
|
+
}
|
|
6303
|
+
)
|
|
6304
|
+
if (error) {
|
|
6305
|
+
return this._returnResult({ data: null, error })
|
|
6306
|
+
}
|
|
6307
|
+
return this._returnResult({ data, error: null })
|
|
6308
|
+
})
|
|
6309
|
+
} catch (error) {
|
|
6310
|
+
if (isAuthError(error)) {
|
|
6311
|
+
return this._returnResult({ data: null, error })
|
|
6312
|
+
}
|
|
6313
|
+
throw error
|
|
6314
|
+
}
|
|
6315
|
+
}
|
|
6316
|
+
|
|
6317
|
+
/**
|
|
6318
|
+
* Delete a passkey.
|
|
6319
|
+
*/
|
|
6320
|
+
private async _deletePasskey(params: PasskeyDeleteParams): Promise<AuthPasskeyDeleteResponse> {
|
|
6321
|
+
assertPasskeyExperimentalEnabled(this.experimental)
|
|
6322
|
+
try {
|
|
6323
|
+
return await this._useSession(async (result) => {
|
|
6324
|
+
const {
|
|
6325
|
+
data: { session },
|
|
6326
|
+
error: sessionError,
|
|
6327
|
+
} = result
|
|
6328
|
+
if (sessionError) {
|
|
6329
|
+
return this._returnResult({ data: null, error: sessionError })
|
|
6330
|
+
}
|
|
6331
|
+
if (!session) {
|
|
6332
|
+
return this._returnResult({ data: null, error: new AuthSessionMissingError() })
|
|
6333
|
+
}
|
|
6334
|
+
const { error } = await _request(
|
|
6335
|
+
this.fetch,
|
|
6336
|
+
'DELETE',
|
|
6337
|
+
`${this.url}/passkeys/${params.passkeyId}`,
|
|
6338
|
+
{
|
|
6339
|
+
headers: this.headers,
|
|
6340
|
+
jwt: session.access_token,
|
|
6341
|
+
noResolveJson: true,
|
|
6342
|
+
}
|
|
6343
|
+
)
|
|
6344
|
+
if (error) {
|
|
6345
|
+
return this._returnResult({ data: null, error })
|
|
6346
|
+
}
|
|
6347
|
+
return this._returnResult({ data: null, error: null })
|
|
6348
|
+
})
|
|
6349
|
+
} catch (error) {
|
|
6350
|
+
if (isAuthError(error)) {
|
|
6351
|
+
return this._returnResult({ data: null, error })
|
|
6352
|
+
}
|
|
6353
|
+
throw error
|
|
6354
|
+
}
|
|
6355
|
+
}
|
|
5909
6356
|
}
|