diff --git a/src/GoTrueClient.ts b/src/GoTrueClient.ts index c105be617..5df03e58c 100644 --- a/src/GoTrueClient.ts +++ b/src/GoTrueClient.ts @@ -70,12 +70,15 @@ import type { VerifyOtpParams, GoTrueMFAApi, MFAEnrollParams, + MFAVerifyParams, AuthMFAEnrollResponse, MFAChallengeParams, AuthMFAChallengeResponse, MFAUnenrollParams, AuthMFAUnenrollResponse, - MFAVerifyParams, + MFAVerifyTOTPParams, + MFAVerifyPhoneParams, + MFAVerifyWebAuthnParams, AuthMFAVerifyResponse, AuthMFAListFactorsResponse, AMREntry, @@ -88,11 +91,13 @@ import type { LockFunc, UserIdentity, SignInAnonymouslyCredentials, - MFAEnrollTOTPParams, AuthMFAEnrollTOTPResponse, + AuthMFAEnrollPhoneResponse, + AuthMFAEnrollWebAuthnResponse, AuthMFAEnrollErrorResponse, + MFAEnrollTOTPParams, MFAEnrollPhoneParams, - AuthMFAEnrollPhoneResponse, + MFAEnrollWebAuthnParams, } from './lib/types' polyfillGlobalThis() // Make "globalThis" available @@ -2364,6 +2369,9 @@ export default class GoTrueClient { private async _enroll( params: MFAEnrollPhoneParams ): Promise + private async _enroll( + params: MFAEnrollWebAuthnParams + ): Promise private async _enroll(params: MFAEnrollParams): Promise { try { return await this._useSession(async (result) => { @@ -2410,7 +2418,9 @@ export default class GoTrueClient { /** * {@see GoTrueMFAApi#verify} */ - private async _verify(params: MFAVerifyParams): Promise { + private async _verify(params: MFAVerifyTOTPParams): Promise + private async _verify(params: MFAVerifyPhoneParams): Promise + private async _verify(params: MFAVerifyWebAuthnParams): Promise { return this._acquireLock(-1, async () => { try { return await this._useSession(async (result) => { @@ -2418,13 +2428,29 @@ export default class GoTrueClient { if (sessionError) { return { data: null, error: sessionError } } + let requestBody: Record + if ('code' in params) { + // This handles MFAVerifyTOTPParams and MFAVerifyPhoneParams + requestBody = { + code: params.code, + challenge_id: params.challengeId, + } + } else { + // This handles MFAVerifyWebAuthnParams + requestBody = { + challenge_id: params.challengeId, + } + if (params.useMultiStepVerify !== undefined) { + requestBody.use_multi_step_verify = params.useMultiStepVerify + } + } const { data, error } = await _request( this.fetch, 'POST', `${this.url}/factors/${params.factorId}/verify`, { - body: { code: params.code, challenge_id: params.challengeId }, + body: requestBody, headers: this.headers, jwt: sessionData?.session?.access_token, } @@ -2526,11 +2552,16 @@ export default class GoTrueClient { (factor) => factor.factor_type === 'phone' && factor.status === 'verified' ) + const webauthn = factors.filter( + (factor) => factor.factor_type === 'webauthn' && factor.status === 'verified' + ) + return { data: { all: factors, totp, phone, + webauthn, }, error: null, } diff --git a/src/lib/types.ts b/src/lib/types.ts index a2b9c3c30..28184ed17 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -808,6 +808,7 @@ export type MFAEnrollTOTPParams = { /** Human readable name assigned to the factor. */ friendlyName?: string } + export type MFAEnrollPhoneParams = { /** The type of factor being enrolled. */ factorType: 'phone' @@ -816,14 +817,23 @@ export type MFAEnrollPhoneParams = { /** Phone number associated with a factor. Number should conform to E.164 format */ phone: string } -export type MFAEnrollParams = MFAEnrollTOTPParams | MFAEnrollPhoneParams -export type MFAUnenrollParams = { - /** ID of the factor being unenrolled. */ - factorId: string +export type MFAEnrollWebAuthnParams = { + /** The type of factor being enrolled. */ + factorType: 'webauthn' + /** Domain which the user is enrolled with. */ + issuer?: string + /** Human readable name assigned to the factor. */ + friendlyName?: string + + /** WebAuthn specific parameters*/ + webAuthn?: Object + + /** Have the Auth client library handle the browser-authenticator interaction for you */ + useMultiStepEnroll: boolean } -export type MFAVerifyParams = { +export type MFAVerifyTOTPParams = { /** ID of the factor being verified. Returned in enroll(). */ factorId: string @@ -834,6 +844,29 @@ export type MFAVerifyParams = { code: string } +// Declared as a separate type to allow for future changes +export type MFAVerifyPhoneParams = MFAVerifyTOTPParams + +export type MFAVerifyWebAuthnParams = { + /** ID of the factor being verified. Returned in enroll(). */ + factorId: string + + /** ID of the challenge being verified. Returned in challenge(). */ + challengeId: string + + /** Have the Auth client library handle the browser-authenticator interaction for you */ + useMultiStepVerify?: boolean +} + +export type MFAEnrollParams = MFAEnrollTOTPParams | MFAEnrollPhoneParams | MFAEnrollWebAuthnParams + +export type MFAVerifyParams = MFAVerifyTOTPParams | MFAVerifyPhoneParams | MFAVerifyWebAuthnParams + +export type MFAUnenrollParams = { + /** ID of the factor being unenrolled. */ + factorId: string +} + export type MFAChallengeParams = { /** ID of the factor to be challenged. Returned in enroll(). */ factorId: string @@ -918,13 +951,30 @@ export type AuthMFAEnrollPhoneResponse = { } error: null } + +export type AuthMFAEnrollWebAuthnResponse = { + data: { + /** ID of the factor that was just enrolled (in an unverified state). */ + id: string + + /** Type of MFA factor. */ + type: 'phone' + + /** Friendly name of the factor, useful for distinguishing between factors **/ + friendly_name?: string + } + error: null +} + export type AuthMFAEnrollErrorResponse = { data: null error: AuthError } + export type AuthMFAEnrollResponse = | AuthMFAEnrollTOTPResponse | AuthMFAEnrollPhoneResponse + | AuthMFAEnrollWebAuthnResponse | AuthMFAEnrollErrorResponse export type AuthMFAUnenrollResponse = @@ -963,6 +1013,9 @@ export type AuthMFAListFactorsResponse = totp: Factor[] /** Only verified Phone factors. (A subset of `all`.) */ phone: Factor[] + /** Only verified Phone factors. (A subset of `all`.) */ + // TODO: Hgihglight that it's not webAuthn since totp and phone it's lower case, and then delete this comment. + webauthn: Factor[] } error: null }