diff --git a/packages/@magic-ext/oauth2/src/index.ts b/packages/@magic-ext/oauth2/src/index.ts index e7ddde91c..d698dd765 100644 --- a/packages/@magic-ext/oauth2/src/index.ts +++ b/packages/@magic-ext/oauth2/src/index.ts @@ -9,7 +9,14 @@ import { OAuthPopupConfiguration, OAuthVerificationConfiguration, } from './types'; -import { OAuthPopupEventEmit, OAuthPopupEventHandlers, OAuthPopupEventOnReceived } from '@magic-sdk/types'; +import { + OAuthMFAEventEmit, + OAuthMFAEventOnReceived, + OAuthPopupEventEmit, + OAuthPopupEventHandlers, + OAuthPopupEventOnReceived, + OAuthGetResultEventHandlers, +} from '@magic-sdk/types'; declare global { interface Window { @@ -83,13 +90,15 @@ export class OAuthExtension extends Extension.Internal<'oauth2'> { const urlWithoutQuery = window.location.origin + window.location.pathname; window.history.replaceState(null, '', urlWithoutQuery); - return getResult.call(this, configuration, queryString); + return this.getResult(configuration, queryString); } public loginWithPopup(configuration: OAuthPopupConfiguration) { + const { showMfaModal } = configuration; const requestPayload = this.utils.createJsonRpcRequestPayload(OAuthPayloadMethods.Popup, [ { ...configuration, + showUI: showMfaModal, returnTo: window.location.href, apiKey: this.sdk.apiKey, platform: 'web', @@ -102,33 +111,145 @@ export class OAuthExtension extends Extension.Internal<'oauth2'> { requestPayload, ); + /** + * Attach Event listeners + */ const redirectEvent = (event: MessageEvent) => { this.createIntermediaryEvent(OAuthPopupEventEmit.PopupEvent, requestPayload.id as string)(event.data); }; - if (configuration.shouldReturnURI) { + if (configuration.shouldReturnURI && oauthPopupRequest) { oauthPopupRequest.on(OAuthPopupEventOnReceived.PopupUrl, popupUrl => { window.addEventListener('message', redirectEvent); promiEvent.emit(OAuthPopupEventOnReceived.PopupUrl, popupUrl); }); } + if (!showMfaModal) { + oauthPopupRequest.on(OAuthMFAEventOnReceived.MfaSentHandle, () => { + promiEvent.emit(OAuthMFAEventOnReceived.MfaSentHandle); + }); + oauthPopupRequest.on(OAuthMFAEventOnReceived.InvalidMfaOtp, () => { + promiEvent.emit(OAuthMFAEventOnReceived.InvalidMfaOtp); + }); + oauthPopupRequest.on(OAuthMFAEventOnReceived.RecoveryCodeSentHandle, () => { + promiEvent.emit(OAuthMFAEventOnReceived.RecoveryCodeSentHandle); + }); + oauthPopupRequest.on(OAuthMFAEventOnReceived.InvalidRecoveryCode, () => { + promiEvent.emit(OAuthMFAEventOnReceived.InvalidRecoveryCode); + }); + oauthPopupRequest.on(OAuthMFAEventOnReceived.RecoveryCodeSuccess, () => { + promiEvent.emit(OAuthMFAEventOnReceived.RecoveryCodeSuccess); + }); + } + const result = await oauthPopupRequest; window.removeEventListener('message', redirectEvent); - if ((result as OAuthRedirectError).error) { - reject(result); + const maybeResult = result as OAuthRedirectResult; + const maybeError = result as OAuthRedirectError; + + if (maybeError.error) { + reject( + this.createError(maybeError.error, maybeError.error_description ?? 'An error occurred.', { + errorURI: maybeError.error_uri, + provider: maybeError.provider, + }), + ); } else { - resolve(result as OAuthRedirectResult); + resolve(maybeResult); } } catch (error) { reject(error); } }); - promiEvent.on(OAuthPopupEventEmit.Cancel, () => { - this.createIntermediaryEvent(OAuthPopupEventEmit.Cancel, requestPayload.id as string)(); - }); + if (!showMfaModal && promiEvent) { + promiEvent.on(OAuthMFAEventEmit.VerifyMFACode, (mfa: string) => { + this.createIntermediaryEvent(OAuthMFAEventEmit.VerifyMFACode, requestPayload.id as string)(mfa); + }); + promiEvent.on(OAuthMFAEventEmit.LostDevice, () => { + this.createIntermediaryEvent(OAuthMFAEventEmit.LostDevice, requestPayload.id as string)(); + }); + promiEvent.on(OAuthMFAEventEmit.VerifyRecoveryCode, (recoveryCode: string) => { + this.createIntermediaryEvent(OAuthMFAEventEmit.VerifyRecoveryCode, requestPayload.id as string)(recoveryCode); + }); + promiEvent.on(OAuthMFAEventEmit.Cancel, () => { + this.createIntermediaryEvent(OAuthMFAEventEmit.Cancel, requestPayload.id as string)(); + }); + } + + return promiEvent; + } + + private getResult(configuration: OAuthVerificationConfiguration, queryString: string) { + const { showMfaModal } = configuration; + const requestPayload = this.utils.createJsonRpcRequestPayload(OAuthPayloadMethods.Verify, [ + { + authorizationResponseParams: queryString, + magicApiKey: this.sdk.apiKey, + platform: 'web', + showUI: showMfaModal, + ...configuration, + }, + ]); + + const promiEvent = this.utils.createPromiEvent( + async (resolve, reject) => { + const getResultRequest = this.request( + requestPayload, + ); + + if (!showMfaModal) { + getResultRequest.on(OAuthMFAEventOnReceived.MfaSentHandle, () => { + promiEvent.emit(OAuthMFAEventOnReceived.MfaSentHandle); + }); + getResultRequest.on(OAuthMFAEventOnReceived.InvalidMfaOtp, () => { + promiEvent.emit(OAuthMFAEventOnReceived.InvalidMfaOtp); + }); + getResultRequest.on(OAuthMFAEventOnReceived.RecoveryCodeSentHandle, () => { + promiEvent.emit(OAuthMFAEventOnReceived.RecoveryCodeSentHandle); + }); + getResultRequest.on(OAuthMFAEventOnReceived.InvalidRecoveryCode, () => { + promiEvent.emit(OAuthMFAEventOnReceived.InvalidRecoveryCode); + }); + getResultRequest.on(OAuthMFAEventOnReceived.RecoveryCodeSuccess, () => { + promiEvent.emit(OAuthMFAEventOnReceived.RecoveryCodeSuccess); + }); + } + + // Parse the result, which may contain an OAuth-formatted error. + const resultOrError = await getResultRequest; + const maybeResult = resultOrError as OAuthRedirectResult; + const maybeError = resultOrError as OAuthRedirectError; + + if (maybeError.error) { + reject( + this.createError(maybeError.error, maybeError.error_description ?? 'An error occurred.', { + errorURI: maybeError.error_uri, + provider: maybeError.provider, + }), + ); + } + + resolve(maybeResult); + }, + ); + + if (!showMfaModal && promiEvent) { + promiEvent.on(OAuthMFAEventEmit.VerifyMFACode, (mfa: string) => { + this.createIntermediaryEvent(OAuthMFAEventEmit.VerifyMFACode, requestPayload.id as string)(mfa); + }); + promiEvent.on(OAuthMFAEventEmit.LostDevice, () => { + this.createIntermediaryEvent(OAuthMFAEventEmit.LostDevice, requestPayload.id as string)(); + }); + promiEvent.on(OAuthMFAEventEmit.VerifyRecoveryCode, (recoveryCode: string) => { + this.createIntermediaryEvent(OAuthMFAEventEmit.VerifyRecoveryCode, requestPayload.id as string)(recoveryCode); + }); + promiEvent.on(OAuthMFAEventEmit.Cancel, () => { + this.createIntermediaryEvent(OAuthMFAEventEmit.Cancel, requestPayload.id as string)(); + }); + } return promiEvent; } @@ -160,33 +281,4 @@ export class OAuthExtension extends Extension.Internal<'oauth2'> { } } -function getResult(this: OAuthExtension, configuration: OAuthVerificationConfiguration, queryString: string) { - return this.utils.createPromiEvent(async (resolve, reject) => { - const parseRedirectResult = this.utils.createJsonRpcRequestPayload(OAuthPayloadMethods.Verify, [ - { - authorizationResponseParams: queryString, - magicApiKey: this.sdk.apiKey, - platform: 'web', - ...configuration, - }, - ]); - - // Parse the result, which may contain an OAuth-formatted error. - const resultOrError = await this.request(parseRedirectResult); - const maybeResult = resultOrError as OAuthRedirectResult; - const maybeError = resultOrError as OAuthRedirectError; - - if (maybeError.error) { - reject( - this.createError(maybeError.error, maybeError.error_description ?? 'An error occurred.', { - errorURI: maybeError.error_uri, - provider: maybeError.provider, - }), - ); - } - - resolve(maybeResult); - }); -} - export * from './types'; diff --git a/packages/@magic-ext/oauth2/src/types.ts b/packages/@magic-ext/oauth2/src/types.ts index 7afa83b85..e9b4f6013 100644 --- a/packages/@magic-ext/oauth2/src/types.ts +++ b/packages/@magic-ext/oauth2/src/types.ts @@ -109,6 +109,7 @@ export interface OAuthVerificationConfiguration { lifespan?: number; optionalQueryString?: string; skipDIDToken?: boolean; + showMfaModal?: boolean; } export interface OAuthPopupConfiguration { @@ -117,6 +118,7 @@ export interface OAuthPopupConfiguration { loginHint?: string; providerParams?: Record; shouldReturnURI?: boolean; + showMfaModal?: boolean; } export enum OAuthErrorCode { diff --git a/packages/@magic-ext/wallet-kit/src/MagicWidget.tsx b/packages/@magic-ext/wallet-kit/src/MagicWidget.tsx index 379e1c5a1..463baa46b 100644 --- a/packages/@magic-ext/wallet-kit/src/MagicWidget.tsx +++ b/packages/@magic-ext/wallet-kit/src/MagicWidget.tsx @@ -11,6 +11,7 @@ import { OAuthPendingView } from './views/OAuthPendingView'; import AdditionalProvidersView from './views/AdditionalProvidersView'; import { getExtensionInstance } from './extension'; import { EmailLoginProvider } from './context/EmailLoginContext'; +import { OAuthLoginProvider } from './context/OAuthLoginContext'; import { WidgetConfigProvider } from './context/WidgetConfigContext'; import { EmailOTPView } from './views/EmailOTPView'; import { DeviceVerificationView } from './views/DeviceVerificationView'; @@ -79,6 +80,7 @@ function WidgetContent({ ); @@ -109,12 +111,14 @@ function WidgetContent({ return ( - - - {renderView()} -