@supabase/auth-js 3.0.0-next.5 → 3.0.0-next.6
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 +26 -2
- package/dist/main/GoTrueAdminApi.d.ts.map +1 -1
- package/dist/main/GoTrueAdminApi.js +48 -1
- package/dist/main/GoTrueAdminApi.js.map +1 -1
- package/dist/main/GoTrueClient.d.ts +63 -1
- package/dist/main/GoTrueClient.d.ts.map +1 -1
- package/dist/main/GoTrueClient.js +340 -3
- package/dist/main/GoTrueClient.js.map +1 -1
- package/dist/main/lib/fetch.d.ts +1 -1
- package/dist/main/lib/fetch.d.ts.map +1 -1
- package/dist/main/lib/helpers.d.ts +3 -0
- package/dist/main/lib/helpers.d.ts.map +1 -1
- package/dist/main/lib/helpers.js +6 -0
- package/dist/main/lib/helpers.js.map +1 -1
- package/dist/main/lib/types.d.ts +128 -1
- 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/main/lib/webauthn.d.ts +8 -0
- package/dist/main/lib/webauthn.d.ts.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 +26 -2
- package/dist/module/GoTrueAdminApi.d.ts.map +1 -1
- package/dist/module/GoTrueAdminApi.js +49 -2
- package/dist/module/GoTrueAdminApi.js.map +1 -1
- package/dist/module/GoTrueClient.d.ts +63 -1
- package/dist/module/GoTrueClient.d.ts.map +1 -1
- package/dist/module/GoTrueClient.js +342 -5
- package/dist/module/GoTrueClient.js.map +1 -1
- package/dist/module/lib/fetch.d.ts +1 -1
- package/dist/module/lib/fetch.d.ts.map +1 -1
- package/dist/module/lib/helpers.d.ts +3 -0
- package/dist/module/lib/helpers.d.ts.map +1 -1
- package/dist/module/lib/helpers.js +5 -0
- package/dist/module/lib/helpers.js.map +1 -1
- package/dist/module/lib/types.d.ts +128 -1
- 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/module/lib/webauthn.d.ts +8 -0
- package/dist/module/lib/webauthn.d.ts.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 +80 -1
- package/src/GoTrueClient.ts +430 -0
- package/src/lib/fetch.ts +1 -1
- package/src/lib/helpers.ts +8 -0
- package/src/lib/types.ts +178 -0
- package/src/lib/version.ts +1 -1
- 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.
|
|
@@ -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
|
|
@@ -5912,4 +5958,388 @@ export default class GoTrueClient {
|
|
|
5912
5958
|
throw error
|
|
5913
5959
|
}
|
|
5914
5960
|
}
|
|
5961
|
+
|
|
5962
|
+
// --- Passkey Methods ---
|
|
5963
|
+
|
|
5964
|
+
/**
|
|
5965
|
+
* Sign in with a passkey. Handles the full WebAuthn ceremony:
|
|
5966
|
+
* 1. Fetches authentication challenge from server
|
|
5967
|
+
* 2. Prompts user via navigator.credentials.get()
|
|
5968
|
+
* 3. Verifies credential with server and creates session
|
|
5969
|
+
*
|
|
5970
|
+
* Requires `auth.experimental.passkey: true`.
|
|
5971
|
+
*/
|
|
5972
|
+
async signInWithPasskey(
|
|
5973
|
+
credentials?: SignInWithPasskeyCredentials
|
|
5974
|
+
): Promise<AuthPasskeyAuthenticationVerifyResponse> {
|
|
5975
|
+
assertPasskeyExperimentalEnabled(this.experimental)
|
|
5976
|
+
try {
|
|
5977
|
+
if (!browserSupportsWebAuthn()) {
|
|
5978
|
+
return this._returnResult({
|
|
5979
|
+
data: null,
|
|
5980
|
+
error: new AuthUnknownError('Browser does not support WebAuthn', null),
|
|
5981
|
+
})
|
|
5982
|
+
}
|
|
5983
|
+
|
|
5984
|
+
// 1. Get challenge options from server
|
|
5985
|
+
const { data: options, error: optionsError } = await this._startPasskeyAuthentication({
|
|
5986
|
+
options: { captchaToken: credentials?.options?.captchaToken },
|
|
5987
|
+
})
|
|
5988
|
+
if (optionsError || !options) {
|
|
5989
|
+
return this._returnResult({ data: null, error: optionsError })
|
|
5990
|
+
}
|
|
5991
|
+
|
|
5992
|
+
// 2. Deserialize and prompt user via browser WebAuthn API
|
|
5993
|
+
const publicKeyOptions = deserializeCredentialRequestOptions(options.options)
|
|
5994
|
+
const signal = credentials?.options?.signal ?? webAuthnAbortService.createNewAbortSignal()
|
|
5995
|
+
const { data: credential, error: credentialError } = await getCredential({
|
|
5996
|
+
publicKey: publicKeyOptions,
|
|
5997
|
+
signal,
|
|
5998
|
+
})
|
|
5999
|
+
if (credentialError || !credential) {
|
|
6000
|
+
return this._returnResult({
|
|
6001
|
+
data: null,
|
|
6002
|
+
error: credentialError ?? new AuthUnknownError('WebAuthn ceremony failed', null),
|
|
6003
|
+
})
|
|
6004
|
+
}
|
|
6005
|
+
|
|
6006
|
+
// 3. Serialize and verify with server
|
|
6007
|
+
const serialized = serializeCredentialRequestResponse(credential)
|
|
6008
|
+
return this._verifyPasskeyAuthentication({
|
|
6009
|
+
challengeId: options.challenge_id,
|
|
6010
|
+
credential: serialized,
|
|
6011
|
+
})
|
|
6012
|
+
} catch (error) {
|
|
6013
|
+
if (isAuthError(error)) {
|
|
6014
|
+
return this._returnResult({ data: null, error })
|
|
6015
|
+
}
|
|
6016
|
+
throw error
|
|
6017
|
+
}
|
|
6018
|
+
}
|
|
6019
|
+
|
|
6020
|
+
/**
|
|
6021
|
+
* Register a passkey for the current authenticated user. Handles the full WebAuthn ceremony:
|
|
6022
|
+
* 1. Fetches registration challenge from server
|
|
6023
|
+
* 2. Prompts user via navigator.credentials.create()
|
|
6024
|
+
* 3. Verifies credential with server
|
|
6025
|
+
*
|
|
6026
|
+
* Requires an active session. Requires `auth.experimental.passkey: true`.
|
|
6027
|
+
*/
|
|
6028
|
+
async registerPasskey(
|
|
6029
|
+
credentials?: RegisterPasskeyCredentials
|
|
6030
|
+
): Promise<AuthPasskeyRegistrationVerifyResponse> {
|
|
6031
|
+
assertPasskeyExperimentalEnabled(this.experimental)
|
|
6032
|
+
try {
|
|
6033
|
+
if (!browserSupportsWebAuthn()) {
|
|
6034
|
+
return this._returnResult({
|
|
6035
|
+
data: null,
|
|
6036
|
+
error: new AuthUnknownError('Browser does not support WebAuthn', null),
|
|
6037
|
+
})
|
|
6038
|
+
}
|
|
6039
|
+
|
|
6040
|
+
// 1. Get challenge options from server
|
|
6041
|
+
const { data: options, error: optionsError } = await this._startPasskeyRegistration()
|
|
6042
|
+
if (optionsError || !options) {
|
|
6043
|
+
return this._returnResult({ data: null, error: optionsError })
|
|
6044
|
+
}
|
|
6045
|
+
|
|
6046
|
+
// 2. Deserialize and prompt user via browser WebAuthn API
|
|
6047
|
+
const publicKeyOptions = deserializeCredentialCreationOptions(options.options)
|
|
6048
|
+
const signal = credentials?.options?.signal ?? webAuthnAbortService.createNewAbortSignal()
|
|
6049
|
+
const { data: credential, error: credentialError } = await createCredential({
|
|
6050
|
+
publicKey: publicKeyOptions,
|
|
6051
|
+
signal,
|
|
6052
|
+
})
|
|
6053
|
+
if (credentialError || !credential) {
|
|
6054
|
+
return this._returnResult({
|
|
6055
|
+
data: null,
|
|
6056
|
+
error: credentialError ?? new AuthUnknownError('WebAuthn ceremony failed', null),
|
|
6057
|
+
})
|
|
6058
|
+
}
|
|
6059
|
+
|
|
6060
|
+
// 3. Serialize and verify with server
|
|
6061
|
+
const serialized = serializeCredentialCreationResponse(credential)
|
|
6062
|
+
return this._verifyPasskeyRegistration({
|
|
6063
|
+
challengeId: options.challenge_id,
|
|
6064
|
+
credential: serialized,
|
|
6065
|
+
})
|
|
6066
|
+
} catch (error) {
|
|
6067
|
+
if (isAuthError(error)) {
|
|
6068
|
+
return this._returnResult({ data: null, error })
|
|
6069
|
+
}
|
|
6070
|
+
throw error
|
|
6071
|
+
}
|
|
6072
|
+
}
|
|
6073
|
+
|
|
6074
|
+
/**
|
|
6075
|
+
* Start passkey registration for the current authenticated user.
|
|
6076
|
+
* Returns WebAuthn credential creation options to pass to navigator.credentials.create().
|
|
6077
|
+
*/
|
|
6078
|
+
private async _startPasskeyRegistration(): Promise<AuthPasskeyRegistrationOptionsResponse> {
|
|
6079
|
+
assertPasskeyExperimentalEnabled(this.experimental)
|
|
6080
|
+
try {
|
|
6081
|
+
return await this._useSession(async (result) => {
|
|
6082
|
+
const {
|
|
6083
|
+
data: { session },
|
|
6084
|
+
error: sessionError,
|
|
6085
|
+
} = result
|
|
6086
|
+
if (sessionError) {
|
|
6087
|
+
return this._returnResult({ data: null, error: sessionError })
|
|
6088
|
+
}
|
|
6089
|
+
if (!session) {
|
|
6090
|
+
return this._returnResult({ data: null, error: new AuthSessionMissingError() })
|
|
6091
|
+
}
|
|
6092
|
+
const { data, error } = await _request(
|
|
6093
|
+
this.fetch,
|
|
6094
|
+
'POST',
|
|
6095
|
+
`${this.url}/passkeys/registration/options`,
|
|
6096
|
+
{
|
|
6097
|
+
headers: this.headers,
|
|
6098
|
+
jwt: session.access_token,
|
|
6099
|
+
body: {},
|
|
6100
|
+
}
|
|
6101
|
+
)
|
|
6102
|
+
if (error) {
|
|
6103
|
+
return this._returnResult({ data: null, error })
|
|
6104
|
+
}
|
|
6105
|
+
return this._returnResult({ data, error: null })
|
|
6106
|
+
})
|
|
6107
|
+
} catch (error) {
|
|
6108
|
+
if (isAuthError(error)) {
|
|
6109
|
+
return this._returnResult({ data: null, error })
|
|
6110
|
+
}
|
|
6111
|
+
throw error
|
|
6112
|
+
}
|
|
6113
|
+
}
|
|
6114
|
+
|
|
6115
|
+
/**
|
|
6116
|
+
* Verify passkey registration with the credential response.
|
|
6117
|
+
* The credentialResponse should be the serialized output of navigator.credentials.create().
|
|
6118
|
+
*/
|
|
6119
|
+
private async _verifyPasskeyRegistration(
|
|
6120
|
+
params: VerifyPasskeyRegistrationParams
|
|
6121
|
+
): Promise<AuthPasskeyRegistrationVerifyResponse> {
|
|
6122
|
+
assertPasskeyExperimentalEnabled(this.experimental)
|
|
6123
|
+
try {
|
|
6124
|
+
return await this._useSession(async (result) => {
|
|
6125
|
+
const {
|
|
6126
|
+
data: { session },
|
|
6127
|
+
error: sessionError,
|
|
6128
|
+
} = result
|
|
6129
|
+
if (sessionError) {
|
|
6130
|
+
return this._returnResult({ data: null, error: sessionError })
|
|
6131
|
+
}
|
|
6132
|
+
if (!session) {
|
|
6133
|
+
return this._returnResult({ data: null, error: new AuthSessionMissingError() })
|
|
6134
|
+
}
|
|
6135
|
+
const { data, error } = await _request(
|
|
6136
|
+
this.fetch,
|
|
6137
|
+
'POST',
|
|
6138
|
+
`${this.url}/passkeys/registration/verify`,
|
|
6139
|
+
{
|
|
6140
|
+
headers: this.headers,
|
|
6141
|
+
jwt: session.access_token,
|
|
6142
|
+
body: {
|
|
6143
|
+
challenge_id: params.challengeId,
|
|
6144
|
+
credential: params.credential,
|
|
6145
|
+
},
|
|
6146
|
+
}
|
|
6147
|
+
)
|
|
6148
|
+
if (error) {
|
|
6149
|
+
return this._returnResult({ data: null, error })
|
|
6150
|
+
}
|
|
6151
|
+
return this._returnResult({ data, error: null })
|
|
6152
|
+
})
|
|
6153
|
+
} catch (error) {
|
|
6154
|
+
if (isAuthError(error)) {
|
|
6155
|
+
return this._returnResult({ data: null, error })
|
|
6156
|
+
}
|
|
6157
|
+
throw error
|
|
6158
|
+
}
|
|
6159
|
+
}
|
|
6160
|
+
|
|
6161
|
+
/**
|
|
6162
|
+
* Start passkey authentication.
|
|
6163
|
+
* Returns WebAuthn credential request options to pass to navigator.credentials.get().
|
|
6164
|
+
*/
|
|
6165
|
+
private async _startPasskeyAuthentication(
|
|
6166
|
+
params?: StartPasskeyAuthenticationParams
|
|
6167
|
+
): Promise<AuthPasskeyAuthenticationOptionsResponse> {
|
|
6168
|
+
assertPasskeyExperimentalEnabled(this.experimental)
|
|
6169
|
+
try {
|
|
6170
|
+
const { data, error } = await _request(
|
|
6171
|
+
this.fetch,
|
|
6172
|
+
'POST',
|
|
6173
|
+
`${this.url}/passkeys/authentication/options`,
|
|
6174
|
+
{
|
|
6175
|
+
headers: this.headers,
|
|
6176
|
+
body: {
|
|
6177
|
+
gotrue_meta_security: { captcha_token: params?.options?.captchaToken },
|
|
6178
|
+
},
|
|
6179
|
+
}
|
|
6180
|
+
)
|
|
6181
|
+
if (error) {
|
|
6182
|
+
return this._returnResult({ data: null, error })
|
|
6183
|
+
}
|
|
6184
|
+
return this._returnResult({ data, error: null })
|
|
6185
|
+
} catch (error) {
|
|
6186
|
+
if (isAuthError(error)) {
|
|
6187
|
+
return this._returnResult({ data: null, error })
|
|
6188
|
+
}
|
|
6189
|
+
throw error
|
|
6190
|
+
}
|
|
6191
|
+
}
|
|
6192
|
+
|
|
6193
|
+
/**
|
|
6194
|
+
* Verify passkey authentication and create a session.
|
|
6195
|
+
* The credential should be the serialized output of navigator.credentials.get().
|
|
6196
|
+
*/
|
|
6197
|
+
private async _verifyPasskeyAuthentication(
|
|
6198
|
+
params: VerifyPasskeyAuthenticationParams
|
|
6199
|
+
): Promise<AuthPasskeyAuthenticationVerifyResponse> {
|
|
6200
|
+
assertPasskeyExperimentalEnabled(this.experimental)
|
|
6201
|
+
try {
|
|
6202
|
+
const { data, error } = await _request(
|
|
6203
|
+
this.fetch,
|
|
6204
|
+
'POST',
|
|
6205
|
+
`${this.url}/passkeys/authentication/verify`,
|
|
6206
|
+
{
|
|
6207
|
+
headers: this.headers,
|
|
6208
|
+
body: {
|
|
6209
|
+
challenge_id: params.challengeId,
|
|
6210
|
+
credential: params.credential,
|
|
6211
|
+
},
|
|
6212
|
+
xform: _sessionResponse,
|
|
6213
|
+
}
|
|
6214
|
+
)
|
|
6215
|
+
if (error) {
|
|
6216
|
+
return this._returnResult({ data: null, error })
|
|
6217
|
+
}
|
|
6218
|
+
if (data.session) {
|
|
6219
|
+
await this._saveSession(data.session)
|
|
6220
|
+
await this._notifyAllSubscribers('SIGNED_IN', data.session)
|
|
6221
|
+
}
|
|
6222
|
+
return this._returnResult({ data, error: null })
|
|
6223
|
+
} catch (error) {
|
|
6224
|
+
if (isAuthError(error)) {
|
|
6225
|
+
return this._returnResult({ data: null, error })
|
|
6226
|
+
}
|
|
6227
|
+
throw error
|
|
6228
|
+
}
|
|
6229
|
+
}
|
|
6230
|
+
|
|
6231
|
+
/**
|
|
6232
|
+
* List all passkeys for the current user.
|
|
6233
|
+
*/
|
|
6234
|
+
private async _listPasskeys(): Promise<AuthPasskeyListResponse> {
|
|
6235
|
+
assertPasskeyExperimentalEnabled(this.experimental)
|
|
6236
|
+
try {
|
|
6237
|
+
return await this._useSession(async (result) => {
|
|
6238
|
+
const {
|
|
6239
|
+
data: { session },
|
|
6240
|
+
error: sessionError,
|
|
6241
|
+
} = result
|
|
6242
|
+
if (sessionError) {
|
|
6243
|
+
return this._returnResult({ data: null, error: sessionError })
|
|
6244
|
+
}
|
|
6245
|
+
if (!session) {
|
|
6246
|
+
return this._returnResult({ data: null, error: new AuthSessionMissingError() })
|
|
6247
|
+
}
|
|
6248
|
+
const { data, error } = await _request(this.fetch, 'GET', `${this.url}/passkeys`, {
|
|
6249
|
+
headers: this.headers,
|
|
6250
|
+
jwt: session.access_token,
|
|
6251
|
+
xform: (data: any) => ({ data, error: null }),
|
|
6252
|
+
})
|
|
6253
|
+
if (error) {
|
|
6254
|
+
return this._returnResult({ data: null, error })
|
|
6255
|
+
}
|
|
6256
|
+
return this._returnResult({ data, error: null })
|
|
6257
|
+
})
|
|
6258
|
+
} catch (error) {
|
|
6259
|
+
if (isAuthError(error)) {
|
|
6260
|
+
return this._returnResult({ data: null, error })
|
|
6261
|
+
}
|
|
6262
|
+
throw error
|
|
6263
|
+
}
|
|
6264
|
+
}
|
|
6265
|
+
|
|
6266
|
+
/**
|
|
6267
|
+
* Update a passkey.
|
|
6268
|
+
*/
|
|
6269
|
+
private async _updatePasskey(params: PasskeyUpdateParams): Promise<AuthPasskeyUpdateResponse> {
|
|
6270
|
+
assertPasskeyExperimentalEnabled(this.experimental)
|
|
6271
|
+
try {
|
|
6272
|
+
return await this._useSession(async (result) => {
|
|
6273
|
+
const {
|
|
6274
|
+
data: { session },
|
|
6275
|
+
error: sessionError,
|
|
6276
|
+
} = result
|
|
6277
|
+
if (sessionError) {
|
|
6278
|
+
return this._returnResult({ data: null, error: sessionError })
|
|
6279
|
+
}
|
|
6280
|
+
if (!session) {
|
|
6281
|
+
return this._returnResult({ data: null, error: new AuthSessionMissingError() })
|
|
6282
|
+
}
|
|
6283
|
+
const { data, error } = await _request(
|
|
6284
|
+
this.fetch,
|
|
6285
|
+
'PATCH',
|
|
6286
|
+
`${this.url}/passkeys/${params.passkeyId}`,
|
|
6287
|
+
{
|
|
6288
|
+
headers: this.headers,
|
|
6289
|
+
jwt: session.access_token,
|
|
6290
|
+
body: { friendly_name: params.friendlyName },
|
|
6291
|
+
}
|
|
6292
|
+
)
|
|
6293
|
+
if (error) {
|
|
6294
|
+
return this._returnResult({ data: null, error })
|
|
6295
|
+
}
|
|
6296
|
+
return this._returnResult({ data, error: null })
|
|
6297
|
+
})
|
|
6298
|
+
} catch (error) {
|
|
6299
|
+
if (isAuthError(error)) {
|
|
6300
|
+
return this._returnResult({ data: null, error })
|
|
6301
|
+
}
|
|
6302
|
+
throw error
|
|
6303
|
+
}
|
|
6304
|
+
}
|
|
6305
|
+
|
|
6306
|
+
/**
|
|
6307
|
+
* Delete a passkey.
|
|
6308
|
+
*/
|
|
6309
|
+
private async _deletePasskey(params: PasskeyDeleteParams): Promise<AuthPasskeyDeleteResponse> {
|
|
6310
|
+
assertPasskeyExperimentalEnabled(this.experimental)
|
|
6311
|
+
try {
|
|
6312
|
+
return await this._useSession(async (result) => {
|
|
6313
|
+
const {
|
|
6314
|
+
data: { session },
|
|
6315
|
+
error: sessionError,
|
|
6316
|
+
} = result
|
|
6317
|
+
if (sessionError) {
|
|
6318
|
+
return this._returnResult({ data: null, error: sessionError })
|
|
6319
|
+
}
|
|
6320
|
+
if (!session) {
|
|
6321
|
+
return this._returnResult({ data: null, error: new AuthSessionMissingError() })
|
|
6322
|
+
}
|
|
6323
|
+
const { error } = await _request(
|
|
6324
|
+
this.fetch,
|
|
6325
|
+
'DELETE',
|
|
6326
|
+
`${this.url}/passkeys/${params.passkeyId}`,
|
|
6327
|
+
{
|
|
6328
|
+
headers: this.headers,
|
|
6329
|
+
jwt: session.access_token,
|
|
6330
|
+
noResolveJson: true,
|
|
6331
|
+
}
|
|
6332
|
+
)
|
|
6333
|
+
if (error) {
|
|
6334
|
+
return this._returnResult({ data: null, error })
|
|
6335
|
+
}
|
|
6336
|
+
return this._returnResult({ data: null, error: null })
|
|
6337
|
+
})
|
|
6338
|
+
} catch (error) {
|
|
6339
|
+
if (isAuthError(error)) {
|
|
6340
|
+
return this._returnResult({ data: null, error })
|
|
6341
|
+
}
|
|
6342
|
+
throw error
|
|
6343
|
+
}
|
|
6344
|
+
}
|
|
5915
6345
|
}
|
package/src/lib/fetch.ts
CHANGED
|
@@ -30,7 +30,7 @@ export interface FetchParameters {
|
|
|
30
30
|
signal?: AbortSignal
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export type RequestMethodType = 'GET' | 'POST' | 'PUT' | 'DELETE'
|
|
33
|
+
export type RequestMethodType = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
|
|
34
34
|
|
|
35
35
|
const _getErrorMessage = (err: any): string =>
|
|
36
36
|
err.msg || err.message || err.error_description || err.error || JSON.stringify(err)
|
package/src/lib/helpers.ts
CHANGED
|
@@ -372,6 +372,14 @@ export function validateUUID(str: string) {
|
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
+
export function assertPasskeyExperimentalEnabled(experimental: { passkey?: boolean }): void {
|
|
376
|
+
if (!experimental.passkey) {
|
|
377
|
+
throw new Error(
|
|
378
|
+
'@supabase/auth-js: the passkey API is experimental and disabled by default. Enable it by passing `auth: { experimental: { passkey: true } }` to createClient (or to the GoTrueClient constructor).'
|
|
379
|
+
)
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
375
383
|
export function userNotAvailableProxy(): User {
|
|
376
384
|
const proxyTarget = {} as User
|
|
377
385
|
|
package/src/lib/types.ts
CHANGED
|
@@ -6,6 +6,12 @@ import {
|
|
|
6
6
|
ServerCredentialCreationOptions,
|
|
7
7
|
ServerCredentialRequestOptions,
|
|
8
8
|
WebAuthnApi,
|
|
9
|
+
WebAuthnError,
|
|
10
|
+
} from './webauthn'
|
|
11
|
+
import type {
|
|
12
|
+
RegistrationResponseJSON,
|
|
13
|
+
AuthenticationResponseJSON,
|
|
14
|
+
ServerCredentialResponse,
|
|
9
15
|
} from './webauthn'
|
|
10
16
|
import {
|
|
11
17
|
AuthenticationCredential,
|
|
@@ -174,6 +180,27 @@ export type GoTrueClientOptions = {
|
|
|
174
180
|
* @default false
|
|
175
181
|
*/
|
|
176
182
|
skipAutoInitialize?: boolean
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Opt-in flags for experimental features. These APIs may change without
|
|
186
|
+
* notice and are disabled by default.
|
|
187
|
+
*
|
|
188
|
+
* @experimental
|
|
189
|
+
*/
|
|
190
|
+
experimental?: ExperimentalFeatureFlags
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export type ExperimentalFeatureFlags = {
|
|
194
|
+
/**
|
|
195
|
+
* Enables passkey support:
|
|
196
|
+
* - `auth.signInWithPasskey()`, `auth.registerPasskey()`
|
|
197
|
+
* - `auth.passkey.*`
|
|
198
|
+
* - `auth.admin.passkey.*`
|
|
199
|
+
*
|
|
200
|
+
* Defaults to `false`. Calling any passkey method while this flag is
|
|
201
|
+
* disabled throws a descriptive error at call time.
|
|
202
|
+
*/
|
|
203
|
+
passkey?: boolean
|
|
177
204
|
}
|
|
178
205
|
|
|
179
206
|
const WeakPasswordReasons = ['length', 'characters', 'pwned'] as const
|
|
@@ -2624,3 +2651,154 @@ export interface AuthOAuthServerApi {
|
|
|
2624
2651
|
*/
|
|
2625
2652
|
revokeGrant(options: { clientId: string }): Promise<AuthOAuthRevokeGrantResponse>
|
|
2626
2653
|
}
|
|
2654
|
+
|
|
2655
|
+
// --- Passkey Types ---
|
|
2656
|
+
|
|
2657
|
+
/** Response from POST /passkeys/registration/options */
|
|
2658
|
+
export type PasskeyRegistrationOptionsResponse = {
|
|
2659
|
+
challenge_id: string
|
|
2660
|
+
options: ServerCredentialCreationOptions
|
|
2661
|
+
expires_at: number
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
/** Request body for POST /passkeys/registration/verify */
|
|
2665
|
+
export type PasskeyRegistrationVerifyParams = {
|
|
2666
|
+
challenge_id: string
|
|
2667
|
+
credential: RegistrationResponseJSON
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
/** Response from POST /passkeys/registration/verify */
|
|
2671
|
+
export type PasskeyMetadata = {
|
|
2672
|
+
id: string
|
|
2673
|
+
friendly_name?: string
|
|
2674
|
+
created_at: string
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
/** Response from POST /passkeys/authentication/options */
|
|
2678
|
+
export type PasskeyAuthenticationOptionsResponse = {
|
|
2679
|
+
challenge_id: string
|
|
2680
|
+
options: ServerCredentialRequestOptions
|
|
2681
|
+
expires_at: number
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
/** Request body for POST /passkeys/authentication/verify */
|
|
2685
|
+
export type PasskeyAuthenticationVerifyParams = {
|
|
2686
|
+
challenge_id: string
|
|
2687
|
+
credential: AuthenticationResponseJSON
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
/** Item in the passkeys list (GET /passkeys/ and admin list) */
|
|
2691
|
+
export type PasskeyListItem = {
|
|
2692
|
+
id: string
|
|
2693
|
+
friendly_name?: string
|
|
2694
|
+
created_at: string
|
|
2695
|
+
last_used_at?: string
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
// --- Passkey SDK Method Parameter/Response Types ---
|
|
2699
|
+
|
|
2700
|
+
export type SignInWithPasskeyCredentials = {
|
|
2701
|
+
options?: {
|
|
2702
|
+
captchaToken?: string
|
|
2703
|
+
signal?: AbortSignal
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
export type RegisterPasskeyCredentials = {
|
|
2708
|
+
options?: {
|
|
2709
|
+
signal?: AbortSignal
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
export type VerifyPasskeyRegistrationParams = {
|
|
2714
|
+
/** Challenge ID from startRegistration */
|
|
2715
|
+
challengeId: string
|
|
2716
|
+
/** Serialized credential from navigator.credentials.create() */
|
|
2717
|
+
credential: ServerCredentialResponse
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
export type StartPasskeyAuthenticationParams = {
|
|
2721
|
+
options?: {
|
|
2722
|
+
captchaToken?: string
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
export type VerifyPasskeyAuthenticationParams = {
|
|
2727
|
+
/** Challenge ID from startAuthentication */
|
|
2728
|
+
challengeId: string
|
|
2729
|
+
/** Serialized credential from navigator.credentials.get() */
|
|
2730
|
+
credential: ServerCredentialResponse
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
export type PasskeyUpdateParams = {
|
|
2734
|
+
/** UUID of the passkey to update */
|
|
2735
|
+
passkeyId: string
|
|
2736
|
+
/** New friendly name (max 120 chars) */
|
|
2737
|
+
friendlyName: string
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
export type PasskeyDeleteParams = {
|
|
2741
|
+
/** UUID of the passkey to delete */
|
|
2742
|
+
passkeyId: string
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
// --- Passkey Response Types ---
|
|
2746
|
+
|
|
2747
|
+
export type AuthPasskeyRegistrationOptionsResponse =
|
|
2748
|
+
RequestResult<PasskeyRegistrationOptionsResponse>
|
|
2749
|
+
export type AuthPasskeyRegistrationVerifyResponse = RequestResult<
|
|
2750
|
+
PasskeyMetadata,
|
|
2751
|
+
WebAuthnError | AuthError
|
|
2752
|
+
>
|
|
2753
|
+
export type AuthPasskeyAuthenticationOptionsResponse =
|
|
2754
|
+
RequestResult<PasskeyAuthenticationOptionsResponse>
|
|
2755
|
+
export type AuthPasskeyAuthenticationVerifyResponse = RequestResult<
|
|
2756
|
+
{ session: Session | null; user: User | null },
|
|
2757
|
+
WebAuthnError | AuthError
|
|
2758
|
+
>
|
|
2759
|
+
export type AuthPasskeyListResponse = RequestResult<PasskeyListItem[]>
|
|
2760
|
+
export type AuthPasskeyUpdateResponse = RequestResult<PasskeyListItem>
|
|
2761
|
+
export type AuthPasskeyDeleteResponse = RequestResult<null>
|
|
2762
|
+
|
|
2763
|
+
// --- Passkey Admin Types ---
|
|
2764
|
+
|
|
2765
|
+
export type AuthPasskeyAdminListParams = {
|
|
2766
|
+
userId: string
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
export type AuthPasskeyAdminDeleteParams = {
|
|
2770
|
+
userId: string
|
|
2771
|
+
passkeyId: string
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
// --- Passkey Namespace Interfaces ---
|
|
2775
|
+
|
|
2776
|
+
/**
|
|
2777
|
+
* Lower-level two-step API and management methods for passkeys.
|
|
2778
|
+
* Access via `supabase.auth.passkey`.
|
|
2779
|
+
*/
|
|
2780
|
+
export interface AuthPasskeyApi {
|
|
2781
|
+
// Two-step registration
|
|
2782
|
+
startRegistration(): Promise<AuthPasskeyRegistrationOptionsResponse>
|
|
2783
|
+
verifyRegistration(
|
|
2784
|
+
params: VerifyPasskeyRegistrationParams
|
|
2785
|
+
): Promise<AuthPasskeyRegistrationVerifyResponse>
|
|
2786
|
+
|
|
2787
|
+
// Two-step authentication
|
|
2788
|
+
startAuthentication(
|
|
2789
|
+
params?: StartPasskeyAuthenticationParams
|
|
2790
|
+
): Promise<AuthPasskeyAuthenticationOptionsResponse>
|
|
2791
|
+
verifyAuthentication(
|
|
2792
|
+
params: VerifyPasskeyAuthenticationParams
|
|
2793
|
+
): Promise<AuthPasskeyAuthenticationVerifyResponse>
|
|
2794
|
+
|
|
2795
|
+
// Management
|
|
2796
|
+
list(): Promise<AuthPasskeyListResponse>
|
|
2797
|
+
update(params: PasskeyUpdateParams): Promise<AuthPasskeyUpdateResponse>
|
|
2798
|
+
delete(params: PasskeyDeleteParams): Promise<AuthPasskeyDeleteResponse>
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
export interface GoTrueAdminPasskeyApi {
|
|
2802
|
+
listPasskeys(params: AuthPasskeyAdminListParams): Promise<AuthPasskeyListResponse>
|
|
2803
|
+
deletePasskey(params: AuthPasskeyAdminDeleteParams): Promise<AuthPasskeyDeleteResponse>
|
|
2804
|
+
}
|