From 9fdcf35c0952d1b1c02329ec06f7a2e964f7c345 Mon Sep 17 00:00:00 2001 From: ieow Date: Mon, 24 Mar 2025 11:54:20 +0800 Subject: [PATCH 001/187] feat: initial commit seedlessOnboarding social login flow --- app.config.js | 8 +- app/actions/user/types.ts | 19 +- .../Oauth2Login/Oauth2LoginComponent.tsx | 50 ++++ app/components/Views/Onboarding/index.js | 53 +++- app/constants/deeplinks.ts | 2 + app/core/DeeplinkManager/DeeplinkManager.ts | 10 + .../Handlers/handleOauth2RedirectUrl.ts | 55 ++++ .../ParseManager/handleMetaMaskDeeplink.ts | 2 + app/core/Oauth2Login/utils.ts | 212 ++++++++++++++++ app/reducers/user/index.ts | 31 +++ app/reducers/user/types.ts | 4 + ios/MetaMask/Info.plist | 6 +- ios/MetaMask/MetaMask.entitlements | 4 + ios/MetaMask/MetaMaskDebug.entitlements | 4 + ios/Podfile.lock | 25 ++ locales/languages/en.json | 5 +- package.json | 3 + patches/expo-apple-authentication+6.1.2.patch | 12 + yarn.lock | 234 +++++++++++++++++- 19 files changed, 730 insertions(+), 9 deletions(-) create mode 100644 app/components/Oauth2Login/Oauth2LoginComponent.tsx create mode 100644 app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts create mode 100644 app/core/Oauth2Login/utils.ts create mode 100644 patches/expo-apple-authentication+6.1.2.patch diff --git a/app.config.js b/app.config.js index f1f87b695a49..b7806b051ae7 100644 --- a/app.config.js +++ b/app.config.js @@ -18,6 +18,10 @@ module.exports = { { subdomains: '*' } - ] - ] + ], + 'expo-apple-authentication', + ], + ios: { + usesAppleSignIn: true + } }; diff --git a/app/actions/user/types.ts b/app/actions/user/types.ts index ce1e41a30049..300872d75ca7 100644 --- a/app/actions/user/types.ts +++ b/app/actions/user/types.ts @@ -24,6 +24,11 @@ export enum UserActionType { SET_APP_THEME = 'SET_APP_THEME', CHECKED_AUTH = 'CHECKED_AUTH', SET_APP_SERVICES_READY = 'SET_APP_SERVICES_READY', + + OAUTH2_LOGIN = 'OAUTH2_LOGIN', + OAUTH2_LOGIN_COMPLETE = 'OAUTH2_LOGIN_COMPLETE', + OAUTH2_LOGIN_SUCCESS = 'OAUTH2_LOGIN_SUCCESS', + OAUTH2_LOGIN_ERROR = 'OAUTH2_LOGIN_ERROR', } // User actions @@ -89,6 +94,14 @@ export type CheckedAuthAction = Action & { export type SetAppServicesReadyAction = Action; +export type OAuth2LoginAction = Action; + +export type OAuth2LoginSuccessAction = Action & { payload: { existingUser: boolean } }; + +export type OAuth2LoginErrorAction = Action & { payload: { error: string } }; + +export type OAuth2LoginCompleteAction = Action; + /** * User actions union type */ @@ -113,4 +126,8 @@ export type UserAction = | SetGasEducationCarouselSeenAction | SetAppThemeAction | CheckedAuthAction - | SetAppServicesReadyAction; + | SetAppServicesReadyAction + | OAuth2LoginAction + | OAuth2LoginSuccessAction + | OAuth2LoginErrorAction + | OAuth2LoginCompleteAction; diff --git a/app/components/Oauth2Login/Oauth2LoginComponent.tsx b/app/components/Oauth2Login/Oauth2LoginComponent.tsx new file mode 100644 index 000000000000..01f0ce9662ae --- /dev/null +++ b/app/components/Oauth2Login/Oauth2LoginComponent.tsx @@ -0,0 +1,50 @@ +import React, { View, StyleSheet } from 'react-native'; +import handleOauth2Login from '../../core/Oauth2Login/utils'; +import StyledButton from '../UI/StyledButton'; +import { OnboardingSelectorIDs } from '../../../e2e/selectors/Onboarding/Onboarding.selectors'; +import { strings } from '../../../locales/i18n'; +import DevLogger from '../../core/SDKConnect/utils/DevLogger'; +import { useDispatch } from 'react-redux'; +import { UserAction, UserActionType } from '../../actions/user'; +import { Dispatch } from 'redux'; +const styles = StyleSheet.create({ + buttonWrapper: { + marginBottom: 16, + }, +}); + +export default function Oauth2LoginComponent( ) { + const dispatch = useDispatch>(); + + return ( + <> + + { + dispatch({ type: UserActionType.OAUTH2_LOGIN }); + handleOauth2Login('apple', dispatch).catch((e) => { + DevLogger.log(e); + }); + }} + > {strings('login.apple_button')} + + + { + dispatch({ type: UserActionType.OAUTH2_LOGIN }); + handleOauth2Login('google', dispatch).catch((e) => { + DevLogger.log(e); + }); + }} + > {strings('login.google_button')} + + + ); +} + diff --git a/app/components/Views/Onboarding/index.js b/app/components/Views/Onboarding/index.js index f20f1c1caf44..7fc2d927cb6c 100644 --- a/app/components/Views/Onboarding/index.js +++ b/app/components/Views/Onboarding/index.js @@ -49,6 +49,8 @@ import { selectAccounts } from '../../../selectors/accountTrackerController'; import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; import { trace, TraceName, TraceOperation } from '../../../util/trace'; import { MetricsEventBuilder } from '../../../core/Analytics/MetricsEventBuilder'; +import Oauth2LoginComponent from '../../Oauth2Login/Oauth2LoginComponent'; +import DevLogger from '../../../core/SDKConnect/utils/DevLogger'; const createStyles = (colors) => StyleSheet.create({ @@ -172,8 +174,23 @@ class Onboarding extends PureComponent { * Metrics injected by withMetricsAwareness HOC */ metrics: PropTypes.object, + /** + * oauth2LoginInProgress + */ + oauth2LoginInProgress: PropTypes.bool, + /** + * oauth2LoginError + */ + oauth2LoginError: PropTypes.string, + /** + * oauth2LoginSuccess + */ + oauth2LoginSuccess: PropTypes.bool, + /** + * oauth2LoginExistingUser + */ + oauth2LoginExistingUser: PropTypes.bool, }; - notificationAnimated = new Animated.Value(100); detailsYAnimated = new Animated.Value(0); actionXAnimated = new Animated.Value(0); @@ -229,6 +246,33 @@ class Onboarding extends PureComponent { ); }; + updateOAuth2Login = () => { + const { oauth2LoginSuccess, oauth2LoginExistingUser, oauth2LoginError, oauth2LoginInProgress, navigation} = this.props; + // if oauth2LoginSuccess is true, navigate to home + DevLogger.log('updateOAuth2Login: oauth2LoginSuccess', oauth2LoginSuccess); + DevLogger.log('updateOAuth2Login: oauth2LoginExistingUser', oauth2LoginExistingUser); + DevLogger.log('updateOAuth2Login: oauth2LoginError', oauth2LoginError); + DevLogger.log('updateOAuth2Login: oauth2LoginInProgress', oauth2LoginInProgress); + if (oauth2LoginSuccess) { + if (oauth2LoginExistingUser) { + // TODO: handle existing user + // Navigate to Relogin Wallet + navigation.navigate('ChoosePassword'); + } else { + setTimeout(() => { + navigation.navigate('ChoosePassword'); + }, 1000); + } + } + if (oauth2LoginError) { + // TODO: handle error + // Show error message + } + if (oauth2LoginInProgress) { + // TODO: handle in progress + } + }; + componentDidMount() { this.updateNavBar(); this.mounted = true; @@ -254,6 +298,7 @@ class Onboarding extends PureComponent { } componentDidUpdate = () => { + this.updateOAuth2Login(); this.updateNavBar(); }; @@ -388,6 +433,7 @@ class Onboarding extends PureComponent { + ({ passwordSet: state.user.passwordSet, loading: state.user.loadingSet, loadingMsg: state.user.loadingMsg, + + oauth2LoginInProgress: state.user.oauth2LoginInProgress, + oauth2LoginError: state.user.oauth2LoginError, + oauth2LoginSuccess: state.user.oauth2LoginSuccess, + oauth2LoginExistingUser: state.user.oauth2LoginExistingUser, }); const mapDispatchToProps = (dispatch) => ({ diff --git a/app/constants/deeplinks.ts b/app/constants/deeplinks.ts index 439416c211b8..0ac973f8c6fe 100644 --- a/app/constants/deeplinks.ts +++ b/app/constants/deeplinks.ts @@ -27,6 +27,7 @@ export enum ACTIONS { SELL = 'sell', SELL_CRYPTO = 'sell-crypto', EMPTY = '', + OAUTH2_REDIRECT = 'oauth2-redirect', } export const PREFIXES = { @@ -43,5 +44,6 @@ export const PREFIXES = { [ACTIONS.SELL]: '', [ACTIONS.BUY_CRYPTO]: '', [ACTIONS.SELL_CRYPTO]: '', + [ACTIONS.OAUTH2_REDIRECT]: '', METAMASK: 'metamask://', }; diff --git a/app/core/DeeplinkManager/DeeplinkManager.ts b/app/core/DeeplinkManager/DeeplinkManager.ts index 114d1a60a669..cabca3803363 100644 --- a/app/core/DeeplinkManager/DeeplinkManager.ts +++ b/app/core/DeeplinkManager/DeeplinkManager.ts @@ -10,6 +10,8 @@ import switchNetwork from './Handlers/switchNetwork'; import parseDeeplink from './ParseManager/parseDeeplink'; import approveTransaction from './TransactionManager/approveTransaction'; import { RampType } from '../../reducers/fiatOrders/types'; +import handleOauth2RedirectUrl from './Handlers/handleOauth2RedirectUrl'; +import { ACTIONS, PREFIXES } from '../../constants/deeplinks'; class DeeplinkManager { public navigation: NavigationProp; @@ -87,6 +89,14 @@ class DeeplinkManager { rampType: RampType.SELL, }); } + // handle oauth2 redirect url + _handleOauth2RedirectUrl(url: string) { + handleOauth2RedirectUrl({ + deeplinkManager: this, + url, + base: PREFIXES.METAMASK + ACTIONS.OAUTH2_REDIRECT, + }); + } parse( url: string, diff --git a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts new file mode 100644 index 000000000000..b2e52d5e70e5 --- /dev/null +++ b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts @@ -0,0 +1,55 @@ +import { ACTIONS, PREFIXES } from '../../../constants/deeplinks'; +import DevLogger from '../../SDKConnect/utils/DevLogger'; +import { handleCodeFlow } from '../../Oauth2Login/utils'; +import DeeplinkManager from '../DeeplinkManager'; + + +function handleOauth2RedirectUrl({ + deeplinkManager, + url, + base, +}: { + deeplinkManager: DeeplinkManager; + url: string; + base: string; +}) { + const minusBase = url.replace(base, ''); + const params = new URLSearchParams(minusBase); + + const state = params.get('state'); + const code = params.get('code'); + const idToken = params.get('idToken'); + const accessToken = params.get('accessToken'); + + const provider = JSON.parse(state || '{}').provider; + + DevLogger.log('handleOauth2RedirectUrl: provider', provider); + DevLogger.log('handleOauth2RedirectUrl: code', code); + + if (!provider) { + DevLogger.log('handleOauth2RedirectUrl: no provider'); + return; + } + + if (code || idToken || accessToken ) { + handleCodeFlow({ code, idToken, accessToken, provider: provider as 'apple' | 'google'}, deeplinkManager.dispatch).catch((error) => { + DevLogger.log('handleOauth2RedirectUrl: error', error); + }); + return code; + } + + DevLogger.log('handleOauth2RedirectUrl: no code, idToken, or accessToken'); + // on failure ? + +// deeplinkManager.dispatch( +// showAlert({ +// isVisible: true, +// autodismiss: 5000, +// content: 'clipboard-alert', +// data: { msg: strings('social login failed')}, +// }), +// ); + +} + +export default handleOauth2RedirectUrl; diff --git a/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts b/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts index 544186f29d63..a291137d9e0d 100644 --- a/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts +++ b/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts @@ -152,6 +152,8 @@ export function handleMetaMaskDeeplink({ .replace(`${PREFIXES.METAMASK}${ACTIONS.SELL_CRYPTO}`, '') .replace(`${PREFIXES.METAMASK}${ACTIONS.SELL}`, ''); instance._handleSellCrypto(rampPath); + } else if (url.startsWith(`${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`)) { + instance._handleOauth2RedirectUrl(url); } } diff --git a/app/core/Oauth2Login/utils.ts b/app/core/Oauth2Login/utils.ts new file mode 100644 index 000000000000..303ab28d4955 --- /dev/null +++ b/app/core/Oauth2Login/utils.ts @@ -0,0 +1,212 @@ +import { signInAsync, AppleAuthenticationScope } from 'expo-apple-authentication'; +import { Platform } from 'react-native'; +import { + AuthRequest, + ResponseType, + CodeChallengeMethod + } from 'expo-auth-session'; +import {signInWithGoogle} from 'react-native-google-acm'; +import DevLogger from '../SDKConnect/utils/DevLogger'; +import { UserAction, UserActionType } from '../../actions/user'; +import { Dispatch } from 'redux'; +import { ACTIONS, PREFIXES } from '../../constants/deeplinks'; + +const AppRedirect = `${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`; + +const IosGID = '882363291751-nbbp9n0o307cfil1lup766g1s99k0932.apps.googleusercontent.com'; +const IosGoogleRedirectUri = 'com.googleusercontent.apps.882363291751-nbbp9n0o307cfil1lup766g1s99k0932:/oauth2redirect/google'; + +const AndroidWebGID = '882363291751-2a37cchrq9oc1lfj1p419otvahnbhguv.apps.googleusercontent.com'; +const AndroidGID = AndroidWebGID; +const AndroidAppleRedirectUri = 'https://simple-auth-server-jade.vercel.app/apple/redirect'; + + + +interface HandleFlowParams { + provider: 'apple' | 'google'; + code: string | null; + idToken: string | null; + accessToken: string | null; +} + +export const handleCodeFlow = async (data : HandleFlowParams, dispatch: Dispatch) => { + console.log(data); + + if (data.code) { + // exchange code for AuthToken from byoa server + } + else if (data.idToken) { + // exchange idToken for AuthToken from byoa server + } + else if (data.accessToken) { + // exchange accessToken for AuthToken from byoa server + } + else { + throw new Error('No code, idToken, or accessToken'); + } + + // const result = seedlessOnboardingController.authenticate(byoaAuthToken) + // const existingUser = result.existingUser; + + // dispatch Action for login success + dispatch({ + type: UserActionType.OAUTH2_LOGIN_SUCCESS, + payload: { + existingUser: true, + }, + }); +}; + + +const handleAppleLogin = async (dispatch: Dispatch) => { + if (Platform.OS === 'ios') { + try { + const credential = await signInAsync({ + requestedScopes: [ + AppleAuthenticationScope.FULL_NAME, + AppleAuthenticationScope.EMAIL, + ], + }); + + handleCodeFlow({ + provider: 'apple', + code: credential.authorizationCode, + idToken: null, + accessToken: null, + }, dispatch); + + return credential.authorizationCode ? 'success' : 'error'; + } catch (error) { + DevLogger.log('handleAppleLogin: error', error); + + dispatch({ + type: UserActionType.OAUTH2_LOGIN_ERROR, + payload: { + error: 'Apple login failed', + }, + }); + return 'error'; + } + } + else if (Platform.OS === 'android') { + const state = JSON.stringify({ + provider: 'apple', + redirectUri: AppRedirect, + random: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15), + }); + const authRequest = new AuthRequest({ + clientId: 'com.web3auth.appleloginextension', + redirectUri: AndroidAppleRedirectUri, + scopes: ['email', 'name'], + responseType: ResponseType.Code, + codeChallengeMethod: CodeChallengeMethod.S256, + state, + usePKCE: true, + extraParams: { + response_mode: 'form_post', + } + }); + const result = await authRequest.promptAsync({ + authorizationEndpoint: 'https://appleid.apple.com/auth/authorize', + }).catch((error: any) => { + DevLogger.log('handleAppleLogin: error', error); + return {type: 'error'}; + }); + + // Apple login use redirect flow thus no handleCodeFlow here + DevLogger.log('handleAppleLogin: result', result); + dispatch({ + type: UserActionType.OAUTH2_LOGIN_COMPLETE, + }); + return result.type; + } + throw new Error('Apple login is not supported on this platform'); +}; + + +const handleGoogleLogin = async(dispatch: Dispatch) => { + if (Platform.OS === 'ios') { + const authRequest = new AuthRequest({ + clientId: IosGID, + redirectUri: IosGoogleRedirectUri, + scopes: ['email', 'profile'], + responseType: ResponseType.Code, + usePKCE: true, + + }); + const result = await authRequest.promptAsync({ + authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth', + }); + + DevLogger.log('handleGoogleLogin: result', result); + + if (result.type === 'success') { + handleCodeFlow({ + provider: 'google', + code: result.params.code, // result.params.idToken + idToken: null, + accessToken: null, + }, dispatch); + } else if (result.type === 'error') { + dispatch({ + type: UserActionType.OAUTH2_LOGIN_ERROR, + payload: { + error: 'Google login failed', + }, + }); + } else { + dispatch({ type: UserActionType.OAUTH2_LOGIN_COMPLETE }); + } + + return result.type; + } + else if (Platform.OS === 'android') { + + const result = await signInWithGoogle({ + serverClientId: AndroidGID, + nonce: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15), + autoSelectEnabled: true, + }).catch((error: any) => { + DevLogger.log('handleGoogleLogin: error', error); + return {type: 'error'}; + }); + + DevLogger.log('handleGoogleLogin: result', result); + + if (result.type === 'success') { + handleCodeFlow({ + provider: 'google', + code: result.params.code, // result.params.idToken + idToken: null, + accessToken: null, + }, dispatch); + } else if (result.type === 'error') { + dispatch({ + type: UserActionType.OAUTH2_LOGIN_ERROR, + payload: { + error: result.params.error, + }, + }); + } else { + dispatch({ type: UserActionType.OAUTH2_LOGIN_COMPLETE }); + } + + return result.type; + } + + throw new Error('Google login is not supported on this platform'); +}; + + +const handleOauth2Login = (provider: 'apple' | 'google', dispatch: Dispatch) => { + if (provider === 'apple') { + return handleAppleLogin(dispatch); + } + else if (provider === 'google') { + return handleGoogleLogin(dispatch); + } + throw new Error('Invalid provider'); +}; + + +export default handleOauth2Login; diff --git a/app/reducers/user/index.ts b/app/reducers/user/index.ts index 1ff742fdb580..6b1508d09fcd 100644 --- a/app/reducers/user/index.ts +++ b/app/reducers/user/index.ts @@ -23,6 +23,10 @@ export const userInitialState: UserState = { appTheme: AppThemeKey.os, ambiguousAddressEntries: {}, appServicesReady: false, + oauth2LoginInProgress: false, + oauth2LoginSuccess: false, + oauth2LoginError: null, + oauth2LoginExistingUser: false, }; /** @@ -115,6 +119,33 @@ const userReducer = ( ...state, appServicesReady: true, }; + + case UserActionType.OAUTH2_LOGIN: + return { + ...state, + oauth2LoginInProgress: true, + oauth2LoginSuccess: false, + oauth2LoginError: null, + }; + case UserActionType.OAUTH2_LOGIN_COMPLETE: + return { + ...state, + oauth2LoginInProgress: false, + }; + case UserActionType.OAUTH2_LOGIN_SUCCESS: + return { + ...state, + oauth2LoginInProgress: false, + oauth2LoginSuccess: true, + oauth2LoginExistingUser: action.payload.existingUser, + }; + case UserActionType.OAUTH2_LOGIN_ERROR: + return { + ...state, + oauth2LoginInProgress: false, + oauth2LoginSuccess: false, + oauth2LoginError: action.payload.error, + }; default: return state; } diff --git a/app/reducers/user/types.ts b/app/reducers/user/types.ts index 18cad4998474..2ec740a55dc3 100644 --- a/app/reducers/user/types.ts +++ b/app/reducers/user/types.ts @@ -17,4 +17,8 @@ export interface UserState { appTheme: AppThemeKey; ambiguousAddressEntries: Record; appServicesReady: boolean; + oauth2LoginInProgress: boolean; + oauth2LoginError: string | null; + oauth2LoginSuccess: boolean; + oauth2LoginExistingUser: boolean; } diff --git a/ios/MetaMask/Info.plist b/ios/MetaMask/Info.plist index 23e491c44acc..045aabc483e1 100644 --- a/ios/MetaMask/Info.plist +++ b/ios/MetaMask/Info.plist @@ -2,8 +2,6 @@ - LSMinimumSystemVersion - 12.0.0 CFBundleDevelopmentRegion en CFBundleDisplayName @@ -11,7 +9,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconName - AppIcon + AppIcon CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion @@ -46,6 +44,8 @@ twitter itms-apps + LSMinimumSystemVersion + 12.0.0 LSRequiresIPhoneOS NSAppTransportSecurity diff --git a/ios/MetaMask/MetaMask.entitlements b/ios/MetaMask/MetaMask.entitlements index f29c11d5d6c1..dec440b1ed51 100644 --- a/ios/MetaMask/MetaMask.entitlements +++ b/ios/MetaMask/MetaMask.entitlements @@ -4,6 +4,10 @@ aps-environment development + com.apple.developer.applesignin + + Default + com.apple.developer.associated-domains applinks:metamask.io diff --git a/ios/MetaMask/MetaMaskDebug.entitlements b/ios/MetaMask/MetaMaskDebug.entitlements index 0ee0d28c177e..ceb0098df9d9 100644 --- a/ios/MetaMask/MetaMaskDebug.entitlements +++ b/ios/MetaMask/MetaMaskDebug.entitlements @@ -4,6 +4,10 @@ aps-environment development + com.apple.developer.applesignin + + Default + com.apple.developer.associated-domains applinks:metamask.io diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 3595aaee3a73..f6e7c88dbbc9 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -79,6 +79,10 @@ PODS: - expo-dev-menu/SafeAreaView - RCT-Folly (= 2021.07.22.00) - React-Core + - ExpoAppleAuthentication (6.1.2): + - ExpoModulesCore + - ExpoCrypto (12.4.1): + - ExpoModulesCore - ExpoKeepAwake (12.3.0): - ExpoModulesCore - ExpoModulesCore (1.5.13): @@ -87,6 +91,8 @@ PODS: - React-NativeModulesApple - React-RCTAppDelegate - ReactCommon/turbomodule/core + - ExpoWebBrowser (12.3.2): + - ExpoModulesCore - EXUpdatesInterface (0.12.0) - FBLazyVector (0.72.15) - FBReactNativeSpec (0.72.15): @@ -206,6 +212,9 @@ PODS: - FlipperKit/FlipperKitNetworkPlugin - fmt (6.2.1) - glog (0.3.5) + - GoogleAcm (0.1.0): + - RCT-Folly (= 2021.07.22.00) + - React-Core - GoogleAppMeasurement (10.29.0): - GoogleAppMeasurement/AdIdSupport (= 10.29.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) @@ -888,8 +897,11 @@ DEPENDENCIES: - expo-dev-launcher (from `../node_modules/expo-dev-launcher`) - expo-dev-menu (from `../node_modules/expo-dev-menu`) - expo-dev-menu-interface (from `../node_modules/expo-dev-menu-interface/ios`) + - ExpoAppleAuthentication (from `../node_modules/expo-apple-authentication/ios`) + - ExpoCrypto (from `../node_modules/expo-crypto/ios`) - ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`) - ExpoModulesCore (from `../node_modules/expo-modules-core`) + - ExpoWebBrowser (from `../node_modules/expo-web-browser/ios`) - EXUpdatesInterface (from `../node_modules/expo-updates-interface/ios`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`) @@ -917,6 +929,7 @@ DEPENDENCIES: - FlipperKit/FlipperKitUserDefaultsPlugin (= 0.182.0) - FlipperKit/SKIOSNetworkPlugin (= 0.182.0) - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) + - GoogleAcm (from `../node_modules/react-native-google-acm`) - GoogleUtilities - GzipSwift - lottie-ios (from `../node_modules/lottie-ios`) @@ -1086,10 +1099,16 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-dev-menu" expo-dev-menu-interface: :path: "../node_modules/expo-dev-menu-interface/ios" + ExpoAppleAuthentication: + :path: "../node_modules/expo-apple-authentication/ios" + ExpoCrypto: + :path: "../node_modules/expo-crypto/ios" ExpoKeepAwake: :path: "../node_modules/expo-keep-awake/ios" ExpoModulesCore: :path: "../node_modules/expo-modules-core" + ExpoWebBrowser: + :path: "../node_modules/expo-web-browser/ios" EXUpdatesInterface: :path: "../node_modules/expo-updates-interface/ios" FBLazyVector: @@ -1098,6 +1117,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/React/FBReactNativeSpec" glog: :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" + GoogleAcm: + :path: "../node_modules/react-native-google-acm" lottie-ios: :path: "../node_modules/lottie-ios" lottie-react-native: @@ -1308,8 +1329,11 @@ SPEC CHECKSUMS: expo-dev-launcher: ada23ef0663a5a64be9af560507408add783844f expo-dev-menu: 6eb7fcd6d21151f4c2c2d679628d72879e3a5d2d expo-dev-menu-interface: 9eb98037fb7d9c500ad5734261b43911f06afe0d + ExpoAppleAuthentication: 944b1e3a31b4068fbae09f030323a468a71a2319 + ExpoCrypto: e0714ca676dc875ee772802cf20bcea6ec75c3e7 ExpoKeepAwake: 8ab1087501f5ccb91146447756b787575b13f13e ExpoModulesCore: 3312621274fbba06fe382a37d8218a43cd522823 + ExpoWebBrowser: 025c51f93c6a04beb169388877918de64ccae171 EXUpdatesInterface: 1f9cdd9e1e1026de7488ac537e9c40bc6b6fb872 FBLazyVector: 25cbffbaec517695d376ab4bc428948cd0f08088 FBReactNativeSpec: e03b22fbf7017a6f76641ea4472e73c915dcdda7 @@ -1330,6 +1354,7 @@ SPEC CHECKSUMS: FlipperKit: 2efad7007d6745a3f95e4034d547be637f89d3f6 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b + GoogleAcm: 8bb9e2d7b8effe2f178d3212fbb90719e8d52ab8 GoogleAppMeasurement: f9de05ee17401e3355f68e8fc8b5064d429f5918 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 diff --git a/locales/languages/en.json b/locales/languages/en.json index 324bd4e8279e..d5f9f6f7b490 100644 --- a/locales/languages/en.json +++ b/locales/languages/en.json @@ -256,7 +256,10 @@ "vault_error": "Error: Cannot unlock without a previous vault.", "clean_vault_error": "MetaMask encountered an error due to reaching a storage limit. The local data has been corrupted. Please reinstall MetaMask and restore with your Secret Recovery Phrase.", "security_alert_title": "Security Alert", - "security_alert_desc": "In order to proceed, you need to turn Passcode on or any biometrics authentication method supported in your device (FaceID, TouchID or Fingerprint)" + "security_alert_desc": "In order to proceed, you need to turn Passcode on or any biometrics authentication method supported in your device (FaceID, TouchID or Fingerprint)", + + "apple_button": "Sign in with Apple", + "google_button": "Sign in with Google" }, "connect_hardware": { "title_select_hardware": "Connect a hardware wallet", diff --git a/package.json b/package.json index 6a31f9cab0bb..41814d101589 100644 --- a/package.json +++ b/package.json @@ -281,6 +281,8 @@ "eventemitter2": "^6.4.9", "events": "3.0.0", "expo": "^49.0.0", + "expo-apple-authentication": "~6.1.0", + "expo-auth-session": "~5.0.2", "expo-build-properties": "~0.8.3", "expo-dev-client": "3.1.0", "fuse.js": "3.4.4", @@ -330,6 +332,7 @@ "react-native-fs": "^2.20.0", "react-native-gesture-handler": "^1.10.3", "react-native-get-random-values": "^1.8.0", + "react-native-google-acm": "git+https://github.com/ieow/react-native-google-acm.git#8360769aaaf1ded46c9823c8761df9d3811da63c", "react-native-gzip": "^1.1.0", "react-native-i18n": "2.0.15", "react-native-in-app-review": "^4.3.3", diff --git a/patches/expo-apple-authentication+6.1.2.patch b/patches/expo-apple-authentication+6.1.2.patch new file mode 100644 index 000000000000..f1f39dd027d0 --- /dev/null +++ b/patches/expo-apple-authentication+6.1.2.patch @@ -0,0 +1,12 @@ +diff --git a/node_modules/expo-apple-authentication/ios/AppleAuthenticationExceptions.swift b/node_modules/expo-apple-authentication/ios/AppleAuthenticationExceptions.swift +index 6eb50b1..24ac10a 100644 +--- a/node_modules/expo-apple-authentication/ios/AppleAuthenticationExceptions.swift ++++ b/node_modules/expo-apple-authentication/ios/AppleAuthenticationExceptions.swift +@@ -63,5 +63,7 @@ func exceptionForAuthorizationError(_ error: ASAuthorizationError) -> Exception + return RequestFailedException() + case .notInteractive: + return RequestNotInteractiveException() ++ default: ++ return RequestUnknownException() + } + } diff --git a/yarn.lock b/yarn.lock index dbe4207e7246..94d20fef8c53 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9922,6 +9922,11 @@ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce" integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg== +"@types/qs@^6.9.7": + version "6.9.18" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.18.tgz#877292caa91f7c1b213032b34626505b746624c2" + integrity sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA== + "@types/ramda@^0.27.40", "@types/ramda@^0.27.44": version "0.27.66" resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.27.66.tgz#f1a23d13b0087d806a62e3ff941e5e59b3318999" @@ -12586,7 +12591,7 @@ base64-js@1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== -base64-js@^1.0.2, base64-js@^1.1.2, base64-js@^1.2.3, base64-js@^1.3.1, base64-js@^1.5.1: +base64-js@^1.0.2, base64-js@^1.1.2, base64-js@^1.2.3, base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -13203,6 +13208,14 @@ caf@^15.0.1: resolved "https://registry.yarnpkg.com/caf/-/caf-15.0.1.tgz#28f1f17bd93dc4b5d95207ad07066eddf4768160" integrity sha512-Xp/IK6vMwujxWZXra7djdYzPdPnEQKa7Mudu2wZgDQ3TJry1I0TgtjEgwZHpoBcMp68j4fb0/FZ1SJyMEgJrXQ== +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" @@ -13214,6 +13227,14 @@ call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bin get-intrinsic "^1.2.4" set-function-length "^1.2.1" +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + call-me-maybe@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.2.tgz#03f964f19522ba643b1b0693acb9152fe2074baa" @@ -13874,6 +13895,13 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= +compare-urls@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/compare-urls/-/compare-urls-2.0.0.tgz#9b378c4abd43980a8700fffec9afb85de4df9075" + integrity sha512-eCJcWn2OYFEIqbm70ta7LQowJOOZZqq1a2YbbFCFI1uwSvj+TWMwXVn7vPR1ceFNcAIt5RSTDbwdlX82gYLTkA== + dependencies: + normalize-url "^2.0.1" + compare-versions@^3.4.0, compare-versions@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" @@ -15258,6 +15286,15 @@ dtrace-provider@~0.8: dependencies: nan "^2.14.0" +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + duplexer2@^0.1.2: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" @@ -15671,6 +15708,11 @@ es-define-property@^1.0.0: dependencies: get-intrinsic "^1.2.4" +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + es-errors@^1.2.1, es-errors@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" @@ -15723,6 +15765,13 @@ es-object-atoms@^1.0.0: dependencies: es-errors "^1.3.0" +es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + es-set-tostringtag@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" @@ -16625,6 +16674,11 @@ expect@^29.0.0, expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" +expo-apple-authentication@~6.1.0: + version "6.1.2" + resolved "https://registry.yarnpkg.com/expo-apple-authentication/-/expo-apple-authentication-6.1.2.tgz#46150a41988550a491c321effaf3d6cdaaf12be9" + integrity sha512-wXGrltitAu/MncQ7F16uQIZO5Uva2tof7pBa3pbEg1g6TAp4YQhJ1iwEeit0Jlk9J64R9c+kKzbyuRquWMzyyA== + expo-application@~5.3.0: version "5.3.1" resolved "https://registry.yarnpkg.com/expo-application/-/expo-application-5.3.1.tgz#074bbfc6bb5d65ae74a67f5288fa3eb582237e53" @@ -16643,6 +16697,18 @@ expo-asset@~8.10.1: path-browserify "^1.0.0" url-parse "^1.5.9" +expo-auth-session@~5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/expo-auth-session/-/expo-auth-session-5.0.2.tgz#1e9e6e7ef4d6784e77d770f0d3ea3a4adfc0e0b4" + integrity sha512-hzuIGATiyZ4ICuzSnCTTQLUA74eHGd1aaPydsSAQEAkMnNT2bMoIYLq1rp971xF+eqWz0lzMVboRYTnxuvEKJg== + dependencies: + expo-constants "~14.4.2" + expo-crypto "~12.4.0" + expo-linking "~5.0.0" + expo-web-browser "~12.3.0" + invariant "^2.2.4" + qs "^6.11.0" + expo-build-properties@~0.12.1: version "0.12.5" resolved "https://registry.yarnpkg.com/expo-build-properties/-/expo-build-properties-0.12.5.tgz#4d6232389f00c846ba37ca5df2c0b5527c2d94ca" @@ -16667,6 +16733,13 @@ expo-constants@~14.4.2: "@expo/config" "~8.1.0" uuid "^3.3.2" +expo-crypto@~12.4.0: + version "12.4.1" + resolved "https://registry.yarnpkg.com/expo-crypto/-/expo-crypto-12.4.1.tgz#f3ee7156fd8167c3c3304b6e21dcfb0dc8a44bba" + integrity sha512-/en03oPNAX6gP0bKpwA1EyLBnGG9uv0+Q7uvGYyOXaQQEvj31a+8cEvNPkv75x6GuK1hcaBfO25RtX9AGOMwVA== + dependencies: + base64-js "^1.3.0" + expo-dev-client@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/expo-dev-client/-/expo-dev-client-3.1.0.tgz#23359d2f765fa5e47dec2cd8be57e0428d0177fb" @@ -16725,6 +16798,17 @@ expo-keep-awake@~12.3.0: resolved "https://registry.yarnpkg.com/expo-keep-awake/-/expo-keep-awake-12.3.0.tgz#c42449ae19c993274ddc43aafa618792b6aec408" integrity sha512-ujiJg1p9EdCOYS05jh5PtUrfiZnK0yyLy+UewzqrjUqIT8eAGMQbkfOn3C3fHE7AKd5AefSMzJnS3lYZcZYHDw== +expo-linking@~5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/expo-linking/-/expo-linking-5.0.2.tgz#273c9dfec0c5542a13638bd422ef9acbf4638bc5" + integrity sha512-SPQus0+tYGx9c69Uw4wmdo3rkKX8vRT1vyJz/mvkpSlZN986s0NmP/V0M5vDv5Zv2qZzVdqJyuITFe0Pg5aI+A== + dependencies: + "@types/qs" "^6.9.7" + expo-constants "~14.4.2" + invariant "^2.2.4" + qs "^6.11.0" + url-parse "^1.5.9" + expo-manifests@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/expo-manifests/-/expo-manifests-0.9.0.tgz#69cfa3fb574129eb23df80e89fec3f9e90857313" @@ -16758,6 +16842,14 @@ expo-updates-interface@~0.12.0: resolved "https://registry.yarnpkg.com/expo-updates-interface/-/expo-updates-interface-0.12.0.tgz#76827311bca274b179cabead3e27ca594b3bf4c4" integrity sha512-0vg/8RrmuQH9yA+FLV8XfeDj9sJai/10v65yzgHF6rPLGuAoT6vH/5hOSvHwtaEFnLLRycViyh4EuIU5Res++g== +expo-web-browser@~12.3.0: + version "12.3.2" + resolved "https://registry.yarnpkg.com/expo-web-browser/-/expo-web-browser-12.3.2.tgz#45ac727a5d8462d7faa403ea2fa1db160ed8e4b5" + integrity sha512-ohBf+vnRnGzlTleY8EQ2XQU0vRdRwqMJtKkzM9MZRPDOK5QyJYPJjpk6ixGhxdeoUG2Ogj0InvhhgX9NUn4jkg== + dependencies: + compare-urls "^2.0.0" + url "^0.11.0" + expo@^49.0.0: version "49.0.23" resolved "https://registry.yarnpkg.com/expo/-/expo-49.0.23.tgz#b8dc4daecdc2e912607a4bc63dede5506017976d" @@ -17724,6 +17816,22 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" +get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + get-nonce@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" @@ -17759,6 +17867,14 @@ get-port@^5.1.1: resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + get-stdin@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" @@ -18068,6 +18184,11 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + got@^11.0.2, got@^11.8.1: version "11.8.5" resolved "https://registry.yarnpkg.com/got/-/got-11.8.5.tgz#ce77d045136de56e8f024bebb82ea349bc730046" @@ -18219,6 +18340,11 @@ has-symbols@^1.0.2, has-symbols@^1.0.3: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" @@ -19137,6 +19263,11 @@ is-path-inside@^3.0.2, is-path-inside@^3.0.3: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== +is-plain-obj@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== + is-plain-obj@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" @@ -21161,6 +21292,16 @@ marky@^1.2.2: resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.5.tgz#55796b688cbd72390d2d399eaaf1832c9413e3c0" integrity sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q== +material-colors@^1.2.1: + version "1.2.6" + resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46" + integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg== + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + md5-file@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/md5-file/-/md5-file-3.2.3.tgz#f9bceb941eca2214a4c0727f5e700314e770f06f" @@ -22387,6 +22528,15 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +normalize-url@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" + integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw== + dependencies: + prepend-http "^2.0.0" + query-string "^5.0.1" + sort-keys "^2.0.0" + normalize-url@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" @@ -22543,6 +22693,11 @@ object-inspect@^1.10.3, object-inspect@^1.13.1, object-inspect@^1.6.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== +object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + object-is@^1.0.1, object-is@^1.1.2, object-is@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" @@ -23525,6 +23680,11 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA== + prettier-linter-helpers@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" @@ -23948,6 +24108,13 @@ qs@6.13.0, qs@^6.10.0, qs@^6.11.2, qs@^6.5.2: dependencies: side-channel "^1.0.6" +qs@^6.11.0: + version "6.14.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" + integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== + dependencies: + side-channel "^1.1.0" + qs@~6.5.2: version "6.5.3" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" @@ -23977,6 +24144,15 @@ query-string@7.1.3: split-on-first "^1.0.0" strict-uri-encode "^2.0.0" +query-string@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" + integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== + dependencies: + decode-uri-component "^0.2.0" + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + query-string@^6.12.1, query-string@^6.13.6: version "6.14.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a" @@ -24331,6 +24507,10 @@ react-native-get-random-values@^1.8.0: dependencies: fast-base64-decode "^1.0.0" +"react-native-google-acm@git+https://github.com/ieow/react-native-google-acm.git#8360769aaaf1ded46c9823c8761df9d3811da63c": + version "0.1.0" + resolved "git+https://github.com/ieow/react-native-google-acm.git#8360769aaaf1ded46c9823c8761df9d3811da63c" + react-native-gzip@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/react-native-gzip/-/react-native-gzip-1.1.0.tgz#726396afddc348f60d5acc8bfaed89cc4066bacb" @@ -26004,6 +26184,35 @@ shell-quote@1.8.1, shell-quote@^1.6.1, shell-quote@^1.7.2, shell-quote@^1.7.3, s resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + side-channel@^1.0.4, side-channel@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" @@ -26014,6 +26223,17 @@ side-channel@^1.0.4, side-channel@^1.0.6: get-intrinsic "^1.2.4" object-inspect "^1.13.1" +side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -26212,6 +26432,13 @@ sonic-boom@^2.2.1: dependencies: atomic-sleep "^1.0.0" +sort-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" + integrity sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg== + dependencies: + is-plain-obj "^1.0.0" + source-map-js@^1.0.2, source-map-js@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" @@ -26496,6 +26723,11 @@ streamx@^2.15.0: fast-fifo "^1.1.0" queue-tick "^1.0.1" +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + integrity sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ== + strict-uri-encode@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" From e5a2d389b4146c2493663cde2735266e0fbce138 Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 26 Mar 2025 11:02:22 +0800 Subject: [PATCH 002/187] fix: remove unused code --- app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts index b2e52d5e70e5..c45968e7703d 100644 --- a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts +++ b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts @@ -1,4 +1,3 @@ -import { ACTIONS, PREFIXES } from '../../../constants/deeplinks'; import DevLogger from '../../SDKConnect/utils/DevLogger'; import { handleCodeFlow } from '../../Oauth2Login/utils'; import DeeplinkManager from '../DeeplinkManager'; From deaf1552c0a17cd90e4548ee602721d8db4000d3 Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 26 Mar 2025 15:08:05 +0800 Subject: [PATCH 003/187] feat: initial seedless onboarding controller integration --- app/actions/seedlessOnboarding/index.ts | 75 +++++++++++++++++++ app/actions/user/types.ts | 6 +- app/components/Views/ChoosePassword/index.js | 4 + app/components/Views/Login/index.js | 2 + app/components/Views/Onboarding/index.js | 26 +++++-- app/core/Authentication/Authentication.ts | 13 ++++ .../handleMetaMaskDeeplink.test.ts | 23 +++++- app/core/Engine/Engine.ts | 15 +++- app/core/Engine/constants.ts | 1 + app/core/Engine/messengers/index.ts | 5 ++ .../index.ts | 24 ++++++ app/core/Engine/types.ts | 11 ++- app/core/EngineService/EngineService.ts | 4 + app/core/Oauth2Login/utils.ts | 5 +- app/reducers/user/index.ts | 7 ++ package.json | 1 + yarn.lock | 14 ++-- 17 files changed, 218 insertions(+), 18 deletions(-) create mode 100644 app/actions/seedlessOnboarding/index.ts create mode 100644 app/core/Engine/messengers/seedless-onboarding-controller-messenger/index.ts diff --git a/app/actions/seedlessOnboarding/index.ts b/app/actions/seedlessOnboarding/index.ts new file mode 100644 index 000000000000..154f5a695a8d --- /dev/null +++ b/app/actions/seedlessOnboarding/index.ts @@ -0,0 +1,75 @@ +import { getErrorMessage } from '@metamask/utils'; +import Engine from '../../core/Engine'; +import { AuthenticateUserParams } from '@metamask/seedless-onboarding-controller'; + + +// /** +// * Action to signal that app services are ready +// */ +// export function setAppServicesReady(): SetAppServicesReadyAction { +// return { +// type: UserActionType.SET_APP_SERVICES_READY, +// }; +// } +// export function createAndBackupSeedPhrase( +// password: string, +// oAuthLoginInfo: { +// verifier: OAuthProvider; +// idToken: string; +// verifierId: string; +// }, +// ): ThunkAction { +// return async (dispatch: MetaMaskReduxDispatch) => { +// // dispatch(showLoadingIndication()); + +// try { +// await createNewVault(password); +// const seedPhrase = await getSeedPhrase(password); +// const { verifier, idToken, verifierId } = oAuthLoginInfo; +// await backupSeedPhrase(seedPhrase, password, idToken, verifier, verifierId); +// return seedPhrase; +// } catch (error) { +// dispatch(displayWarning(error)); +// if (isErrorWithMessage(error)) { +// throw new Error(getErrorMessage(error)); +// } else { +// throw error; +// } +// } finally { +// dispatch(hideLoadingIndication()); +// } +// }; +// } + +export const performeSeedlessOnboardingAuthenticate = async (authToken: AuthenticateUserParams) => { + try { + const result = await Engine.context.SeedlessOnboardingController.authenticateOAuthUser(authToken); + return result; + } catch (error) { + return getErrorMessage(error); + } +}; + +export const performSeedlessOnboardingCreate = async (params: {password: string, seedPhrase: string}) => { + try { + const result = await Engine.context.SeedlessOnboardingController.createSeedPhraseBackup(params); + // if (!result) { + // return getErrorMessage(identityErrors.PERFORM_SIGN_IN); + // } + return result; + } catch (error) { + return getErrorMessage(error); + } +}; + +export const performSeedlessOnboardingRehydrate = async (password: string) => { + try { + const result = await Engine.context.SeedlessOnboardingController.fetchAndRestoreSeedPhraseMetadata(password); + // if (!result) { + // return getErrorMessage(identityErrors.PERFORM_SIGN_IN); + // } + return result; + } catch (error) { + return getErrorMessage(error); + } +}; diff --git a/app/actions/user/types.ts b/app/actions/user/types.ts index 300872d75ca7..37009d4e6373 100644 --- a/app/actions/user/types.ts +++ b/app/actions/user/types.ts @@ -26,6 +26,7 @@ export enum UserActionType { SET_APP_SERVICES_READY = 'SET_APP_SERVICES_READY', OAUTH2_LOGIN = 'OAUTH2_LOGIN', + OAUTH2_LOGIN_RESET = 'OAUTH2_LOGIN_RESET', OAUTH2_LOGIN_COMPLETE = 'OAUTH2_LOGIN_COMPLETE', OAUTH2_LOGIN_SUCCESS = 'OAUTH2_LOGIN_SUCCESS', OAUTH2_LOGIN_ERROR = 'OAUTH2_LOGIN_ERROR', @@ -102,6 +103,8 @@ export type OAuth2LoginErrorAction = Action & export type OAuth2LoginCompleteAction = Action; +export type OAuth2LoginResetAction = Action; + /** * User actions union type */ @@ -130,4 +133,5 @@ export type UserAction = | OAuth2LoginAction | OAuth2LoginSuccessAction | OAuth2LoginErrorAction - | OAuth2LoginCompleteAction; + | OAuth2LoginCompleteAction + | OAuth2LoginResetAction; diff --git a/app/components/Views/ChoosePassword/index.js b/app/components/Views/ChoosePassword/index.js index 52eb87d2c1f2..4cd86db3b214 100644 --- a/app/components/Views/ChoosePassword/index.js +++ b/app/components/Views/ChoosePassword/index.js @@ -356,6 +356,10 @@ class ChoosePassword extends PureComponent { } catch (error) { if (Device.isIos) await this.handleRejectedOsBiometricPrompt(); } + + // get seedphrase from vault + // TODO: seedless onboarding create ( {password, seedPhrase} ); + this.keyringControllerPasswordSet = true; this.props.seedphraseNotBackedUp(); } else { diff --git a/app/components/Views/Login/index.js b/app/components/Views/Login/index.js index 1d2ac4f452f5..3104af6dd24b 100644 --- a/app/components/Views/Login/index.js +++ b/app/components/Views/Login/index.js @@ -380,6 +380,8 @@ class Login extends PureComponent { onLogin = async () => { endTrace({ name: TraceName.LoginUserInteraction }); + // if password is not set, and seedlessOnboarding.state.nodeAuthTokens exist, we proceed with seedless onboarding rehydration + const { password } = this.state; const { current: field } = this.fieldRef; const locked = !passwordRequirementsMet(password); diff --git a/app/components/Views/Onboarding/index.js b/app/components/Views/Onboarding/index.js index 7fc2d927cb6c..15d26b83a5dd 100644 --- a/app/components/Views/Onboarding/index.js +++ b/app/components/Views/Onboarding/index.js @@ -32,7 +32,7 @@ import { import Device from '../../../util/device'; import BaseNotification from '../../UI/Notification/BaseNotification'; import ElevatedView from 'react-native-elevated-view'; -import { loadingSet, loadingUnset } from '../../../actions/user'; +import { loadingSet, loadingUnset, UserActionType } from '../../../actions/user'; import { storePrivacyPolicyClickedOrClosed as storePrivacyPolicyClickedOrClosedAction } from '../../../reducers/legalNotices'; import PreventScreenshot from '../../../core/PreventScreenshot'; import WarningExistingUserModal from '../../UI/WarningExistingUserModal'; @@ -162,6 +162,10 @@ class Onboarding extends PureComponent { * unset loading status */ unsetLoading: PropTypes.func, + /** + * oauth2LoginReset + */ + oauth2LoginReset: PropTypes.func, /** * loadings msg */ @@ -246,22 +250,29 @@ class Onboarding extends PureComponent { ); }; - updateOAuth2Login = () => { + updateOAuth2Login = async () => { const { oauth2LoginSuccess, oauth2LoginExistingUser, oauth2LoginError, oauth2LoginInProgress, navigation} = this.props; // if oauth2LoginSuccess is true, navigate to home DevLogger.log('updateOAuth2Login: oauth2LoginSuccess', oauth2LoginSuccess); DevLogger.log('updateOAuth2Login: oauth2LoginExistingUser', oauth2LoginExistingUser); DevLogger.log('updateOAuth2Login: oauth2LoginError', oauth2LoginError); - DevLogger.log('updateOAuth2Login: oauth2LoginInProgress', oauth2LoginInProgress); + // eslint-disable-next-line no-console + console.log('updateOAuth2Login: oauth2LoginInProgress', oauth2LoginInProgress); + if (oauth2LoginSuccess) { if (oauth2LoginExistingUser) { // TODO: handle existing user // Navigate to Relogin Wallet - navigation.navigate('ChoosePassword'); + this.props.oauth2LoginReset(); + await Authentication.lockApp(); + navigation.navigate(Routes.ONBOARDING.LOGIN); } else { - setTimeout(() => { - navigation.navigate('ChoosePassword'); - }, 1000); + this.props.oauth2LoginReset(); + // eslint-disable-next-line no-console + console.log('updateOAuth2Login: navigate to ChoosePassword'); + this.props.navigation.navigate('ChoosePassword', { + [PREVIOUS_SCREEN]: ONBOARDING, + }); } } if (oauth2LoginError) { @@ -553,6 +564,7 @@ const mapDispatchToProps = (dispatch) => ({ unsetLoading: () => dispatch(loadingUnset()), disableNewPrivacyPolicyToast: () => dispatch(storePrivacyPolicyClickedOrClosedAction()), + oauth2LoginReset: () => dispatch({ type: UserActionType.OAUTH2_LOGIN_RESET }), }); export default connect( diff --git a/app/core/Authentication/Authentication.ts b/app/core/Authentication/Authentication.ts index ea4bf865def6..b4d56a2da998 100644 --- a/app/core/Authentication/Authentication.ts +++ b/app/core/Authentication/Authentication.ts @@ -100,6 +100,8 @@ class AuthenticationService { // eslint-disable-next-line @typescript-eslint/no-explicit-any const { KeyringController }: any = Engine.context; await Engine.resetState(); + // eslint-disable-next-line no-console + console.log('KeyringController.state', KeyringController.state); await KeyringController.createNewVaultAndKeychain(password); password = this.wipeSensitiveData(); }; @@ -170,7 +172,11 @@ class AuthenticationService { // eslint-disable-next-line @typescript-eslint/no-explicit-any const { KeyringController }: any = Engine.context; // Restore vault with empty password + // eslint-disable-next-line no-console + console.log('resetVault: KeyringController', KeyringController); await KeyringController.submitPassword(''); + // eslint-disable-next-line no-console + console.log('resetVault: KeyringController after submitPassword', KeyringController); await this.resetPassword(); }; @@ -308,6 +314,13 @@ class AuthenticationService { await this.storePassword(password, authData?.currentAuthType); await StorageWrapper.setItem(EXISTING_USER, TRUE); await StorageWrapper.removeItem(SEED_PHRASE_HINTS); + + + // if seedless onboarding is enabled, we need to create a seedphrase backup + // if (SeedlessOnboardingController.state.authToken.length > 0) { + // await SeedlessOnboardingController.createSeedPhraseBackup(password, seedPhrase); + // } + this.dispatchLogin(); this.authData = authData; // TODO: Replace "any" with type diff --git a/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.test.ts b/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.test.ts index 9c8020392f15..52db6b8e9304 100644 --- a/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.test.ts +++ b/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.test.ts @@ -22,6 +22,7 @@ jest.mock('../../../core/NativeModules', () => ({ describe('handleMetaMaskProtocol', () => { const mockParse = jest.fn(); + const mockHandleOauth2RedirectUrl = jest.fn(); const mockHandleBuyCrypto = jest.fn(); const mockHandleSellCrypto = jest.fn(); const mockHandleBrowserUrl = jest.fn(); @@ -33,7 +34,6 @@ describe('handleMetaMaskProtocol', () => { const mockGetApprovedHosts = jest.fn(); const mockBindAndroidSDK = jest.fn(); const mockNavigate = jest.fn(); - const mockHandleDeeplink = handleDeeplink as jest.Mock; const mockSDKConnectGetInstance = SDKConnect.getInstance as jest.Mock; const mockWC2ManagerGetInstance = WC2Manager.getInstance as jest.Mock; @@ -43,6 +43,7 @@ describe('handleMetaMaskProtocol', () => { _handleBuyCrypto: mockHandleBuyCrypto, _handleSellCrypto: mockHandleSellCrypto, _handleBrowserUrl: mockHandleBrowserUrl, + _handleOauth2RedirectUrl: mockHandleOauth2RedirectUrl, } as unknown as DeeplinkManager; const handled = jest.fn(); @@ -396,4 +397,24 @@ describe('handleMetaMaskProtocol', () => { expect(mockHandleSellCrypto).toHaveBeenCalled(); }); }); + + + describe('when url start with ${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}', () => { + beforeEach(() => { + url = `${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`; + }); + + it('should call _handleOauth2RedirectUrl', () => { + handleMetaMaskDeeplink({ + instance, + handled, + params, + url, + origin, + wcURL, + }); + + expect(mockHandleOauth2RedirectUrl).toHaveBeenCalled(); + }); + }); }); diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts index 6a65f08ea179..a6851cf5afb7 100644 --- a/app/core/Engine/Engine.ts +++ b/app/core/Engine/Engine.ts @@ -207,6 +207,8 @@ import { EarnController } from '@metamask/earn-controller'; import { TransactionControllerInit } from './controllers/transaction-controller'; import I18n from '../../../locales/i18n'; import { Platform } from '@metamask/profile-sync-controller/sdk'; +// import { SeedlessOnboardingController } from '@metamask/seedless-onboarding-controller'; +import { SeedlessOnboardingController } from '@metamask/seedless-onboarding-controller'; const NON_EMPTY = 'NON_EMPTY'; @@ -1008,6 +1010,14 @@ export class Engine { fetchFn: fetch, }); + const seedlessOnboardingController = new SeedlessOnboardingController({ + messenger: this.controllerMessenger.getRestricted({ + name: 'SeedlessOnboardingController', + allowedActions: [], + allowedEvents: ['KeyringController:stateChange'], + }), + }); + const existingControllersByName = { ApprovalController: approvalController, GasFeeController: gasFeeController, @@ -1015,6 +1025,7 @@ export class Engine { NetworkController: networkController, PreferencesController: preferencesController, SmartTransactionsController: this.smartTransactionsController, + // seedlessOnboardingController }; const initRequest = { @@ -1041,6 +1052,7 @@ export class Engine { MultichainBalancesController: multichainBalancesControllerInit, MultichainTransactionsController: multichainTransactionsControllerInit, ///: END:ONLY_INCLUDE_IF + // SeedlessOnboardingController: seedlessOnboardingControllerInit, }, persistedState: initialState as EngineState, existingControllersByName, @@ -1050,7 +1062,7 @@ export class Engine { const accountsController = controllersByName.AccountsController; const transactionController = controllersByName.TransactionController; - + // const seedlessOnboardingController = seedlessOnboardingController; // Backwards compatibility for existing references this.accountsController = accountsController; this.transactionController = transactionController; @@ -1408,6 +1420,7 @@ export class Engine { BridgeController: bridgeController, BridgeStatusController: bridgeStatusController, EarnController: earnController, + SeedlessOnboardingController: seedlessOnboardingController, }; const childControllers = Object.assign({}, this.context); diff --git a/app/core/Engine/constants.ts b/app/core/Engine/constants.ts index 5d9a172ed9b7..a24aecf2623f 100644 --- a/app/core/Engine/constants.ts +++ b/app/core/Engine/constants.ts @@ -65,4 +65,5 @@ export const BACKGROUND_STATE_CHANGE_EVENT_NAMES = [ 'BridgeController:stateChange', 'BridgeStatusController:stateChange', 'EarnController:stateChange', + 'SeedlessOnboardingController:stateChange', ] as const; diff --git a/app/core/Engine/messengers/index.ts b/app/core/Engine/messengers/index.ts index ab8d501e8250..599bff8dc4df 100644 --- a/app/core/Engine/messengers/index.ts +++ b/app/core/Engine/messengers/index.ts @@ -22,6 +22,7 @@ import { getTransactionControllerInitMessenger, getTransactionControllerMessenger, } from './transaction-controller-messenger'; +import { getSeedlessOnboardingControllerMessenger } from './seedless-onboarding-controller-messenger'; /** * The messengers for the controllers that have been. @@ -83,4 +84,8 @@ export const CONTROLLER_MESSENGERS = { getInitMessenger: noop, }, ///: END:ONLY_INCLUDE_IF + SeedlessOnboardingController: { + getMessenger: getSeedlessOnboardingControllerMessenger, + getInitMessenger: noop, + }, } as const; diff --git a/app/core/Engine/messengers/seedless-onboarding-controller-messenger/index.ts b/app/core/Engine/messengers/seedless-onboarding-controller-messenger/index.ts new file mode 100644 index 000000000000..ba639f750a06 --- /dev/null +++ b/app/core/Engine/messengers/seedless-onboarding-controller-messenger/index.ts @@ -0,0 +1,24 @@ +import { BaseControllerMessenger } from '../../types'; + +export type SeedlessOnboardingControllerMessenger = ReturnType< + typeof getSeedlessOnboardingControllerMessenger +>; + +/** + * Get the SeedlessOnboardingControllerMessenger for the SeedlessOnboardingController. + * + * @param baseControllerMessenger - The base controller messenger. + * @returns The SeedlessOnboardingControllerMessenger. + */ +export function getSeedlessOnboardingControllerMessenger( + baseControllerMessenger: BaseControllerMessenger, +) { + return baseControllerMessenger.getRestricted({ + name: 'SeedlessOnboardingController', + allowedEvents: [ + 'KeyringController:stateChange', + ], + allowedActions: [ + ], + }); +} diff --git a/app/core/Engine/types.ts b/app/core/Engine/types.ts index 18873ad3c78b..d4eed0c16d46 100644 --- a/app/core/Engine/types.ts +++ b/app/core/Engine/types.ts @@ -254,6 +254,12 @@ import { EarnControllerEvents, EarnControllerState, } from '@metamask/earn-controller'; +import { + SeedlessOnboardingController, + SeedlessOnboardingControllerState, + SeedlessOnboardingControllerStateChangeEvent, +} from '@metamask/seedless-onboarding-controller'; + import { Hex } from '@metamask/utils'; import { CONTROLLER_MESSENGERS } from './messengers'; @@ -390,7 +396,8 @@ type GlobalEvents = | MultichainNetworkControllerEvents | BridgeControllerEvents | BridgeStatusControllerEvents - | EarnControllerEvents; + | EarnControllerEvents + | SeedlessOnboardingControllerStateChangeEvent; // TODO: Abstract this into controller utils for TransactionController export interface TransactionEventPayload { @@ -469,6 +476,7 @@ export type Controllers = { BridgeController: BridgeController; BridgeStatusController: BridgeStatusController; EarnController: EarnController; + SeedlessOnboardingController: SeedlessOnboardingController; }; /** @@ -530,6 +538,7 @@ export type EngineState = { BridgeController: BridgeControllerState; BridgeStatusController: BridgeStatusControllerState; EarnController: EarnControllerState; + SeedlessOnboardingController: SeedlessOnboardingControllerState; }; /** Controller names */ diff --git a/app/core/EngineService/EngineService.ts b/app/core/EngineService/EngineService.ts index 6f6c6e46a040..b8e074b38875 100644 --- a/app/core/EngineService/EngineService.ts +++ b/app/core/EngineService/EngineService.ts @@ -47,6 +47,7 @@ export class EngineService { */ start = async () => { const reduxState = ReduxService.store.getState(); + // ReduxService.store.dispatch({ type: UPDATE_BG_STATE_KEY }); trace({ name: TraceName.EngineInitialization, op: TraceOperation.EngineInitialization, @@ -54,6 +55,9 @@ export class EngineService { tags: getTraceTags(reduxState), }); const state = reduxState?.engine?.backgroundState ?? {}; + // const state = {}; + // eslint-disable-next-line no-console + console.log('EngineService: rstate', state); const Engine = UntypedEngine; try { Logger.log(`${LOG_TAG}: Initializing Engine:`, { diff --git a/app/core/Oauth2Login/utils.ts b/app/core/Oauth2Login/utils.ts index 303ab28d4955..96f36734aeff 100644 --- a/app/core/Oauth2Login/utils.ts +++ b/app/core/Oauth2Login/utils.ts @@ -48,11 +48,12 @@ export const handleCodeFlow = async (data : HandleFlowParams, dispatch: Dispatch // const result = seedlessOnboardingController.authenticate(byoaAuthToken) // const existingUser = result.existingUser; + console.log('handleCodeFlow: dispatching OAUTH2_LOGIN_SUCCESS'); // dispatch Action for login success dispatch({ type: UserActionType.OAUTH2_LOGIN_SUCCESS, payload: { - existingUser: true, + existingUser: false, }, }); }; @@ -155,7 +156,7 @@ const handleGoogleLogin = async(dispatch: Dispatch) => { }, }); } else { - dispatch({ type: UserActionType.OAUTH2_LOGIN_COMPLETE }); + // dispatch({ type: UserActionType.OAUTH2_LOGIN_COMPLETE }); } return result.type; diff --git a/app/reducers/user/index.ts b/app/reducers/user/index.ts index 6b1508d09fcd..1b35847aa2e3 100644 --- a/app/reducers/user/index.ts +++ b/app/reducers/user/index.ts @@ -146,6 +146,13 @@ const userReducer = ( oauth2LoginSuccess: false, oauth2LoginError: action.payload.error, }; + case UserActionType.OAUTH2_LOGIN_RESET: + return { + ...state, + oauth2LoginInProgress: false, + oauth2LoginSuccess: false, + oauth2LoginError: null, + }; default: return state; } diff --git a/package.json b/package.json index 41814d101589..47edac3afe3b 100644 --- a/package.json +++ b/package.json @@ -204,6 +204,7 @@ "@metamask/rpc-errors": "^7.0.2", "@metamask/scure-bip39": "^2.1.0", "@metamask/sdk-communication-layer": "0.29.0-wallet", + "@metamask/seedless-onboarding-controller": "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?7e3d33696713d2644480de02409db6f570b56fc4", "@metamask/selected-network-controller": "^21.0.0", "@metamask/signature-controller": "^23.1.0", "@metamask/slip44": "^4.1.0", diff --git a/yarn.lock b/yarn.lock index 94d20fef8c53..1eb1ed5e6f51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5357,6 +5357,15 @@ utf-8-validate "^5.0.2" uuid "^8.3.2" +"@metamask/seedless-onboarding-controller@https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?7e3d33696713d2644480de02409db6f570b56fc4": + version "0.0.1" + resolved "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?7e3d33696713d2644480de02409db6f570b56fc4#c00513c1d5b5a918f64872c0dd160fde3c383c2d" + dependencies: + "@metamask/base-controller" "^8.0.0" + "@metamask/browser-passworder" "^4.3.0" + "@metamask/keyring-controller" "^21.0.0" + "@metamask/utils" "^11.2.0" + "@metamask/selected-network-controller@^21.0.0": version "21.0.0" resolved "https://registry.yarnpkg.com/@metamask/selected-network-controller/-/selected-network-controller-21.0.0.tgz#d1972a2af8a241497c490c32efb1dabd39e75c52" @@ -21292,11 +21301,6 @@ marky@^1.2.2: resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.5.tgz#55796b688cbd72390d2d399eaaf1832c9413e3c0" integrity sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q== -material-colors@^1.2.1: - version "1.2.6" - resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46" - integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg== - math-intrinsics@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" From 36d330e121f47ac97c5876bd222f81a8dd1b2e81 Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 28 Mar 2025 12:25:35 +0800 Subject: [PATCH 004/187] feat: refactor, integrate byoa --- .../Oauth2Login/Oauth2LoginComponent.tsx | 24 +- app/components/Views/Onboarding/index.js | 35 --- .../Handlers/handleOauth2RedirectUrl.ts | 49 ++-- app/core/Engine/Engine.ts | 16 +- .../seedless-onboarding-controller/index.ts | 36 +++ app/core/Engine/types.ts | 3 +- app/core/Oauth2Login/utils.ts | 245 ++++++++---------- 7 files changed, 188 insertions(+), 220 deletions(-) create mode 100644 app/core/Engine/controllers/seedless-onboarding-controller/index.ts diff --git a/app/components/Oauth2Login/Oauth2LoginComponent.tsx b/app/components/Oauth2Login/Oauth2LoginComponent.tsx index 01f0ce9662ae..d3605743558c 100644 --- a/app/components/Oauth2Login/Oauth2LoginComponent.tsx +++ b/app/components/Oauth2Login/Oauth2LoginComponent.tsx @@ -4,9 +4,8 @@ import StyledButton from '../UI/StyledButton'; import { OnboardingSelectorIDs } from '../../../e2e/selectors/Onboarding/Onboarding.selectors'; import { strings } from '../../../locales/i18n'; import DevLogger from '../../core/SDKConnect/utils/DevLogger'; -import { useDispatch } from 'react-redux'; -import { UserAction, UserActionType } from '../../actions/user'; -import { Dispatch } from 'redux'; +import { useNavigation, ParamListBase, NavigationProp } from '@react-navigation/native'; + const styles = StyleSheet.create({ buttonWrapper: { marginBottom: 16, @@ -14,8 +13,7 @@ const styles = StyleSheet.create({ }); export default function Oauth2LoginComponent( ) { - const dispatch = useDispatch>(); - + const navigation = useNavigation>(); return ( <> @@ -24,10 +22,14 @@ export default function Oauth2LoginComponent( ) { type={'normal'} testID={OnboardingSelectorIDs.IMPORT_SEED_BUTTON} onPress={async () => { - dispatch({ type: UserActionType.OAUTH2_LOGIN }); - handleOauth2Login('apple', dispatch).catch((e) => { + const result = await handleOauth2Login('apple' ).catch((e) => { DevLogger.log(e); + return {type: 'error', error: e}; }); + + if (result.type === 'success') { + navigation.navigate('ChoosePassword'); + } }} > {strings('login.apple_button')} @@ -37,10 +39,14 @@ export default function Oauth2LoginComponent( ) { type={'normal'} testID={OnboardingSelectorIDs.IMPORT_SEED_BUTTON} onPress={async () => { - dispatch({ type: UserActionType.OAUTH2_LOGIN }); - handleOauth2Login('google', dispatch).catch((e) => { + const result = await handleOauth2Login('google').catch((e) => { DevLogger.log(e); + return {type: 'error', error: e}; }); + + if (result.type === 'success') { + navigation.navigate('ChoosePassword'); + } }} > {strings('login.google_button')} diff --git a/app/components/Views/Onboarding/index.js b/app/components/Views/Onboarding/index.js index 15d26b83a5dd..41c38663569f 100644 --- a/app/components/Views/Onboarding/index.js +++ b/app/components/Views/Onboarding/index.js @@ -250,40 +250,6 @@ class Onboarding extends PureComponent { ); }; - updateOAuth2Login = async () => { - const { oauth2LoginSuccess, oauth2LoginExistingUser, oauth2LoginError, oauth2LoginInProgress, navigation} = this.props; - // if oauth2LoginSuccess is true, navigate to home - DevLogger.log('updateOAuth2Login: oauth2LoginSuccess', oauth2LoginSuccess); - DevLogger.log('updateOAuth2Login: oauth2LoginExistingUser', oauth2LoginExistingUser); - DevLogger.log('updateOAuth2Login: oauth2LoginError', oauth2LoginError); - // eslint-disable-next-line no-console - console.log('updateOAuth2Login: oauth2LoginInProgress', oauth2LoginInProgress); - - if (oauth2LoginSuccess) { - if (oauth2LoginExistingUser) { - // TODO: handle existing user - // Navigate to Relogin Wallet - this.props.oauth2LoginReset(); - await Authentication.lockApp(); - navigation.navigate(Routes.ONBOARDING.LOGIN); - } else { - this.props.oauth2LoginReset(); - // eslint-disable-next-line no-console - console.log('updateOAuth2Login: navigate to ChoosePassword'); - this.props.navigation.navigate('ChoosePassword', { - [PREVIOUS_SCREEN]: ONBOARDING, - }); - } - } - if (oauth2LoginError) { - // TODO: handle error - // Show error message - } - if (oauth2LoginInProgress) { - // TODO: handle in progress - } - }; - componentDidMount() { this.updateNavBar(); this.mounted = true; @@ -309,7 +275,6 @@ class Onboarding extends PureComponent { } componentDidUpdate = () => { - this.updateOAuth2Login(); this.updateNavBar(); }; diff --git a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts index c45968e7703d..42c7c944d51a 100644 --- a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts +++ b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts @@ -15,40 +15,39 @@ function handleOauth2RedirectUrl({ const minusBase = url.replace(base, ''); const params = new URLSearchParams(minusBase); - const state = params.get('state'); - const code = params.get('code'); - const idToken = params.get('idToken'); - const accessToken = params.get('accessToken'); + const state = JSON.parse(params.get('state') ?? '{}'); + const code = params.get('code') ?? undefined; - const provider = JSON.parse(state || '{}').provider; + const provider = state.provider as 'apple' | 'google'; + const clientId = state.clientId as string; DevLogger.log('handleOauth2RedirectUrl: provider', provider); DevLogger.log('handleOauth2RedirectUrl: code', code); - if (!provider) { - DevLogger.log('handleOauth2RedirectUrl: no provider'); - return; - } - - if (code || idToken || accessToken ) { - handleCodeFlow({ code, idToken, accessToken, provider: provider as 'apple' | 'google'}, deeplinkManager.dispatch).catch((error) => { + console.log('handleOauth2RedirectUrl: state', state); + if (code ) { + handleCodeFlow({ code, provider , clientId, redirectUri: state.redirectUri}) + .then((result) => { + if (result.status === 'success') { + // get current route + // const currentRoute = deeplinkManager.navigation.getCurrentRoute(); + deeplinkManager.navigation.navigate('ChoosePassword'); + return code; + } + }).catch((error) => { DevLogger.log('handleOauth2RedirectUrl: error', error); }); - return code; + } else { + // deeplinkManager.dispatch( + // showAlert({ + // isVisible: true, + // autodismiss: 5000, + // content: 'clipboard-alert', + // data: { msg: strings('social login failed')}, + // }), + // ); } - DevLogger.log('handleOauth2RedirectUrl: no code, idToken, or accessToken'); - // on failure ? - -// deeplinkManager.dispatch( -// showAlert({ -// isVisible: true, -// autodismiss: 5000, -// content: 'clipboard-alert', -// data: { msg: strings('social login failed')}, -// }), -// ); - } export default handleOauth2RedirectUrl; diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts index a6851cf5afb7..4a59473f0639 100644 --- a/app/core/Engine/Engine.ts +++ b/app/core/Engine/Engine.ts @@ -207,8 +207,7 @@ import { EarnController } from '@metamask/earn-controller'; import { TransactionControllerInit } from './controllers/transaction-controller'; import I18n from '../../../locales/i18n'; import { Platform } from '@metamask/profile-sync-controller/sdk'; -// import { SeedlessOnboardingController } from '@metamask/seedless-onboarding-controller'; -import { SeedlessOnboardingController } from '@metamask/seedless-onboarding-controller'; +import { seedlessOnboardingControllerInit } from './controllers/seedless-onboarding-controller'; const NON_EMPTY = 'NON_EMPTY'; @@ -1010,14 +1009,6 @@ export class Engine { fetchFn: fetch, }); - const seedlessOnboardingController = new SeedlessOnboardingController({ - messenger: this.controllerMessenger.getRestricted({ - name: 'SeedlessOnboardingController', - allowedActions: [], - allowedEvents: ['KeyringController:stateChange'], - }), - }); - const existingControllersByName = { ApprovalController: approvalController, GasFeeController: gasFeeController, @@ -1025,7 +1016,6 @@ export class Engine { NetworkController: networkController, PreferencesController: preferencesController, SmartTransactionsController: this.smartTransactionsController, - // seedlessOnboardingController }; const initRequest = { @@ -1052,7 +1042,7 @@ export class Engine { MultichainBalancesController: multichainBalancesControllerInit, MultichainTransactionsController: multichainTransactionsControllerInit, ///: END:ONLY_INCLUDE_IF - // SeedlessOnboardingController: seedlessOnboardingControllerInit, + SeedlessOnboardingController: seedlessOnboardingControllerInit, }, persistedState: initialState as EngineState, existingControllersByName, @@ -1062,7 +1052,7 @@ export class Engine { const accountsController = controllersByName.AccountsController; const transactionController = controllersByName.TransactionController; - // const seedlessOnboardingController = seedlessOnboardingController; + const seedlessOnboardingController = controllersByName.SeedlessOnboardingController; // Backwards compatibility for existing references this.accountsController = accountsController; this.transactionController = transactionController; diff --git a/app/core/Engine/controllers/seedless-onboarding-controller/index.ts b/app/core/Engine/controllers/seedless-onboarding-controller/index.ts new file mode 100644 index 000000000000..511926563307 --- /dev/null +++ b/app/core/Engine/controllers/seedless-onboarding-controller/index.ts @@ -0,0 +1,36 @@ +import type { ControllerInitFunction } from '../../types'; +import { + SeedlessOnboardingController, + SeedlessOnboardingControllerState, + type SeedlessOnboardingControllerMessenger, +} from '@metamask/seedless-onboarding-controller'; + + + +const getDefaultSeedlessOnboardingControllerState = () : SeedlessOnboardingControllerState => ({ + nodeAuthTokens: undefined, + hasValidEncryptionKey: false, +}); + + +/** + * Initialize the SeedlessOnboardingController. + * + * @param request - The request object. + * @returns The SeedlessOnboardingController. + */ +export const seedlessOnboardingControllerInit: ControllerInitFunction< + SeedlessOnboardingController, + SeedlessOnboardingControllerMessenger +> = (request) => { + const { controllerMessenger, persistedState } = request; + + const seedlessOnboardingControllerState = + persistedState.SeedlessOnboardingController ?? getDefaultSeedlessOnboardingControllerState(); + + const controller = new SeedlessOnboardingController({ + messenger: controllerMessenger, + state: seedlessOnboardingControllerState, + }); + return { controller }; +}; diff --git a/app/core/Engine/types.ts b/app/core/Engine/types.ts index d4eed0c16d46..30a163df1fd4 100644 --- a/app/core/Engine/types.ts +++ b/app/core/Engine/types.ts @@ -585,7 +585,8 @@ export type ControllersToInitialize = | 'CurrencyRateController' | 'AccountsController' | 'MultichainNetworkController' - | 'TransactionController'; + | 'TransactionController' + | 'SeedlessOnboardingController'; /** * Callback that returns a controller messenger for a specific controller. diff --git a/app/core/Oauth2Login/utils.ts b/app/core/Oauth2Login/utils.ts index 96f36734aeff..306e31377b21 100644 --- a/app/core/Oauth2Login/utils.ts +++ b/app/core/Oauth2Login/utils.ts @@ -3,14 +3,17 @@ import { Platform } from 'react-native'; import { AuthRequest, ResponseType, - CodeChallengeMethod + CodeChallengeMethod, + AuthSessionResult, } from 'expo-auth-session'; import {signInWithGoogle} from 'react-native-google-acm'; -import DevLogger from '../SDKConnect/utils/DevLogger'; -import { UserAction, UserActionType } from '../../actions/user'; -import { Dispatch } from 'redux'; +import DevLogger from '../../core/SDKConnect/utils/DevLogger'; import { ACTIONS, PREFIXES } from '../../constants/deeplinks'; +import Engine from '../../core/Engine'; +const byoaServerUrl = 'https://api-develop-torus-byoa.web3auth.io'; +// const byoaServerUrl = 'https://organic-gannet-privately.ngrok-free.app'; +const Web3AuthNetwork = 'sapphire-testnet'; const AppRedirect = `${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`; const IosGID = '882363291751-nbbp9n0o307cfil1lup766g1s99k0932.apps.googleusercontent.com'; @@ -18,158 +21,139 @@ const IosGoogleRedirectUri = 'com.googleusercontent.apps.882363291751-nbbp9n0o30 const AndroidWebGID = '882363291751-2a37cchrq9oc1lfj1p419otvahnbhguv.apps.googleusercontent.com'; const AndroidGID = AndroidWebGID; -const AndroidAppleRedirectUri = 'https://simple-auth-server-jade.vercel.app/apple/redirect'; - +const AppleServerRedirectUri = `${byoaServerUrl}/api/v1/oauth/callback`; +const AppleWebClientId = 'com.web3auth.appleloginextension'; interface HandleFlowParams { provider: 'apple' | 'google'; - code: string | null; - idToken: string | null; - accessToken: string | null; + code?: string; + idToken?: string; + clientId: string; + redirectUri?: string; + codeVerifier?: string; + web3AuthNetwork?: string; } -export const handleCodeFlow = async (data : HandleFlowParams, dispatch: Dispatch) => { - console.log(data); - - if (data.code) { - // exchange code for AuthToken from byoa server - } - else if (data.idToken) { - // exchange idToken for AuthToken from byoa server - } - else if (data.accessToken) { - // exchange accessToken for AuthToken from byoa server - } - else { - throw new Error('No code, idToken, or accessToken'); - } - - // const result = seedlessOnboardingController.authenticate(byoaAuthToken) - // const existingUser = result.existingUser; - - console.log('handleCodeFlow: dispatching OAUTH2_LOGIN_SUCCESS'); - // dispatch Action for login success - dispatch({ - type: UserActionType.OAUTH2_LOGIN_SUCCESS, - payload: { - existingUser: false, +type HandleOauth2LoginResult = {type: 'pending'} | {type: AuthSessionResult['type']}; + +export const handleCodeFlow = async (params : HandleFlowParams) : Promise<{status: 'success' | 'error', error?: string}> => { + const {code, idToken, provider, clientId, redirectUri, codeVerifier, web3AuthNetwork} = params; + + const pathname = code ? 'api/v1/oauth/token' : 'api/v1/oauth/id_token'; + const body = code ? { + code, + client_id: clientId, + login_provider: provider, + network: web3AuthNetwork ?? Web3AuthNetwork, + redirect_uri: redirectUri, + code_verifier: codeVerifier, + } : { + id_token: idToken, + client_id: clientId, + login_provider: provider, + network: web3AuthNetwork ?? Web3AuthNetwork, + }; + + const res = await fetch(`${byoaServerUrl}/${pathname}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', }, + body: JSON.stringify(body), }); + const data = await res.json(); + console.log('handleCodeFlow: data', data); + // await Engine.context.SeedlessOnboardingController.authenticateOAuthUser(data); + return {status: 'success'}; }; -const handleAppleLogin = async (dispatch: Dispatch) => { - if (Platform.OS === 'ios') { - try { +const iosHandleOauth2Login = async (provider: 'apple' | 'google') : Promise => { + try { + if (provider === 'apple') { const credential = await signInAsync({ requestedScopes: [ AppleAuthenticationScope.FULL_NAME, AppleAuthenticationScope.EMAIL, ], }); - - handleCodeFlow({ - provider: 'apple', - code: credential.authorizationCode, - idToken: null, - accessToken: null, - }, dispatch); - - return credential.authorizationCode ? 'success' : 'error'; - } catch (error) { - DevLogger.log('handleAppleLogin: error', error); - - dispatch({ - type: UserActionType.OAUTH2_LOGIN_ERROR, - payload: { - error: 'Apple login failed', - }, + if (credential.identityToken) { + await handleCodeFlow({ + provider: 'apple', + idToken: credential.identityToken, + clientId: IosGID, + }); + return {type: 'success'}; + } + return {type: 'dismiss'}; + } else if (provider === 'google') { + const authRequest = new AuthRequest({ + clientId: IosGID, + redirectUri: IosGoogleRedirectUri, + scopes: ['email', 'profile'], + responseType: ResponseType.Code, + codeChallengeMethod: CodeChallengeMethod.S256, + usePKCE: true, + }); + const result = await authRequest.promptAsync({ + authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth', }); - return 'error'; + + if (result.type === 'success') { + await handleCodeFlow({ + provider: 'google', + code: result.params.code, // result.params.idToken + clientId: IosGID, + redirectUri: IosGoogleRedirectUri, + codeVerifier: authRequest.codeVerifier, + }); + } + return result; } + throw new Error('Invalid provider : ' + provider); + } catch (error) { + console.log('handleGoogleLogin: error', error); + DevLogger.log('handleGoogleLogin: error', error); + return {type: 'error'}; } - else if (Platform.OS === 'android') { +}; + +const androidHandleOauth2Login = async (provider: 'apple' | 'google') : Promise => { + if (provider === 'apple') { const state = JSON.stringify({ provider: 'apple', - redirectUri: AppRedirect, + client_redirect_back_uri: AppRedirect, + redirectUri: AppleServerRedirectUri, + clientId: AppleWebClientId, random: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15), }); const authRequest = new AuthRequest({ - clientId: 'com.web3auth.appleloginextension', - redirectUri: AndroidAppleRedirectUri, + clientId: AppleWebClientId, + redirectUri: AppleServerRedirectUri, scopes: ['email', 'name'], responseType: ResponseType.Code, - codeChallengeMethod: CodeChallengeMethod.S256, + // codeChallengeMethod: CodeChallengeMethod.S256, + usePKCE: false, state, - usePKCE: true, extraParams: { response_mode: 'form_post', } }); const result = await authRequest.promptAsync({ authorizationEndpoint: 'https://appleid.apple.com/auth/authorize', - }).catch((error: any) => { - DevLogger.log('handleAppleLogin: error', error); - return {type: 'error'}; }); + console.log("handleAppleLogin: result", authRequest.url); // Apple login use redirect flow thus no handleCodeFlow here - DevLogger.log('handleAppleLogin: result', result); - dispatch({ - type: UserActionType.OAUTH2_LOGIN_COMPLETE, - }); - return result.type; - } - throw new Error('Apple login is not supported on this platform'); -}; - - -const handleGoogleLogin = async(dispatch: Dispatch) => { - if (Platform.OS === 'ios') { - const authRequest = new AuthRequest({ - clientId: IosGID, - redirectUri: IosGoogleRedirectUri, - scopes: ['email', 'profile'], - responseType: ResponseType.Code, - usePKCE: true, - - }); - const result = await authRequest.promptAsync({ - authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth', - }); - - DevLogger.log('handleGoogleLogin: result', result); - - if (result.type === 'success') { - handleCodeFlow({ - provider: 'google', - code: result.params.code, // result.params.idToken - idToken: null, - accessToken: null, - }, dispatch); - } else if (result.type === 'error') { - dispatch({ - type: UserActionType.OAUTH2_LOGIN_ERROR, - payload: { - error: 'Google login failed', - }, - }); - } else { - // dispatch({ type: UserActionType.OAUTH2_LOGIN_COMPLETE }); - } - - return result.type; - } - else if (Platform.OS === 'android') { + console.log('handleAppleLogin: result', result); + return {type: 'pending'}; + } else if (provider === 'google') { const result = await signInWithGoogle({ serverClientId: AndroidGID, nonce: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15), autoSelectEnabled: true, - }).catch((error: any) => { - DevLogger.log('handleGoogleLogin: error', error); - return {type: 'error'}; }); DevLogger.log('handleGoogleLogin: result', result); @@ -178,36 +162,23 @@ const handleGoogleLogin = async(dispatch: Dispatch) => { handleCodeFlow({ provider: 'google', code: result.params.code, // result.params.idToken - idToken: null, - accessToken: null, - }, dispatch); - } else if (result.type === 'error') { - dispatch({ - type: UserActionType.OAUTH2_LOGIN_ERROR, - payload: { - error: result.params.error, - }, + idToken: result.params.idToken, + clientId: AndroidGID, }); - } else { - dispatch({ type: UserActionType.OAUTH2_LOGIN_COMPLETE }); } - - return result.type; + return result; } - - throw new Error('Google login is not supported on this platform'); + throw new Error('Invalid provider'); }; -const handleOauth2Login = (provider: 'apple' | 'google', dispatch: Dispatch) => { - if (provider === 'apple') { - return handleAppleLogin(dispatch); - } - else if (provider === 'google') { - return handleGoogleLogin(dispatch); +const handleOauth2Login = async (provider: 'apple' | 'google') : Promise => { + if (Platform.OS === 'ios') { + return await iosHandleOauth2Login(provider); + } else if (Platform.OS === 'android') { + return await androidHandleOauth2Login(provider); } - throw new Error('Invalid provider'); + throw new Error('Invalid platform'); }; - export default handleOauth2Login; From 0a159d20c3bd4c50f0e4b6e6595d0192b0b0b8d4 Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 28 Mar 2025 15:51:13 +0800 Subject: [PATCH 005/187] feat: use Oauth2loginService --- .../Oauth2Login/Oauth2LoginComponent.tsx | 6 +- .../Handlers/handleOauth2RedirectUrl.ts | 15 +- app/core/Oauth2Login/Oauth2loginService.ts | 239 ++++++++++++++++++ app/core/Oauth2Login/utils.ts | 184 -------------- 4 files changed, 249 insertions(+), 195 deletions(-) create mode 100644 app/core/Oauth2Login/Oauth2loginService.ts delete mode 100644 app/core/Oauth2Login/utils.ts diff --git a/app/components/Oauth2Login/Oauth2LoginComponent.tsx b/app/components/Oauth2Login/Oauth2LoginComponent.tsx index d3605743558c..58c6aafe01da 100644 --- a/app/components/Oauth2Login/Oauth2LoginComponent.tsx +++ b/app/components/Oauth2Login/Oauth2LoginComponent.tsx @@ -1,10 +1,10 @@ import React, { View, StyleSheet } from 'react-native'; -import handleOauth2Login from '../../core/Oauth2Login/utils'; import StyledButton from '../UI/StyledButton'; import { OnboardingSelectorIDs } from '../../../e2e/selectors/Onboarding/Onboarding.selectors'; import { strings } from '../../../locales/i18n'; import DevLogger from '../../core/SDKConnect/utils/DevLogger'; import { useNavigation, ParamListBase, NavigationProp } from '@react-navigation/native'; +import Oauth2LoginService from '../../core/Oauth2Login/Oauth2loginService'; const styles = StyleSheet.create({ buttonWrapper: { @@ -22,7 +22,7 @@ export default function Oauth2LoginComponent( ) { type={'normal'} testID={OnboardingSelectorIDs.IMPORT_SEED_BUTTON} onPress={async () => { - const result = await handleOauth2Login('apple' ).catch((e) => { + const result = await Oauth2LoginService.handleOauth2Login('apple' ).catch((e) => { DevLogger.log(e); return {type: 'error', error: e}; }); @@ -39,7 +39,7 @@ export default function Oauth2LoginComponent( ) { type={'normal'} testID={OnboardingSelectorIDs.IMPORT_SEED_BUTTON} onPress={async () => { - const result = await handleOauth2Login('google').catch((e) => { + const result = await Oauth2LoginService.handleOauth2Login('google').catch((e) => { DevLogger.log(e); return {type: 'error', error: e}; }); diff --git a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts index 42c7c944d51a..600891c69c0f 100644 --- a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts +++ b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts @@ -1,7 +1,6 @@ -import DevLogger from '../../SDKConnect/utils/DevLogger'; -import { handleCodeFlow } from '../../Oauth2Login/utils'; import DeeplinkManager from '../DeeplinkManager'; - +import Oauth2LoginService from '../../Oauth2Login/Oauth2loginService'; +import Logger from '../../../util/Logger'; function handleOauth2RedirectUrl({ deeplinkManager, @@ -21,12 +20,12 @@ function handleOauth2RedirectUrl({ const provider = state.provider as 'apple' | 'google'; const clientId = state.clientId as string; - DevLogger.log('handleOauth2RedirectUrl: provider', provider); - DevLogger.log('handleOauth2RedirectUrl: code', code); + Logger.log('handleOauth2RedirectUrl: provider', provider); + Logger.log('handleOauth2RedirectUrl: code', code); - console.log('handleOauth2RedirectUrl: state', state); + Logger.log('handleOauth2RedirectUrl: state', state); if (code ) { - handleCodeFlow({ code, provider , clientId, redirectUri: state.redirectUri}) + Oauth2LoginService.handleCodeFlow({ code, provider , clientId, redirectUri: state.redirectUri, codeVerifier: Oauth2LoginService.localState.codeVerifier ?? undefined }) .then((result) => { if (result.status === 'success') { // get current route @@ -35,7 +34,7 @@ function handleOauth2RedirectUrl({ return code; } }).catch((error) => { - DevLogger.log('handleOauth2RedirectUrl: error', error); + Logger.log('handleOauth2RedirectUrl: error', error); }); } else { // deeplinkManager.dispatch( diff --git a/app/core/Oauth2Login/Oauth2loginService.ts b/app/core/Oauth2Login/Oauth2loginService.ts new file mode 100644 index 000000000000..862465d8ed87 --- /dev/null +++ b/app/core/Oauth2Login/Oauth2loginService.ts @@ -0,0 +1,239 @@ + +import { + Platform +} from 'react-native'; +import Engine from '../Engine'; +import Logger from '../../util/Logger'; +import ReduxService from '../redux'; + +import { signInAsync, AppleAuthenticationScope } from 'expo-apple-authentication'; +import { + AuthRequest, + ResponseType, + CodeChallengeMethod, + AuthSessionResult, + } from 'expo-auth-session'; +import {signInWithGoogle} from 'react-native-google-acm'; + +import DevLogger from '../SDKConnect/utils/DevLogger'; +import { ACTIONS, PREFIXES } from '../../constants/deeplinks'; + +const byoaServerUrl = 'https://api-develop-torus-byoa.web3auth.io'; +// const byoaServerUrl = 'https://organic-gannet-privately.ngrok-free.app'; +const Web3AuthNetwork = 'sapphire-testnet'; +const AppRedirect = `${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`; + +const IosGID = '882363291751-nbbp9n0o307cfil1lup766g1s99k0932.apps.googleusercontent.com'; +const IosGoogleRedirectUri = 'com.googleusercontent.apps.882363291751-nbbp9n0o307cfil1lup766g1s99k0932:/oauth2redirect/google'; + +const AndroidWebGID = '882363291751-2a37cchrq9oc1lfj1p419otvahnbhguv.apps.googleusercontent.com'; +const AndroidGID = AndroidWebGID; +const AppleServerRedirectUri = `${byoaServerUrl}/api/v1/oauth/callback`; + +const AppleWebClientId = 'com.web3auth.appleloginextension'; + + +type HandleOauth2LoginResult = {type: 'pending'} | {type: AuthSessionResult['type']}; + +interface HandleFlowParams { + provider: 'apple' | 'google'; + code?: string; + idToken?: string; + clientId: string; + redirectUri?: string; + codeVerifier?: string; + web3AuthNetwork?: string; +} + +export class Oauth2LoginService { + public localState: { + loginInProgress: boolean; + codeVerifier: string | null; + }; + + + constructor() { + this.localState = { + loginInProgress: false, + codeVerifier: null, + }; + } + + #iosHandleOauth2Login = async (provider: 'apple' | 'google') : Promise => { + try { + if (provider === 'apple') { + const credential = await signInAsync({ + requestedScopes: [ + AppleAuthenticationScope.FULL_NAME, + AppleAuthenticationScope.EMAIL, + ], + }); + if (credential.identityToken) { + await this.handleCodeFlow({ + provider: 'apple', + idToken: credential.identityToken, + clientId: IosGID, + }); + return {type: 'success'}; + } + return {type: 'dismiss'}; + } else if (provider === 'google') { + const authRequest = new AuthRequest({ + clientId: IosGID, + redirectUri: IosGoogleRedirectUri, + scopes: ['email', 'profile'], + responseType: ResponseType.Code, + codeChallengeMethod: CodeChallengeMethod.S256, + usePKCE: true, + }); + const result = await authRequest.promptAsync({ + authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth', + }); + + if (result.type === 'success') { + await this.handleCodeFlow({ + provider: 'google', + code: result.params.code, // result.params.idToken + clientId: IosGID, + redirectUri: IosGoogleRedirectUri, + codeVerifier: authRequest.codeVerifier, + }); + } + return result; + } + throw new Error('Invalid provider : ' + provider); + } catch (error) { + Logger.error( error as Error, { + message: 'iosHandleOauth2Login', + provider, + } ); + return {type: 'error'}; + } + }; + + #androidHandleOauth2Login = async (provider: 'apple' | 'google') : Promise => { + try { + if (provider === 'apple') { + const state = JSON.stringify({ + provider: 'apple', + client_redirect_back_uri: AppRedirect, + redirectUri: AppleServerRedirectUri, + clientId: AppleWebClientId, + random: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15), + }); + const authRequest = new AuthRequest({ + clientId: AppleWebClientId, + redirectUri: AppleServerRedirectUri, + scopes: ['email', 'name'], + responseType: ResponseType.Code, + codeChallengeMethod: CodeChallengeMethod.S256, + usePKCE: false, + state, + extraParams: { + response_mode: 'form_post', + } + }); + const result = await authRequest.promptAsync({ + authorizationEndpoint: 'https://appleid.apple.com/auth/authorize', + }); + this.localState.codeVerifier = authRequest.codeVerifier ?? null; + + Logger.log('handleAppleLogin: result', authRequest.codeVerifier); + // Apple login use redirect flow thus no handleCodeFlow here + Logger.log('handleAppleLogin: result', result); + + return {type: 'pending'}; + } else if (provider === 'google') { + Logger.log('handleGoogleLogin: AndroidGID', AndroidGID); + const result = await signInWithGoogle({ + serverClientId: AndroidGID, + nonce: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15), + autoSelectEnabled: true, + }); + + Logger.log('handleGoogleLogin: result', result); + DevLogger.log('handleGoogleLogin: result', result); + + if (result.type === 'success') { + await this.handleCodeFlow({ + provider: 'google', + code: result.params.code, // result.params.idToken + idToken: result.params.idToken, + clientId: AndroidGID, + }); + } + return result; + } + throw new Error('Invalid provider'); + } catch (error) { + Logger.log('handleGoogleLogin: error', error); + DevLogger.log('handleGoogleLogin: error', error); + return {type: 'error'}; + } + }; + + handleCodeFlow = async (params : HandleFlowParams) : Promise<{status: 'success' | 'error', error?: string}> => { + const {code, idToken, provider, clientId, redirectUri, codeVerifier, web3AuthNetwork} = params; + + const pathname = code ? 'api/v1/oauth/token' : 'api/v1/oauth/id_token'; + const body = code ? { + code, + client_id: clientId, + login_provider: provider, + network: web3AuthNetwork ?? Web3AuthNetwork, + redirect_uri: redirectUri, + code_verifier: codeVerifier, + } : { + id_token: idToken, + client_id: clientId, + login_provider: provider, + network: web3AuthNetwork ?? Web3AuthNetwork, + }; + + Logger.log('handleCodeFlow: body', body); + try { + const res = await fetch(`${byoaServerUrl}/${pathname}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }); + + const data = await res.json(); + Logger.log('handleCodeFlow: data', data); + if (data.success) { + // await Engine.context.SeedlessOnboardingController.authenticateOAuthUser(data); + return {status: 'success'}; + } + throw new Error('Failed to authenticate OAuth user : ' + data.message); + } catch (error) { + Logger.error( error as Error, { + message: 'handleCodeFlow', + } ); + return {status: 'error'}; + } finally { + // ReduxService.store.dispatch({ + // }); + this.localState.codeVerifier = null; + this.localState.loginInProgress = false; + } + }; + + handleOauth2Login = async (provider: 'apple' | 'google') : Promise => { + if (this.localState.loginInProgress) { + throw new Error('Login already in progress'); + } + this.localState.loginInProgress = true; + + if (Platform.OS === 'ios') { + return await this.#iosHandleOauth2Login(provider); + } else if (Platform.OS === 'android') { + return await this.#androidHandleOauth2Login(provider); + } + this.localState.loginInProgress = false; + throw new Error('Invalid platform'); + }; +} + +export default new Oauth2LoginService(); diff --git a/app/core/Oauth2Login/utils.ts b/app/core/Oauth2Login/utils.ts deleted file mode 100644 index 306e31377b21..000000000000 --- a/app/core/Oauth2Login/utils.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { signInAsync, AppleAuthenticationScope } from 'expo-apple-authentication'; -import { Platform } from 'react-native'; -import { - AuthRequest, - ResponseType, - CodeChallengeMethod, - AuthSessionResult, - } from 'expo-auth-session'; -import {signInWithGoogle} from 'react-native-google-acm'; -import DevLogger from '../../core/SDKConnect/utils/DevLogger'; -import { ACTIONS, PREFIXES } from '../../constants/deeplinks'; -import Engine from '../../core/Engine'; - -const byoaServerUrl = 'https://api-develop-torus-byoa.web3auth.io'; -// const byoaServerUrl = 'https://organic-gannet-privately.ngrok-free.app'; -const Web3AuthNetwork = 'sapphire-testnet'; -const AppRedirect = `${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`; - -const IosGID = '882363291751-nbbp9n0o307cfil1lup766g1s99k0932.apps.googleusercontent.com'; -const IosGoogleRedirectUri = 'com.googleusercontent.apps.882363291751-nbbp9n0o307cfil1lup766g1s99k0932:/oauth2redirect/google'; - -const AndroidWebGID = '882363291751-2a37cchrq9oc1lfj1p419otvahnbhguv.apps.googleusercontent.com'; -const AndroidGID = AndroidWebGID; -const AppleServerRedirectUri = `${byoaServerUrl}/api/v1/oauth/callback`; - -const AppleWebClientId = 'com.web3auth.appleloginextension'; - -interface HandleFlowParams { - provider: 'apple' | 'google'; - code?: string; - idToken?: string; - clientId: string; - redirectUri?: string; - codeVerifier?: string; - web3AuthNetwork?: string; -} - -type HandleOauth2LoginResult = {type: 'pending'} | {type: AuthSessionResult['type']}; - -export const handleCodeFlow = async (params : HandleFlowParams) : Promise<{status: 'success' | 'error', error?: string}> => { - const {code, idToken, provider, clientId, redirectUri, codeVerifier, web3AuthNetwork} = params; - - const pathname = code ? 'api/v1/oauth/token' : 'api/v1/oauth/id_token'; - const body = code ? { - code, - client_id: clientId, - login_provider: provider, - network: web3AuthNetwork ?? Web3AuthNetwork, - redirect_uri: redirectUri, - code_verifier: codeVerifier, - } : { - id_token: idToken, - client_id: clientId, - login_provider: provider, - network: web3AuthNetwork ?? Web3AuthNetwork, - }; - - const res = await fetch(`${byoaServerUrl}/${pathname}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(body), - }); - const data = await res.json(); - console.log('handleCodeFlow: data', data); - // await Engine.context.SeedlessOnboardingController.authenticateOAuthUser(data); - return {status: 'success'}; -}; - - -const iosHandleOauth2Login = async (provider: 'apple' | 'google') : Promise => { - try { - if (provider === 'apple') { - const credential = await signInAsync({ - requestedScopes: [ - AppleAuthenticationScope.FULL_NAME, - AppleAuthenticationScope.EMAIL, - ], - }); - if (credential.identityToken) { - await handleCodeFlow({ - provider: 'apple', - idToken: credential.identityToken, - clientId: IosGID, - }); - return {type: 'success'}; - } - return {type: 'dismiss'}; - } else if (provider === 'google') { - const authRequest = new AuthRequest({ - clientId: IosGID, - redirectUri: IosGoogleRedirectUri, - scopes: ['email', 'profile'], - responseType: ResponseType.Code, - codeChallengeMethod: CodeChallengeMethod.S256, - usePKCE: true, - }); - const result = await authRequest.promptAsync({ - authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth', - }); - - if (result.type === 'success') { - await handleCodeFlow({ - provider: 'google', - code: result.params.code, // result.params.idToken - clientId: IosGID, - redirectUri: IosGoogleRedirectUri, - codeVerifier: authRequest.codeVerifier, - }); - } - return result; - } - throw new Error('Invalid provider : ' + provider); - } catch (error) { - console.log('handleGoogleLogin: error', error); - DevLogger.log('handleGoogleLogin: error', error); - return {type: 'error'}; - } -}; - -const androidHandleOauth2Login = async (provider: 'apple' | 'google') : Promise => { - if (provider === 'apple') { - const state = JSON.stringify({ - provider: 'apple', - client_redirect_back_uri: AppRedirect, - redirectUri: AppleServerRedirectUri, - clientId: AppleWebClientId, - random: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15), - }); - const authRequest = new AuthRequest({ - clientId: AppleWebClientId, - redirectUri: AppleServerRedirectUri, - scopes: ['email', 'name'], - responseType: ResponseType.Code, - // codeChallengeMethod: CodeChallengeMethod.S256, - usePKCE: false, - state, - extraParams: { - response_mode: 'form_post', - } - }); - const result = await authRequest.promptAsync({ - authorizationEndpoint: 'https://appleid.apple.com/auth/authorize', - }); - - console.log("handleAppleLogin: result", authRequest.url); - // Apple login use redirect flow thus no handleCodeFlow here - console.log('handleAppleLogin: result', result); - - return {type: 'pending'}; - } else if (provider === 'google') { - const result = await signInWithGoogle({ - serverClientId: AndroidGID, - nonce: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15), - autoSelectEnabled: true, - }); - - DevLogger.log('handleGoogleLogin: result', result); - - if (result.type === 'success') { - handleCodeFlow({ - provider: 'google', - code: result.params.code, // result.params.idToken - idToken: result.params.idToken, - clientId: AndroidGID, - }); - } - return result; - } - throw new Error('Invalid provider'); -}; - - -const handleOauth2Login = async (provider: 'apple' | 'google') : Promise => { - if (Platform.OS === 'ios') { - return await iosHandleOauth2Login(provider); - } else if (Platform.OS === 'android') { - return await androidHandleOauth2Login(provider); - } - throw new Error('Invalid platform'); -}; - -export default handleOauth2Login; From 6cfef4d0b36e2e209ae2fcecdd12ffeeb584fd2b Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 1 Apr 2025 15:36:02 +0800 Subject: [PATCH 006/187] fix: clean up --- .../Handlers/handleOauth2RedirectUrl.ts | 21 ++++++----- app/core/Oauth2Login/Oauth2loginService.ts | 37 ++++++++++--------- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts index 600891c69c0f..eb324a09afc8 100644 --- a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts +++ b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts @@ -1,6 +1,8 @@ import DeeplinkManager from '../DeeplinkManager'; import Oauth2LoginService from '../../Oauth2Login/Oauth2loginService'; import Logger from '../../../util/Logger'; +import { strings } from '../../../../locales/i18n'; +import { showAlert } from '../../../actions/alert'; function handleOauth2RedirectUrl({ deeplinkManager, @@ -27,7 +29,8 @@ function handleOauth2RedirectUrl({ if (code ) { Oauth2LoginService.handleCodeFlow({ code, provider , clientId, redirectUri: state.redirectUri, codeVerifier: Oauth2LoginService.localState.codeVerifier ?? undefined }) .then((result) => { - if (result.status === 'success') { + Logger.log('handleOauth2RedirectUrl: result', result); + if (result.type === 'success') { // get current route // const currentRoute = deeplinkManager.navigation.getCurrentRoute(); deeplinkManager.navigation.navigate('ChoosePassword'); @@ -37,14 +40,14 @@ function handleOauth2RedirectUrl({ Logger.log('handleOauth2RedirectUrl: error', error); }); } else { - // deeplinkManager.dispatch( - // showAlert({ - // isVisible: true, - // autodismiss: 5000, - // content: 'clipboard-alert', - // data: { msg: strings('social login failed')}, - // }), - // ); + deeplinkManager.dispatch( + showAlert({ + isVisible: true, + autodismiss: 5000, + content: 'clipboard-alert', + data: { msg: strings('oauth2-login-failed')}, + }), + ); } } diff --git a/app/core/Oauth2Login/Oauth2loginService.ts b/app/core/Oauth2Login/Oauth2loginService.ts index 862465d8ed87..5a7e493046f7 100644 --- a/app/core/Oauth2Login/Oauth2loginService.ts +++ b/app/core/Oauth2Login/Oauth2loginService.ts @@ -19,17 +19,14 @@ import DevLogger from '../SDKConnect/utils/DevLogger'; import { ACTIONS, PREFIXES } from '../../constants/deeplinks'; const byoaServerUrl = 'https://api-develop-torus-byoa.web3auth.io'; -// const byoaServerUrl = 'https://organic-gannet-privately.ngrok-free.app'; const Web3AuthNetwork = 'sapphire-testnet'; -const AppRedirect = `${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`; +const AppRedirectUri = `${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`; const IosGID = '882363291751-nbbp9n0o307cfil1lup766g1s99k0932.apps.googleusercontent.com'; const IosGoogleRedirectUri = 'com.googleusercontent.apps.882363291751-nbbp9n0o307cfil1lup766g1s99k0932:/oauth2redirect/google'; -const AndroidWebGID = '882363291751-2a37cchrq9oc1lfj1p419otvahnbhguv.apps.googleusercontent.com'; -const AndroidGID = AndroidWebGID; +const AndroidGoogleWebGID = '882363291751-2a37cchrq9oc1lfj1p419otvahnbhguv.apps.googleusercontent.com'; const AppleServerRedirectUri = `${byoaServerUrl}/api/v1/oauth/callback`; - const AppleWebClientId = 'com.web3auth.appleloginextension'; @@ -69,12 +66,11 @@ export class Oauth2LoginService { ], }); if (credential.identityToken) { - await this.handleCodeFlow({ + return await this.handleCodeFlow({ provider: 'apple', idToken: credential.identityToken, clientId: IosGID, }); - return {type: 'success'}; } return {type: 'dismiss'}; } else if (provider === 'google') { @@ -91,7 +87,7 @@ export class Oauth2LoginService { }); if (result.type === 'success') { - await this.handleCodeFlow({ + return this.handleCodeFlow({ provider: 'google', code: result.params.code, // result.params.idToken clientId: IosGID, @@ -116,7 +112,7 @@ export class Oauth2LoginService { if (provider === 'apple') { const state = JSON.stringify({ provider: 'apple', - client_redirect_back_uri: AppRedirect, + client_redirect_back_uri: AppRedirectUri, redirectUri: AppleServerRedirectUri, clientId: AppleWebClientId, random: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15), @@ -144,9 +140,9 @@ export class Oauth2LoginService { return {type: 'pending'}; } else if (provider === 'google') { - Logger.log('handleGoogleLogin: AndroidGID', AndroidGID); + Logger.log('handleGoogleLogin: AndroidGID', AndroidGoogleWebGID); const result = await signInWithGoogle({ - serverClientId: AndroidGID, + serverClientId: AndroidGoogleWebGID, nonce: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15), autoSelectEnabled: true, }); @@ -155,11 +151,11 @@ export class Oauth2LoginService { DevLogger.log('handleGoogleLogin: result', result); if (result.type === 'success') { - await this.handleCodeFlow({ + return this.handleCodeFlow({ provider: 'google', code: result.params.code, // result.params.idToken idToken: result.params.idToken, - clientId: AndroidGID, + clientId: AndroidGoogleWebGID, }); } return result; @@ -172,7 +168,7 @@ export class Oauth2LoginService { } }; - handleCodeFlow = async (params : HandleFlowParams) : Promise<{status: 'success' | 'error', error?: string}> => { + handleCodeFlow = async (params : HandleFlowParams) : Promise<{type: 'success' | 'error', error?: string}> => { const {code, idToken, provider, clientId, redirectUri, codeVerifier, web3AuthNetwork} = params; const pathname = code ? 'api/v1/oauth/token' : 'api/v1/oauth/id_token'; @@ -203,15 +199,22 @@ export class Oauth2LoginService { const data = await res.json(); Logger.log('handleCodeFlow: data', data); if (data.success) { - // await Engine.context.SeedlessOnboardingController.authenticateOAuthUser(data); - return {status: 'success'}; + await Engine.context.SeedlessOnboardingController.authenticateOAuthUser({ + idTokens: [data.id_token], + verifier: data.verifier, + verifierID: data.verifier_id, + indexes: [data.index], + endpoints: [data.endpoint], + }); + return {type: 'success'}; } throw new Error('Failed to authenticate OAuth user : ' + data.message); } catch (error) { + Logger.log('handleCodeFlow: error', error); Logger.error( error as Error, { message: 'handleCodeFlow', } ); - return {status: 'error'}; + return {type: 'error'}; } finally { // ReduxService.store.dispatch({ // }); From 785825aa0da89690539e6b3d0cd0cb93f2e602cc Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 2 Apr 2025 14:42:43 +0800 Subject: [PATCH 007/187] fix: update controller and flow --- .../Oauth2Login/Oauth2LoginComponent.tsx | 22 +++- app/components/Views/ChoosePassword/index.js | 13 +- app/components/Views/Login/index.js | 13 +- app/core/Authentication/Authentication.ts | 51 ++++++- .../Handlers/handleOauth2RedirectUrl.ts | 24 +++- .../seedless-onboarding-controller/index.ts | 52 ++++++++ app/core/Oauth2Login/Oauth2loginService.ts | 124 +++++++++++++----- package.json | 2 +- yarn.lock | 7 +- 9 files changed, 252 insertions(+), 56 deletions(-) diff --git a/app/components/Oauth2Login/Oauth2LoginComponent.tsx b/app/components/Oauth2Login/Oauth2LoginComponent.tsx index 58c6aafe01da..87a9a73ec920 100644 --- a/app/components/Oauth2Login/Oauth2LoginComponent.tsx +++ b/app/components/Oauth2Login/Oauth2LoginComponent.tsx @@ -5,7 +5,6 @@ import { strings } from '../../../locales/i18n'; import DevLogger from '../../core/SDKConnect/utils/DevLogger'; import { useNavigation, ParamListBase, NavigationProp } from '@react-navigation/native'; import Oauth2LoginService from '../../core/Oauth2Login/Oauth2loginService'; - const styles = StyleSheet.create({ buttonWrapper: { marginBottom: 16, @@ -22,13 +21,18 @@ export default function Oauth2LoginComponent( ) { type={'normal'} testID={OnboardingSelectorIDs.IMPORT_SEED_BUTTON} onPress={async () => { - const result = await Oauth2LoginService.handleOauth2Login('apple' ).catch((e) => { + const result = await Oauth2LoginService.handleOauth2Login('apple', 'onboarding').catch((e) => { DevLogger.log(e); - return {type: 'error', error: e}; + return {type: 'error', error: e, existingUser: false}; }); if (result.type === 'success') { - navigation.navigate('ChoosePassword'); + + if (result.existingUser) { + navigation.navigate('Login'); + } else { + navigation.navigate('ChoosePassword'); + } } }} > {strings('login.apple_button')} @@ -39,13 +43,17 @@ export default function Oauth2LoginComponent( ) { type={'normal'} testID={OnboardingSelectorIDs.IMPORT_SEED_BUTTON} onPress={async () => { - const result = await Oauth2LoginService.handleOauth2Login('google').catch((e) => { + const result = await Oauth2LoginService.handleOauth2Login('google', 'onboarding').catch((e) => { DevLogger.log(e); - return {type: 'error', error: e}; + return {type: 'error', error: e, existingUser: false}; }); if (result.type === 'success') { - navigation.navigate('ChoosePassword'); + if (result.existingUser) { + navigation.navigate('Login'); + } else { + navigation.navigate('ChoosePassword'); + } } }} > {strings('login.google_button')} diff --git a/app/components/Views/ChoosePassword/index.js b/app/components/Views/ChoosePassword/index.js index 4cd86db3b214..b812f0918df7 100644 --- a/app/components/Views/ChoosePassword/index.js +++ b/app/components/Views/ChoosePassword/index.js @@ -234,6 +234,10 @@ class ChoosePassword extends PureComponent { * Object that represents the current route info like params passed to it */ route: PropTypes.object, + /** + * The flag to check if the oauth2 login was successful + */ + oauth2LoginSuccess: PropTypes.bool, }; state = { @@ -247,6 +251,7 @@ class ChoosePassword extends PureComponent { loading: false, error: null, inputWidth: { width: '99%' }, + oauth2LoginSuccess: false, }; mounted = true; @@ -349,6 +354,7 @@ class ChoosePassword extends PureComponent { this.state.biometryChoice, this.state.rememberMe, ); + authType.oauth2Login = this.props.oauth2LoginSuccess; if (previous_screen === ONBOARDING) { try { @@ -414,6 +420,7 @@ class ChoosePassword extends PureComponent { false, false, ); + newAuthData.oauth2Login = this.props.oauth2LoginSuccess; try { await Authentication.newWalletAndKeychain( this.state.password, @@ -779,4 +786,8 @@ const mapDispatchToProps = (dispatch) => ({ seedphraseNotBackedUp: () => dispatch(seedphraseNotBackedUp()), }); -export default connect(null, mapDispatchToProps)(ChoosePassword); +const mapStateToProps = (state) => ({ + oauth2LoginSuccess: state.user.oauth2LoginSuccess, +}); + +export default connect(mapStateToProps, mapDispatchToProps)(ChoosePassword); diff --git a/app/components/Views/Login/index.js b/app/components/Views/Login/index.js index 3104af6dd24b..1ec71fa63ec9 100644 --- a/app/components/Views/Login/index.js +++ b/app/components/Views/Login/index.js @@ -232,6 +232,10 @@ class Login extends PureComponent { * Full state of the app */ fullState: PropTypes.object, + /** + * The flag to check if the oauth2 login was successful + */ + oauth2LoginSuccess: PropTypes.bool, }; state = { @@ -248,6 +252,7 @@ class Login extends PureComponent { deleteText: '', showDeleteWarning: false, hasBiometricCredentials: false, + oauth2LoginSuccess: false, }; fieldRef = React.createRef(); @@ -393,6 +398,7 @@ class Login extends PureComponent { this.state.biometryChoice, this.state.rememberMe, ); + authType.oauth2Login = this.props.oauth2LoginSuccess; try { await trace( @@ -402,7 +408,11 @@ class Login extends PureComponent { parentContext: this.parentSpan, }, async () => { - await Authentication.userEntryAuth(password, authType); + if (this.props.oauth2LoginSuccess) { + await Authentication.rehydrateSeedPhrase(password, authType); + } else { + await Authentication.userEntryAuth(password, authType); + } }, ); Keyboard.dismiss(); @@ -676,6 +686,7 @@ Login.contextType = ThemeContext; const mapStateToProps = (state) => ({ userLoggedIn: state.user.userLoggedIn, fullState: state, + oauth2LoginSuccess: state.user.oauth2LoginSuccess, }); const mapDispatchToProps = (dispatch) => ({ diff --git a/app/core/Authentication/Authentication.ts b/app/core/Authentication/Authentication.ts index b4d56a2da998..4f011388c736 100644 --- a/app/core/Authentication/Authentication.ts +++ b/app/core/Authentication/Authentication.ts @@ -13,6 +13,7 @@ import { logIn, logOut, passwordSet, + UserActionType, } from '../../actions/user'; import AUTHENTICATION_TYPE from '../../constants/userProperties'; import AuthenticationError from './AuthenticationError'; @@ -31,6 +32,8 @@ import NavigationService from '../NavigationService'; import Routes from '../../constants/navigation/Routes'; import { TraceName, TraceOperation, endTrace, trace } from '../../util/trace'; import ReduxService from '../redux'; +import { getSeedPhrase } from '../Vault'; +import byteArrayToHex from '../../util/bytes'; /** * Holds auth data used to determine auth configuration @@ -38,6 +41,7 @@ import ReduxService from '../redux'; export interface AuthData { currentAuthType: AUTHENTICATION_TYPE; //Enum used to show type for authentication availableBiometryType?: BIOMETRY_TYPE; + oauth2Login?: boolean; } class AuthenticationService { @@ -55,6 +59,12 @@ class AuthenticationService { ReduxService.store.dispatch(logOut()); } + private dispatchOauth2Reset(): void { + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN_RESET, + }); + } + /** * This method recreates the vault upon login if user is new and is not using the latest encryption lib * @param password - password entered on login @@ -310,17 +320,17 @@ class AuthenticationService { authData: AuthData, ): Promise => { try { - await this.createWalletVaultAndKeychain(password); + // check for oauth2 login + if (authData.oauth2Login) { + await this.createAndBackupSeedPhrase(password); + } else { + await this.createWalletVaultAndKeychain(password); + } + await this.storePassword(password, authData?.currentAuthType); await StorageWrapper.setItem(EXISTING_USER, TRUE); await StorageWrapper.removeItem(SEED_PHRASE_HINTS); - - // if seedless onboarding is enabled, we need to create a seedphrase backup - // if (SeedlessOnboardingController.state.authToken.length > 0) { - // await SeedlessOnboardingController.createSeedPhraseBackup(password, seedPhrase); - // } - this.dispatchLogin(); this.authData = authData; // TODO: Replace "any" with type @@ -471,6 +481,33 @@ class AuthenticationService { getType = async (): Promise => await this.checkAuthenticationMethod(); + + createAndBackupSeedPhrase = async( + password: string, + ): Promise => { + const { SeedlessOnboardingController, KeyringController } = Engine.context; + await KeyringController.createNewVaultAndKeychain(password); + const seedPhrase = await getSeedPhrase(password); + await SeedlessOnboardingController.createSeedPhraseBackup({password, seedPhrase: byteArrayToHex(seedPhrase)}); + this.dispatchOauth2Reset(); + }; + + rehydrateSeedPhrase = async( + password: string, + authData: AuthData, + ): Promise => { + const { SeedlessOnboardingController } = Engine.context; + const result = await SeedlessOnboardingController.fetchAndRestoreSeedPhraseMetadata(password); + if (result.secretData !== null) { + await this.newWalletAndRestore(password, authData, result.secretData[0], false); + // add in more srps + } else { + // should we throw an error here? + await this.newWalletAndKeychain(password, authData); + } + this.dispatchOauth2Reset(); + // throw error if no secret data + }; } export const Authentication = new AuthenticationService(); diff --git a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts index eb324a09afc8..bebc0b10091b 100644 --- a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts +++ b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts @@ -1,8 +1,9 @@ import DeeplinkManager from '../DeeplinkManager'; -import Oauth2LoginService from '../../Oauth2Login/Oauth2loginService'; +import Oauth2LoginService, { LoginMode, LoginProvider } from '../../Oauth2Login/Oauth2loginService'; import Logger from '../../../util/Logger'; import { strings } from '../../../../locales/i18n'; import { showAlert } from '../../../actions/alert'; +import { UserActionType } from '../../../actions/user'; function handleOauth2RedirectUrl({ deeplinkManager, @@ -18,8 +19,9 @@ function handleOauth2RedirectUrl({ const state = JSON.parse(params.get('state') ?? '{}'); const code = params.get('code') ?? undefined; + const mode = state.mode as LoginMode; + const provider = state.provider as LoginProvider; - const provider = state.provider as 'apple' | 'google'; const clientId = state.clientId as string; Logger.log('handleOauth2RedirectUrl: provider', provider); @@ -30,10 +32,22 @@ function handleOauth2RedirectUrl({ Oauth2LoginService.handleCodeFlow({ code, provider , clientId, redirectUri: state.redirectUri, codeVerifier: Oauth2LoginService.localState.codeVerifier ?? undefined }) .then((result) => { Logger.log('handleOauth2RedirectUrl: result', result); + + Logger.log('handleOauth2RedirectUrl: result.existingUser', result.existingUser); if (result.type === 'success') { - // get current route - // const currentRoute = deeplinkManager.navigation.getCurrentRoute(); - deeplinkManager.navigation.navigate('ChoosePassword'); + // deeplinkManager.dispatch({type: UserActionType.OAUTH2_LOGIN_SUCCESS}); + + Logger.log('handleOauth2RedirectUrl: mode', mode); + Logger.log('handleOauth2RedirectUrl: result.existingUser', result.existingUser); + if (mode === 'onboarding') { + if (result.existingUser) { + deeplinkManager.navigation.navigate('Login'); + } else { + deeplinkManager.navigation.navigate('ChoosePassword'); + } + } else if (mode === 'change-password') { + deeplinkManager.navigation.navigate('ChangePassword'); + } return code; } }).catch((error) => { diff --git a/app/core/Engine/controllers/seedless-onboarding-controller/index.ts b/app/core/Engine/controllers/seedless-onboarding-controller/index.ts index 511926563307..66fb4554b86f 100644 --- a/app/core/Engine/controllers/seedless-onboarding-controller/index.ts +++ b/app/core/Engine/controllers/seedless-onboarding-controller/index.ts @@ -1,9 +1,13 @@ +import { AuthenticationParams, AuthenticationResult } from '@metamask/seedless-onboarding-controller/dist/ToprfClient.cjs'; +import Logger from '../../../../util/Logger'; import type { ControllerInitFunction } from '../../types'; import { SeedlessOnboardingController, SeedlessOnboardingControllerState, type SeedlessOnboardingControllerMessenger, } from '@metamask/seedless-onboarding-controller'; +import { keccak_256 } from '@noble/hashes/sha3'; +import { secp256k1 } from '@metamask/key-tree'; @@ -32,5 +36,53 @@ export const seedlessOnboardingControllerInit: ControllerInitFunction< messenger: controllerMessenger, state: seedlessOnboardingControllerState, }); + + // overwrite function with mock implementation + // controller.authenticateOAuthUser = async (params: AuthenticationParams) : Promise => { + + // Logger.log(params); + // const mockKey = keccak_256.create().update(params.verifier + params.verifierID).digest(); + // const derivedKey = secp256k1.getPublicKey(mockKey, true); + // const derivedKeyHex = Buffer.from(derivedKey).toString('hex'); + // Logger.log(derivedKeyHex); + + + + // const response = await fetch('https://node-2.dev-node.web3auth.io/metadata'); + // const metadata = await response.json(); + // Logger.log(metadata); + // // get from metadata url + // // https://node-2.dev-node.web3auth.io/metadata + // return { + // nodeAuthTokens: [ + // { + // nodeIndex: 1, + // nodeAuthToken: 'nodeAuthToken', + // }, + // { + // nodeIndex: 2, + // nodeAuthToken: 'nodeAuthToken', + // }, + // ], + // hasValidEncKey: true, + // // existingEncKeyPublicData?: { + // // pubKeyX: string; + // // pubKeyY: string; + // // keyIndex: number; + // // }; + // }; + // }; + + // controller.createEncKey = async (params: CreateEncKeyParams) : Promise => { + // Logger.log(params); + // return { + // encKey: 'encKey', + // }; + // }; + + // controller.fetchAndRestoreSeedPhraseMetadata = async () => { + // Logger.log('fetchAndRestoreSeedPhraseMetadata'); + // } + return { controller }; }; diff --git a/app/core/Oauth2Login/Oauth2loginService.ts b/app/core/Oauth2Login/Oauth2loginService.ts index 5a7e493046f7..6a032f98f835 100644 --- a/app/core/Oauth2Login/Oauth2loginService.ts +++ b/app/core/Oauth2Login/Oauth2loginService.ts @@ -17,23 +17,29 @@ import {signInWithGoogle} from 'react-native-google-acm'; import DevLogger from '../SDKConnect/utils/DevLogger'; import { ACTIONS, PREFIXES } from '../../constants/deeplinks'; +import { OAuthVerifier } from '@metamask/seedless-onboarding-controller'; +import { UserActionType } from '../../actions/user'; -const byoaServerUrl = 'https://api-develop-torus-byoa.web3auth.io'; -const Web3AuthNetwork = 'sapphire-testnet'; +const ByoaServerUrl = 'https://api-develop-torus-byoa.web3auth.io'; +// const ByoaServerUrl = 'https://organic-gannet-privately.ngrok-free.app'; +const Web3AuthNetwork = 'sapphire_devnet'; const AppRedirectUri = `${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`; const IosGID = '882363291751-nbbp9n0o307cfil1lup766g1s99k0932.apps.googleusercontent.com'; const IosGoogleRedirectUri = 'com.googleusercontent.apps.882363291751-nbbp9n0o307cfil1lup766g1s99k0932:/oauth2redirect/google'; const AndroidGoogleWebGID = '882363291751-2a37cchrq9oc1lfj1p419otvahnbhguv.apps.googleusercontent.com'; -const AppleServerRedirectUri = `${byoaServerUrl}/api/v1/oauth/callback`; +const AppleServerRedirectUri = `${ByoaServerUrl}/api/v1/oauth/callback`; const AppleWebClientId = 'com.web3auth.appleloginextension'; -type HandleOauth2LoginResult = {type: 'pending'} | {type: AuthSessionResult['type']}; +export type HandleOauth2LoginResult = ({type: 'pending'} | {type: AuthSessionResult['type'], existingUser: boolean} | {type: 'error', error: string}); +export type LoginProvider = 'apple' | 'google'; +export type LoginMode = 'onboarding' | 'change-password'; + interface HandleFlowParams { - provider: 'apple' | 'google'; + provider: LoginProvider; code?: string; idToken?: string; clientId: string; @@ -42,6 +48,17 @@ interface HandleFlowParams { web3AuthNetwork?: string; } +interface ByoaResponse { + id_token: string; + verifier: string; + verifier_id: string; + indexes: Record; + endpoints: Record; + success: boolean; + message: string; + jwt_tokens: Record; +} + export class Oauth2LoginService { public localState: { loginInProgress: boolean; @@ -56,7 +73,7 @@ export class Oauth2LoginService { }; } - #iosHandleOauth2Login = async (provider: 'apple' | 'google') : Promise => { + #iosHandleOauth2Login = async (provider: LoginProvider, mode: LoginMode) : Promise => { try { if (provider === 'apple') { const credential = await signInAsync({ @@ -70,10 +87,14 @@ export class Oauth2LoginService { provider: 'apple', idToken: credential.identityToken, clientId: IosGID, - }); + } ); } - return {type: 'dismiss'}; + return {type: 'dismiss', existingUser: false}; } else if (provider === 'google') { + const state = JSON.stringify({ + mode, + random: Math.random().toString(36).substring(2, 15), + }); const authRequest = new AuthRequest({ clientId: IosGID, redirectUri: IosGoogleRedirectUri, @@ -81,6 +102,7 @@ export class Oauth2LoginService { responseType: ResponseType.Code, codeChallengeMethod: CodeChallengeMethod.S256, usePKCE: true, + state, }); const result = await authRequest.promptAsync({ authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth', @@ -95,7 +117,7 @@ export class Oauth2LoginService { codeVerifier: authRequest.codeVerifier, }); } - return result; + return {...result, existingUser: false}; } throw new Error('Invalid provider : ' + provider); } catch (error) { @@ -103,11 +125,11 @@ export class Oauth2LoginService { message: 'iosHandleOauth2Login', provider, } ); - return {type: 'error'}; + return {type: 'error', existingUser: false}; } }; - #androidHandleOauth2Login = async (provider: 'apple' | 'google') : Promise => { + #androidHandleOauth2Login = async (provider: LoginProvider, mode: LoginMode) : Promise => { try { if (provider === 'apple') { const state = JSON.stringify({ @@ -115,7 +137,8 @@ export class Oauth2LoginService { client_redirect_back_uri: AppRedirectUri, redirectUri: AppleServerRedirectUri, clientId: AppleWebClientId, - random: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15), + random: Math.random().toString(36).substring(2, 15), + mode, }); const authRequest = new AuthRequest({ clientId: AppleWebClientId, @@ -164,11 +187,11 @@ export class Oauth2LoginService { } catch (error) { Logger.log('handleGoogleLogin: error', error); DevLogger.log('handleGoogleLogin: error', error); - return {type: 'error'}; + return {type: 'error', existingUser: false}; } }; - handleCodeFlow = async (params : HandleFlowParams) : Promise<{type: 'success' | 'error', error?: string}> => { + handleCodeFlow = async (params : HandleFlowParams) : Promise<{type: 'success' | 'error', error?: string, existingUser : boolean}> => { const {code, idToken, provider, clientId, redirectUri, codeVerifier, web3AuthNetwork} = params; const pathname = code ? 'api/v1/oauth/token' : 'api/v1/oauth/id_token'; @@ -188,25 +211,25 @@ export class Oauth2LoginService { Logger.log('handleCodeFlow: body', body); try { - const res = await fetch(`${byoaServerUrl}/${pathname}`, { + const res = await fetch(`${ByoaServerUrl}/${pathname}`, { method: 'POST', headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(body), + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), }); - const data = await res.json(); + const data = await res.json() as ByoaResponse; Logger.log('handleCodeFlow: data', data); if (data.success) { - await Engine.context.SeedlessOnboardingController.authenticateOAuthUser({ - idTokens: [data.id_token], - verifier: data.verifier, + const result = await Engine.context.SeedlessOnboardingController.authenticateOAuthUser({ + idTokens: Object.values(data.jwt_tokens), + verifier: data.verifier as OAuthVerifier, verifierID: data.verifier_id, - indexes: [data.index], - endpoints: [data.endpoint], + indexes: Object.values(data.indexes), + endpoints: Object.values(data.endpoints), }); - return {type: 'success'}; + return {type: 'success', existingUser: result.hasValidEncKey}; } throw new Error('Failed to authenticate OAuth user : ' + data.message); } catch (error) { @@ -214,7 +237,7 @@ export class Oauth2LoginService { Logger.error( error as Error, { message: 'handleCodeFlow', } ); - return {type: 'error'}; + return {type: 'error', existingUser: false}; } finally { // ReduxService.store.dispatch({ // }); @@ -223,19 +246,56 @@ export class Oauth2LoginService { } }; - handleOauth2Login = async (provider: 'apple' | 'google') : Promise => { + handleOauth2Login = async (provider: LoginProvider, mode: LoginMode) : Promise => { if (this.localState.loginInProgress) { throw new Error('Login already in progress'); } this.localState.loginInProgress = true; - + let result; if (Platform.OS === 'ios') { - return await this.#iosHandleOauth2Login(provider); + result = await this.#iosHandleOauth2Login(provider, mode); } else if (Platform.OS === 'android') { - return await this.#androidHandleOauth2Login(provider); + result = await this.#androidHandleOauth2Login(provider, mode); + } + + if (result === undefined) { + this.localState.loginInProgress = false; + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN_ERROR, + payload: { + error: 'Invalid platform', + }, + }); + throw new Error('Invalid platform'); + } + + if (result.type === 'success') { + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN_SUCCESS, + payload: { + existingUser: result.existingUser, + }, + }); + } else if (result.type === 'error' && 'error' in result) { + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN_ERROR, + payload: { + error: result.error, + }, + }); + } else if (result.type === 'pending') { + setTimeout(() => { + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN_COMPLETE, + }); + }, 10000); + } else { + this.localState.loginInProgress = false; + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN_RESET, + }); } - this.localState.loginInProgress = false; - throw new Error('Invalid platform'); + return result; }; } diff --git a/package.json b/package.json index 47edac3afe3b..778662ba9587 100644 --- a/package.json +++ b/package.json @@ -204,7 +204,7 @@ "@metamask/rpc-errors": "^7.0.2", "@metamask/scure-bip39": "^2.1.0", "@metamask/sdk-communication-layer": "0.29.0-wallet", - "@metamask/seedless-onboarding-controller": "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?7e3d33696713d2644480de02409db6f570b56fc4", + "@metamask/seedless-onboarding-controller": "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?a46c7fb67392d78e3235a00dbea36e60e483c836", "@metamask/selected-network-controller": "^21.0.0", "@metamask/signature-controller": "^23.1.0", "@metamask/slip44": "^4.1.0", diff --git a/yarn.lock b/yarn.lock index 1eb1ed5e6f51..2c96c2e316c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5357,14 +5357,17 @@ utf-8-validate "^5.0.2" uuid "^8.3.2" -"@metamask/seedless-onboarding-controller@https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?7e3d33696713d2644480de02409db6f570b56fc4": +"@metamask/seedless-onboarding-controller@https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?a46c7fb67392d78e3235a00dbea36e60e483c836": version "0.0.1" - resolved "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?7e3d33696713d2644480de02409db6f570b56fc4#c00513c1d5b5a918f64872c0dd160fde3c383c2d" + resolved "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?a46c7fb67392d78e3235a00dbea36e60e483c836#c03ff8b5d762ef4da9a2bfaa62bd8ecb4f659642" dependencies: "@metamask/base-controller" "^8.0.0" "@metamask/browser-passworder" "^4.3.0" "@metamask/keyring-controller" "^21.0.0" "@metamask/utils" "^11.2.0" + "@noble/ciphers" "^0.5.2" + "@noble/hashes" "^1.4.0" + async-mutex "^0.5.0" "@metamask/selected-network-controller@^21.0.0": version "21.0.0" From 8bb7c59c1a52fffac806eee9ce5a5233673caa73 Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 3 Apr 2025 10:28:22 +0800 Subject: [PATCH 008/187] fix: stable rehydrate flow --- .../Oauth2Login/Oauth2LoginComponent.tsx | 10 +++- app/components/Views/ChoosePassword/index.js | 1 + app/components/Views/Login/index.js | 4 +- app/components/Views/Onboarding/index.js | 2 + app/core/Authentication/Authentication.ts | 35 ++++++++++-- .../Handlers/handleOauth2RedirectUrl.ts | 1 - .../seedless-onboarding-controller/index.ts | 56 ++----------------- app/core/Oauth2Login/Oauth2loginService.ts | 20 +++++++ package.json | 2 +- yarn.lock | 4 +- 10 files changed, 71 insertions(+), 64 deletions(-) diff --git a/app/components/Oauth2Login/Oauth2LoginComponent.tsx b/app/components/Oauth2Login/Oauth2LoginComponent.tsx index 87a9a73ec920..494c8b0873ff 100644 --- a/app/components/Oauth2Login/Oauth2LoginComponent.tsx +++ b/app/components/Oauth2Login/Oauth2LoginComponent.tsx @@ -5,6 +5,8 @@ import { strings } from '../../../locales/i18n'; import DevLogger from '../../core/SDKConnect/utils/DevLogger'; import { useNavigation, ParamListBase, NavigationProp } from '@react-navigation/native'; import Oauth2LoginService from '../../core/Oauth2Login/Oauth2loginService'; +import { ONBOARDING } from '../../constants/navigation'; +import { PREVIOUS_SCREEN } from '../../constants/navigation'; const styles = StyleSheet.create({ buttonWrapper: { marginBottom: 16, @@ -31,7 +33,9 @@ export default function Oauth2LoginComponent( ) { if (result.existingUser) { navigation.navigate('Login'); } else { - navigation.navigate('ChoosePassword'); + navigation.navigate('ChoosePassword',{ + [PREVIOUS_SCREEN]: ONBOARDING, + }); } } }} @@ -52,7 +56,9 @@ export default function Oauth2LoginComponent( ) { if (result.existingUser) { navigation.navigate('Login'); } else { - navigation.navigate('ChoosePassword'); + navigation.navigate('ChoosePassword',{ + [PREVIOUS_SCREEN]: ONBOARDING, + }); } } }} diff --git a/app/components/Views/ChoosePassword/index.js b/app/components/Views/ChoosePassword/index.js index b812f0918df7..856eaeffaea5 100644 --- a/app/components/Views/ChoosePassword/index.js +++ b/app/components/Views/ChoosePassword/index.js @@ -355,6 +355,7 @@ class ChoosePassword extends PureComponent { this.state.rememberMe, ); authType.oauth2Login = this.props.oauth2LoginSuccess; + Logger.log('previous_screen', previous_screen); if (previous_screen === ONBOARDING) { try { diff --git a/app/components/Views/Login/index.js b/app/components/Views/Login/index.js index 1ec71fa63ec9..92594ec35c6d 100644 --- a/app/components/Views/Login/index.js +++ b/app/components/Views/Login/index.js @@ -319,7 +319,9 @@ class Login extends PureComponent { } handleBackPress = async () => { - await Authentication.lockApp(); + if (!this.props.oauth2LoginSuccess) { + await Authentication.lockApp(); + } return false; }; diff --git a/app/components/Views/Onboarding/index.js b/app/components/Views/Onboarding/index.js index 41c38663569f..6968cdfbfeba 100644 --- a/app/components/Views/Onboarding/index.js +++ b/app/components/Views/Onboarding/index.js @@ -305,6 +305,7 @@ class Onboarding extends PureComponent { }; onPressCreate = () => { + this.props.oauth2LoginReset(); const action = () => { const { metrics } = this.props; if (metrics.isEnabled()) { @@ -328,6 +329,7 @@ class Onboarding extends PureComponent { }; onPressImport = () => { + this.props.oauth2LoginReset(); const action = async () => { const { metrics } = this.props; if (metrics.isEnabled()) { diff --git a/app/core/Authentication/Authentication.ts b/app/core/Authentication/Authentication.ts index 4f011388c736..ff7fde486826 100644 --- a/app/core/Authentication/Authentication.ts +++ b/app/core/Authentication/Authentication.ts @@ -33,7 +33,10 @@ import Routes from '../../constants/navigation/Routes'; import { TraceName, TraceOperation, endTrace, trace } from '../../util/trace'; import ReduxService from '../redux'; import { getSeedPhrase } from '../Vault'; -import byteArrayToHex from '../../util/bytes'; +import { wordlist } from '@metamask/scure-bip39/dist/wordlists/english'; +import { uint8ArrayToMnemonic } from '../../util/mnemonic'; +import Logger from '../../util/Logger'; +import Oauth2LoginService from '../Oauth2Login/Oauth2loginService'; /** * Holds auth data used to determine auth configuration @@ -485,10 +488,25 @@ class AuthenticationService { createAndBackupSeedPhrase = async( password: string, ): Promise => { - const { SeedlessOnboardingController, KeyringController } = Engine.context; - await KeyringController.createNewVaultAndKeychain(password); - const seedPhrase = await getSeedPhrase(password); - await SeedlessOnboardingController.createSeedPhraseBackup({password, seedPhrase: byteArrayToHex(seedPhrase)}); + const { SeedlessOnboardingController } = Engine.context; + const { verifier, verifierID } = Oauth2LoginService.getVerifierDetails(); + if (!verifier || !verifierID) { + this.dispatchOauth2Reset(); + throw new Error('Verifier details not found'); + } + // rollback on fail ( reset wallet ) + await this.createWalletVaultAndKeychain(password); + const uint8ArrayMnemonic = await getSeedPhrase(password); + const seedPhrase = uint8ArrayToMnemonic(uint8ArrayMnemonic, wordlist); + + Logger.log('SeedlessOnboardingController state', SeedlessOnboardingController.state); + + await SeedlessOnboardingController.createSeedPhraseBackup({password, seedPhrase, verifier, verifierID }).catch((error) => { + // should allow user to link account later + // prompt that account linking failed but vault was created + Logger.log('error', error); + + }); this.dispatchOauth2Reset(); }; @@ -497,7 +515,12 @@ class AuthenticationService { authData: AuthData, ): Promise => { const { SeedlessOnboardingController } = Engine.context; - const result = await SeedlessOnboardingController.fetchAndRestoreSeedPhraseMetadata(password); + const { verifier, verifierID } = Oauth2LoginService.getVerifierDetails(); + if (!verifier || !verifierID) { + this.dispatchOauth2Reset(); + throw new Error('Verifier details not found'); + } + const result = await SeedlessOnboardingController.fetchAndRestoreSeedPhraseMetadata( verifier, verifierID, password); if (result.secretData !== null) { await this.newWalletAndRestore(password, authData, result.secretData[0], false); // add in more srps diff --git a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts index bebc0b10091b..3c439a5dac66 100644 --- a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts +++ b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts @@ -3,7 +3,6 @@ import Oauth2LoginService, { LoginMode, LoginProvider } from '../../Oauth2Login/ import Logger from '../../../util/Logger'; import { strings } from '../../../../locales/i18n'; import { showAlert } from '../../../actions/alert'; -import { UserActionType } from '../../../actions/user'; function handleOauth2RedirectUrl({ deeplinkManager, diff --git a/app/core/Engine/controllers/seedless-onboarding-controller/index.ts b/app/core/Engine/controllers/seedless-onboarding-controller/index.ts index 66fb4554b86f..64d419e1d326 100644 --- a/app/core/Engine/controllers/seedless-onboarding-controller/index.ts +++ b/app/core/Engine/controllers/seedless-onboarding-controller/index.ts @@ -1,14 +1,10 @@ -import { AuthenticationParams, AuthenticationResult } from '@metamask/seedless-onboarding-controller/dist/ToprfClient.cjs'; -import Logger from '../../../../util/Logger'; import type { ControllerInitFunction } from '../../types'; import { SeedlessOnboardingController, SeedlessOnboardingControllerState, type SeedlessOnboardingControllerMessenger, } from '@metamask/seedless-onboarding-controller'; -import { keccak_256 } from '@noble/hashes/sha3'; -import { secp256k1 } from '@metamask/key-tree'; - +import { Encryptor, LEGACY_DERIVATION_OPTIONS } from '../../../Encryptor'; const getDefaultSeedlessOnboardingControllerState = () : SeedlessOnboardingControllerState => ({ @@ -16,6 +12,9 @@ const getDefaultSeedlessOnboardingControllerState = () : SeedlessOnboardingContr hasValidEncryptionKey: false, }); +const encryptor = new Encryptor({ + keyDerivationOptions: LEGACY_DERIVATION_OPTIONS, +}); /** * Initialize the SeedlessOnboardingController. @@ -35,54 +34,9 @@ export const seedlessOnboardingControllerInit: ControllerInitFunction< const controller = new SeedlessOnboardingController({ messenger: controllerMessenger, state: seedlessOnboardingControllerState, + encryptor, }); - // overwrite function with mock implementation - // controller.authenticateOAuthUser = async (params: AuthenticationParams) : Promise => { - - // Logger.log(params); - // const mockKey = keccak_256.create().update(params.verifier + params.verifierID).digest(); - // const derivedKey = secp256k1.getPublicKey(mockKey, true); - // const derivedKeyHex = Buffer.from(derivedKey).toString('hex'); - // Logger.log(derivedKeyHex); - - - - // const response = await fetch('https://node-2.dev-node.web3auth.io/metadata'); - // const metadata = await response.json(); - // Logger.log(metadata); - // // get from metadata url - // // https://node-2.dev-node.web3auth.io/metadata - // return { - // nodeAuthTokens: [ - // { - // nodeIndex: 1, - // nodeAuthToken: 'nodeAuthToken', - // }, - // { - // nodeIndex: 2, - // nodeAuthToken: 'nodeAuthToken', - // }, - // ], - // hasValidEncKey: true, - // // existingEncKeyPublicData?: { - // // pubKeyX: string; - // // pubKeyY: string; - // // keyIndex: number; - // // }; - // }; - // }; - - // controller.createEncKey = async (params: CreateEncKeyParams) : Promise => { - // Logger.log(params); - // return { - // encKey: 'encKey', - // }; - // }; - - // controller.fetchAndRestoreSeedPhraseMetadata = async () => { - // Logger.log('fetchAndRestoreSeedPhraseMetadata'); - // } return { controller }; }; diff --git a/app/core/Oauth2Login/Oauth2loginService.ts b/app/core/Oauth2Login/Oauth2loginService.ts index 6a032f98f835..93d2a6bcf86f 100644 --- a/app/core/Oauth2Login/Oauth2loginService.ts +++ b/app/core/Oauth2Login/Oauth2loginService.ts @@ -20,6 +20,7 @@ import { ACTIONS, PREFIXES } from '../../constants/deeplinks'; import { OAuthVerifier } from '@metamask/seedless-onboarding-controller'; import { UserActionType } from '../../actions/user'; + const ByoaServerUrl = 'https://api-develop-torus-byoa.web3auth.io'; // const ByoaServerUrl = 'https://organic-gannet-privately.ngrok-free.app'; const Web3AuthNetwork = 'sapphire_devnet'; @@ -63,6 +64,8 @@ export class Oauth2LoginService { public localState: { loginInProgress: boolean; codeVerifier: string | null; + verifier: OAuthVerifier | null; + verifierID: string | null; }; @@ -70,6 +73,8 @@ export class Oauth2LoginService { this.localState = { loginInProgress: false, codeVerifier: null, + verifier: null, + verifierID: null, }; } @@ -222,6 +227,9 @@ export class Oauth2LoginService { const data = await res.json() as ByoaResponse; Logger.log('handleCodeFlow: data', data); if (data.success) { + this.localState.verifier = data.verifier as OAuthVerifier; + this.localState.verifierID = data.verifier_id; + const result = await Engine.context.SeedlessOnboardingController.authenticateOAuthUser({ idTokens: Object.values(data.jwt_tokens), verifier: data.verifier as OAuthVerifier, @@ -229,6 +237,8 @@ export class Oauth2LoginService { indexes: Object.values(data.indexes), endpoints: Object.values(data.endpoints), }); + Logger.log('handleCodeFlow: result', result); + Logger.log('handleCodeFlow: SeedlessOnboardingController state', Engine.context.SeedlessOnboardingController.state); return {type: 'success', existingUser: result.hasValidEncKey}; } throw new Error('Failed to authenticate OAuth user : ' + data.message); @@ -297,6 +307,16 @@ export class Oauth2LoginService { } return result; }; + + getVerifierDetails = () => ({ + verifier: this.localState.verifier, + verifierID: this.localState.verifierID, + }); + + clearVerifierDetails = () => { + this.localState.verifier = null; + this.localState.verifierID = null; + }; } export default new Oauth2LoginService(); diff --git a/package.json b/package.json index 778662ba9587..85b0ca9d4e0e 100644 --- a/package.json +++ b/package.json @@ -204,7 +204,7 @@ "@metamask/rpc-errors": "^7.0.2", "@metamask/scure-bip39": "^2.1.0", "@metamask/sdk-communication-layer": "0.29.0-wallet", - "@metamask/seedless-onboarding-controller": "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?a46c7fb67392d78e3235a00dbea36e60e483c836", + "@metamask/seedless-onboarding-controller": "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?fe57cedde254f219d1958e8cd9b89549bda9c881", "@metamask/selected-network-controller": "^21.0.0", "@metamask/signature-controller": "^23.1.0", "@metamask/slip44": "^4.1.0", diff --git a/yarn.lock b/yarn.lock index 2c96c2e316c4..39b8fb7b0a26 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5357,9 +5357,9 @@ utf-8-validate "^5.0.2" uuid "^8.3.2" -"@metamask/seedless-onboarding-controller@https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?a46c7fb67392d78e3235a00dbea36e60e483c836": +"@metamask/seedless-onboarding-controller@https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?fe57cedde254f219d1958e8cd9b89549bda9c881": version "0.0.1" - resolved "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?a46c7fb67392d78e3235a00dbea36e60e483c836#c03ff8b5d762ef4da9a2bfaa62bd8ecb4f659642" + resolved "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?fe57cedde254f219d1958e8cd9b89549bda9c881#3f15adf062aaf13571e852800acdf071e1f75b3c" dependencies: "@metamask/base-controller" "^8.0.0" "@metamask/browser-passworder" "^4.3.0" From 7bb1f70e900fd7df9bb4f967196e751ab5d5e478 Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 3 Apr 2025 12:07:56 +0800 Subject: [PATCH 009/187] fix: update login --- app/components/Views/Onboarding/index.js | 69 ++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/app/components/Views/Onboarding/index.js b/app/components/Views/Onboarding/index.js index 07d10a398055..d22383985bd4 100644 --- a/app/components/Views/Onboarding/index.js +++ b/app/components/Views/Onboarding/index.js @@ -58,6 +58,7 @@ import Icon, { import ButtonComp, { ButtonVariants, } from '../../../component-library/components/Buttons/Button'; +import Oauth2loginService from '../../../core/Oauth2Login/Oauth2loginService'; const createStyles = (colors) => StyleSheet.create({ @@ -386,6 +387,69 @@ class Onboarding extends PureComponent { this.handleExistingUser(action); }; + + metricNavigationWrapper = (targetRoute, previousScreen) => { + const { metrics } = this.props; + if (metrics.isEnabled()) { + this.props.navigation.push( + targetRoute, + { + [PREVIOUS_SCREEN]: previousScreen, + } + ); + this.track(MetaMetricsEvents.WALLET_IMPORT_STARTED); + } else { + this.props.navigation.navigate('OptinMetrics', { + onContinue: () => { + this.props.navigation.replace( + targetRoute, + { + [PREVIOUS_SCREEN]: previousScreen, + } + ); + this.track(MetaMetricsEvents.WALLET_IMPORT_STARTED); + }, + }); + } + }; + + onPressContinueWithApple = async () => { + const action = async () => { + const result = await Oauth2loginService.handleOauth2Login('apple', 'onboarding').catch((e) => { + DevLogger.log(e); + return {type: 'error', error: e, existingUser: false}; + }); + + if (result.type === 'success') { + + if (result.existingUser) { + this.metricNavigationWrapper('Login', ONBOARDING); + } else { + this.metricNavigationWrapper('ChoosePassword', ONBOARDING); + } + } + }; + this.handleExistingUser(action); + }; + + onPressContinueWithGoogle = async () => { + const action = async () => { + const result = await Oauth2loginService.handleOauth2Login('google', 'onboarding').catch((e) => { + DevLogger.log(e); + return {type: 'error', error: e, existingUser: false}; + }); + + if (result.type === 'success') { + if (result.existingUser) { + this.metricNavigationWrapper('Login', ONBOARDING); + } else { + this.metricNavigationWrapper('ChoosePassword', ONBOARDING); + } + } + }; + this.handleExistingUser(action); + }; + track = (event) => { trackOnboarding(MetricsEventBuilder.createEventBuilder(event).build()); }; @@ -438,11 +502,10 @@ class Onboarding extends PureComponent { {strings('onboarding.title')} - Date: Thu, 3 Apr 2025 16:23:46 +0800 Subject: [PATCH 010/187] fix: style typing error --- app/components/Views/Onboarding/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/Views/Onboarding/index.js b/app/components/Views/Onboarding/index.js index d22383985bd4..e0157231a91e 100644 --- a/app/components/Views/Onboarding/index.js +++ b/app/components/Views/Onboarding/index.js @@ -92,7 +92,7 @@ const createStyles = (colors) => title: { textAlign: 'center', fontSize: 32, - fontWeight: 700, + fontWeight: '700', lineHeight: 38, }, ctas: { From abe4b278a7afa4073028f3c262006199b550524d Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 4 Apr 2025 10:30:05 +0800 Subject: [PATCH 011/187] fix: update mock server redirect to onboarding success after wallet created for seedless --- app/components/Views/ChoosePassword/index.js | 17 ++++++++++++++--- package.json | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/app/components/Views/ChoosePassword/index.js b/app/components/Views/ChoosePassword/index.js index 12797de1663b..54f3257be8e7 100644 --- a/app/components/Views/ChoosePassword/index.js +++ b/app/components/Views/ChoosePassword/index.js @@ -36,7 +36,10 @@ import AppConstants from '../../../core/AppConstants'; import OnboardingProgress from '../../UI/OnboardingProgress'; import zxcvbn from 'zxcvbn'; import Logger from '../../../util/Logger'; -import { ONBOARDING, PREVIOUS_SCREEN } from '../../../constants/navigation'; +import { + ONBOARDING, + PREVIOUS_SCREEN, +} from '../../../constants/navigation'; import { EXISTING_USER, TRUE, @@ -71,7 +74,7 @@ import Button, { ButtonVariants, ButtonWidthTypes, } from '../../../component-library/components/Buttons/Button'; - +import Routes from '../../../constants/navigation/Routes'; const createStyles = (colors) => StyleSheet.create({ mainWrapper: { @@ -455,7 +458,15 @@ class ChoosePassword extends PureComponent { this.props.passwordSet(); this.props.setLockTime(AppConstants.DEFAULT_LOCK_TIMEOUT); this.setState({ loading: false }); - this.props.navigation.replace('AccountBackupStep1'); + + if (authType.oauth2Login) { + this.props.navigation.reset({ + index: 1, + routes: [{ name: Routes.ONBOARDING.SUCCESS }], + }); + } else { + this.props.navigation.replace('AccountBackupStep1'); + } this.track(MetaMetricsEvents.WALLET_CREATED, { biometrics_enabled: Boolean(this.state.biometryType), }); diff --git a/package.json b/package.json index 85b0ca9d4e0e..31a5ee607448 100644 --- a/package.json +++ b/package.json @@ -204,7 +204,7 @@ "@metamask/rpc-errors": "^7.0.2", "@metamask/scure-bip39": "^2.1.0", "@metamask/sdk-communication-layer": "0.29.0-wallet", - "@metamask/seedless-onboarding-controller": "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?fe57cedde254f219d1958e8cd9b89549bda9c881", + "@metamask/seedless-onboarding-controller": "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?48cfe50c89299385fbc64cfd0bf548501623267b", "@metamask/selected-network-controller": "^21.0.0", "@metamask/signature-controller": "^23.1.0", "@metamask/slip44": "^4.1.0", From 45fd16732684219e4ebbaa5a2ae07b1993957647 Mon Sep 17 00:00:00 2001 From: ieow Date: Mon, 7 Apr 2025 11:47:42 +0800 Subject: [PATCH 012/187] update: yarn lock file --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 39b8fb7b0a26..9dd208c4350c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5357,9 +5357,9 @@ utf-8-validate "^5.0.2" uuid "^8.3.2" -"@metamask/seedless-onboarding-controller@https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?fe57cedde254f219d1958e8cd9b89549bda9c881": +"@metamask/seedless-onboarding-controller@https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?48cfe50c89299385fbc64cfd0bf548501623267b": version "0.0.1" - resolved "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?fe57cedde254f219d1958e8cd9b89549bda9c881#3f15adf062aaf13571e852800acdf071e1f75b3c" + resolved "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?48cfe50c89299385fbc64cfd0bf548501623267b#847d115723b8c0bed9d3e0e090306e59b489ebd7" dependencies: "@metamask/base-controller" "^8.0.0" "@metamask/browser-passworder" "^4.3.0" From 1ea8457cb5a98ac6e60a8208d3219f219ef3410e Mon Sep 17 00:00:00 2001 From: ieow Date: Mon, 7 Apr 2025 16:10:52 +0800 Subject: [PATCH 013/187] fix: add loading --- .../Oauth2Login/Oauth2LoginComponent.tsx | 4 +-- .../Handlers/handleOauth2RedirectUrl.ts | 30 +++++++++++++++++-- app/core/Oauth2Login/Oauth2loginService.ts | 20 +++++++++++++ 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/app/components/Oauth2Login/Oauth2LoginComponent.tsx b/app/components/Oauth2Login/Oauth2LoginComponent.tsx index 494c8b0873ff..d88478ed8928 100644 --- a/app/components/Oauth2Login/Oauth2LoginComponent.tsx +++ b/app/components/Oauth2Login/Oauth2LoginComponent.tsx @@ -5,8 +5,8 @@ import { strings } from '../../../locales/i18n'; import DevLogger from '../../core/SDKConnect/utils/DevLogger'; import { useNavigation, ParamListBase, NavigationProp } from '@react-navigation/native'; import Oauth2LoginService from '../../core/Oauth2Login/Oauth2loginService'; -import { ONBOARDING } from '../../constants/navigation'; -import { PREVIOUS_SCREEN } from '../../constants/navigation'; +import { ONBOARDING, PREVIOUS_SCREEN } from '../../constants/navigation'; + const styles = StyleSheet.create({ buttonWrapper: { marginBottom: 16, diff --git a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts index 3c439a5dac66..5574c1ec35dc 100644 --- a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts +++ b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts @@ -3,8 +3,11 @@ import Oauth2LoginService, { LoginMode, LoginProvider } from '../../Oauth2Login/ import Logger from '../../../util/Logger'; import { strings } from '../../../../locales/i18n'; import { showAlert } from '../../../actions/alert'; +import { PREVIOUS_SCREEN } from '../../../constants/navigation'; +import ReduxService from '../../redux/ReduxService'; +import { UserActionType } from '../../../actions/user'; -function handleOauth2RedirectUrl({ +async function handleOauth2RedirectUrl({ deeplinkManager, url, base, @@ -39,11 +42,24 @@ function handleOauth2RedirectUrl({ Logger.log('handleOauth2RedirectUrl: mode', mode); Logger.log('handleOauth2RedirectUrl: result.existingUser', result.existingUser); if (mode === 'onboarding') { + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN_SUCCESS, + payload: { + existingUser: result.existingUser, + }, + }); if (result.existingUser) { deeplinkManager.navigation.navigate('Login'); } else { - deeplinkManager.navigation.navigate('ChoosePassword'); + deeplinkManager.navigation.navigate('ChoosePassword', { + [PREVIOUS_SCREEN]: 'onboarding', + }); } + + ReduxService.store.dispatch({ + type: UserActionType.LOADING_UNSET, + }); + } else if (mode === 'change-password') { deeplinkManager.navigation.navigate('ChangePassword'); } @@ -51,6 +67,15 @@ function handleOauth2RedirectUrl({ } }).catch((error) => { Logger.log('handleOauth2RedirectUrl: error', error); + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN_ERROR, + payload: { + error: error.message, + }, + }); + ReduxService.store.dispatch({ + type: UserActionType.LOADING_UNSET, + }); }); } else { deeplinkManager.dispatch( @@ -62,7 +87,6 @@ function handleOauth2RedirectUrl({ }), ); } - } export default handleOauth2RedirectUrl; diff --git a/app/core/Oauth2Login/Oauth2loginService.ts b/app/core/Oauth2Login/Oauth2loginService.ts index 93d2a6bcf86f..a41d283c827c 100644 --- a/app/core/Oauth2Login/Oauth2loginService.ts +++ b/app/core/Oauth2Login/Oauth2loginService.ts @@ -261,6 +261,13 @@ export class Oauth2LoginService { throw new Error('Login already in progress'); } this.localState.loginInProgress = true; + ReduxService.store.dispatch({ + type: UserActionType.LOADING_SET, + payload: { + loadingMsg: 'Logging in...', + }, + }); + let result; if (Platform.OS === 'ios') { result = await this.#iosHandleOauth2Login(provider, mode); @@ -305,6 +312,19 @@ export class Oauth2LoginService { type: UserActionType.OAUTH2_LOGIN_RESET, }); } + + if ( result.type !== 'pending') { + ReduxService.store.dispatch({ + type: UserActionType.LOADING_UNSET, + }); + } else { + setTimeout(() => { + ReduxService.store.dispatch({ + type: UserActionType.LOADING_UNSET, + }); + }, 10000); + } + return result; }; From d5670e5d91f18ae75833b7afaba16e6b7f7cb6e2 Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 4 Apr 2025 10:30:05 +0800 Subject: [PATCH 014/187] fix: update mock server redirect to onboarding success after wallet created for seedless --- app/components/Views/ChoosePassword/index.js | 17 +++++++++++++++-- package.json | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/app/components/Views/ChoosePassword/index.js b/app/components/Views/ChoosePassword/index.js index 856eaeffaea5..5b64d783cac5 100644 --- a/app/components/Views/ChoosePassword/index.js +++ b/app/components/Views/ChoosePassword/index.js @@ -38,7 +38,10 @@ import AppConstants from '../../../core/AppConstants'; import OnboardingProgress from '../../UI/OnboardingProgress'; import zxcvbn from 'zxcvbn'; import Logger from '../../../util/Logger'; -import { ONBOARDING, PREVIOUS_SCREEN } from '../../../constants/navigation'; +import { + ONBOARDING, + PREVIOUS_SCREEN, +} from '../../../constants/navigation'; import { EXISTING_USER, TRUE, @@ -63,6 +66,8 @@ import navigateTermsOfUse from '../../../util/termsOfUse/termsOfUse'; import { ChoosePasswordSelectorsIDs } from '../../../../e2e/selectors/Onboarding/ChoosePassword.selectors'; import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; import { MetricsEventBuilder } from '../../../core/Analytics/MetricsEventBuilder'; +import Routes from '../../../constants/navigation/Routes'; + const createStyles = (colors) => StyleSheet.create({ mainWrapper: { @@ -376,7 +381,15 @@ class ChoosePassword extends PureComponent { this.props.passwordSet(); this.props.setLockTime(AppConstants.DEFAULT_LOCK_TIMEOUT); this.setState({ loading: false }); - this.props.navigation.replace('AccountBackupStep1'); + + if (authType.oauth2Login) { + this.props.navigation.reset({ + index: 1, + routes: [{ name: Routes.ONBOARDING.SUCCESS }], + }); + } else { + this.props.navigation.replace('AccountBackupStep1'); + } this.track(MetaMetricsEvents.WALLET_CREATED, { biometrics_enabled: Boolean(this.state.biometryType), }); diff --git a/package.json b/package.json index 85b0ca9d4e0e..31a5ee607448 100644 --- a/package.json +++ b/package.json @@ -204,7 +204,7 @@ "@metamask/rpc-errors": "^7.0.2", "@metamask/scure-bip39": "^2.1.0", "@metamask/sdk-communication-layer": "0.29.0-wallet", - "@metamask/seedless-onboarding-controller": "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?fe57cedde254f219d1958e8cd9b89549bda9c881", + "@metamask/seedless-onboarding-controller": "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?48cfe50c89299385fbc64cfd0bf548501623267b", "@metamask/selected-network-controller": "^21.0.0", "@metamask/signature-controller": "^23.1.0", "@metamask/slip44": "^4.1.0", From 4752b3a11d25476264cb4ba6fe2db648cb44bf74 Mon Sep 17 00:00:00 2001 From: ieow Date: Mon, 7 Apr 2025 11:47:42 +0800 Subject: [PATCH 015/187] update: yarn lock file --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 39b8fb7b0a26..9dd208c4350c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5357,9 +5357,9 @@ utf-8-validate "^5.0.2" uuid "^8.3.2" -"@metamask/seedless-onboarding-controller@https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?fe57cedde254f219d1958e8cd9b89549bda9c881": +"@metamask/seedless-onboarding-controller@https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?48cfe50c89299385fbc64cfd0bf548501623267b": version "0.0.1" - resolved "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?fe57cedde254f219d1958e8cd9b89549bda9c881#3f15adf062aaf13571e852800acdf071e1f75b3c" + resolved "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?48cfe50c89299385fbc64cfd0bf548501623267b#847d115723b8c0bed9d3e0e090306e59b489ebd7" dependencies: "@metamask/base-controller" "^8.0.0" "@metamask/browser-passworder" "^4.3.0" From 6df077c00e871afbaaf7328c46feb8c7601b2f1c Mon Sep 17 00:00:00 2001 From: ieow Date: Mon, 7 Apr 2025 16:10:52 +0800 Subject: [PATCH 016/187] fix: add loading --- .../Oauth2Login/Oauth2LoginComponent.tsx | 4 +-- .../Handlers/handleOauth2RedirectUrl.ts | 30 +++++++++++++++++-- app/core/Oauth2Login/Oauth2loginService.ts | 20 +++++++++++++ 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/app/components/Oauth2Login/Oauth2LoginComponent.tsx b/app/components/Oauth2Login/Oauth2LoginComponent.tsx index 494c8b0873ff..d88478ed8928 100644 --- a/app/components/Oauth2Login/Oauth2LoginComponent.tsx +++ b/app/components/Oauth2Login/Oauth2LoginComponent.tsx @@ -5,8 +5,8 @@ import { strings } from '../../../locales/i18n'; import DevLogger from '../../core/SDKConnect/utils/DevLogger'; import { useNavigation, ParamListBase, NavigationProp } from '@react-navigation/native'; import Oauth2LoginService from '../../core/Oauth2Login/Oauth2loginService'; -import { ONBOARDING } from '../../constants/navigation'; -import { PREVIOUS_SCREEN } from '../../constants/navigation'; +import { ONBOARDING, PREVIOUS_SCREEN } from '../../constants/navigation'; + const styles = StyleSheet.create({ buttonWrapper: { marginBottom: 16, diff --git a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts index 3c439a5dac66..5574c1ec35dc 100644 --- a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts +++ b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts @@ -3,8 +3,11 @@ import Oauth2LoginService, { LoginMode, LoginProvider } from '../../Oauth2Login/ import Logger from '../../../util/Logger'; import { strings } from '../../../../locales/i18n'; import { showAlert } from '../../../actions/alert'; +import { PREVIOUS_SCREEN } from '../../../constants/navigation'; +import ReduxService from '../../redux/ReduxService'; +import { UserActionType } from '../../../actions/user'; -function handleOauth2RedirectUrl({ +async function handleOauth2RedirectUrl({ deeplinkManager, url, base, @@ -39,11 +42,24 @@ function handleOauth2RedirectUrl({ Logger.log('handleOauth2RedirectUrl: mode', mode); Logger.log('handleOauth2RedirectUrl: result.existingUser', result.existingUser); if (mode === 'onboarding') { + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN_SUCCESS, + payload: { + existingUser: result.existingUser, + }, + }); if (result.existingUser) { deeplinkManager.navigation.navigate('Login'); } else { - deeplinkManager.navigation.navigate('ChoosePassword'); + deeplinkManager.navigation.navigate('ChoosePassword', { + [PREVIOUS_SCREEN]: 'onboarding', + }); } + + ReduxService.store.dispatch({ + type: UserActionType.LOADING_UNSET, + }); + } else if (mode === 'change-password') { deeplinkManager.navigation.navigate('ChangePassword'); } @@ -51,6 +67,15 @@ function handleOauth2RedirectUrl({ } }).catch((error) => { Logger.log('handleOauth2RedirectUrl: error', error); + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN_ERROR, + payload: { + error: error.message, + }, + }); + ReduxService.store.dispatch({ + type: UserActionType.LOADING_UNSET, + }); }); } else { deeplinkManager.dispatch( @@ -62,7 +87,6 @@ function handleOauth2RedirectUrl({ }), ); } - } export default handleOauth2RedirectUrl; diff --git a/app/core/Oauth2Login/Oauth2loginService.ts b/app/core/Oauth2Login/Oauth2loginService.ts index 93d2a6bcf86f..a41d283c827c 100644 --- a/app/core/Oauth2Login/Oauth2loginService.ts +++ b/app/core/Oauth2Login/Oauth2loginService.ts @@ -261,6 +261,13 @@ export class Oauth2LoginService { throw new Error('Login already in progress'); } this.localState.loginInProgress = true; + ReduxService.store.dispatch({ + type: UserActionType.LOADING_SET, + payload: { + loadingMsg: 'Logging in...', + }, + }); + let result; if (Platform.OS === 'ios') { result = await this.#iosHandleOauth2Login(provider, mode); @@ -305,6 +312,19 @@ export class Oauth2LoginService { type: UserActionType.OAUTH2_LOGIN_RESET, }); } + + if ( result.type !== 'pending') { + ReduxService.store.dispatch({ + type: UserActionType.LOADING_UNSET, + }); + } else { + setTimeout(() => { + ReduxService.store.dispatch({ + type: UserActionType.LOADING_UNSET, + }); + }, 10000); + } + return result; }; From bd6ea2cc28b44c708384a9c9fdcbe1ea4cbebb27 Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 8 Apr 2025 16:54:35 +0800 Subject: [PATCH 017/187] fix: clean up --- app/actions/seedlessOnboarding/index.ts | 75 ------------------- app/constants/navigation/Routes.ts | 1 + app/core/Authentication/Authentication.ts | 32 ++++---- .../DeeplinkManager/DeeplinkManager.test.ts | 15 ++++ .../Handlers/handleOauth2RedirectUrl.ts | 18 ++--- app/core/EngineService/EngineService.ts | 3 - app/core/Oauth2Login/Oauth2loginService.ts | 18 ++--- package.json | 4 +- yarn.lock | 9 ++- 9 files changed, 53 insertions(+), 122 deletions(-) delete mode 100644 app/actions/seedlessOnboarding/index.ts diff --git a/app/actions/seedlessOnboarding/index.ts b/app/actions/seedlessOnboarding/index.ts deleted file mode 100644 index 154f5a695a8d..000000000000 --- a/app/actions/seedlessOnboarding/index.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { getErrorMessage } from '@metamask/utils'; -import Engine from '../../core/Engine'; -import { AuthenticateUserParams } from '@metamask/seedless-onboarding-controller'; - - -// /** -// * Action to signal that app services are ready -// */ -// export function setAppServicesReady(): SetAppServicesReadyAction { -// return { -// type: UserActionType.SET_APP_SERVICES_READY, -// }; -// } -// export function createAndBackupSeedPhrase( -// password: string, -// oAuthLoginInfo: { -// verifier: OAuthProvider; -// idToken: string; -// verifierId: string; -// }, -// ): ThunkAction { -// return async (dispatch: MetaMaskReduxDispatch) => { -// // dispatch(showLoadingIndication()); - -// try { -// await createNewVault(password); -// const seedPhrase = await getSeedPhrase(password); -// const { verifier, idToken, verifierId } = oAuthLoginInfo; -// await backupSeedPhrase(seedPhrase, password, idToken, verifier, verifierId); -// return seedPhrase; -// } catch (error) { -// dispatch(displayWarning(error)); -// if (isErrorWithMessage(error)) { -// throw new Error(getErrorMessage(error)); -// } else { -// throw error; -// } -// } finally { -// dispatch(hideLoadingIndication()); -// } -// }; -// } - -export const performeSeedlessOnboardingAuthenticate = async (authToken: AuthenticateUserParams) => { - try { - const result = await Engine.context.SeedlessOnboardingController.authenticateOAuthUser(authToken); - return result; - } catch (error) { - return getErrorMessage(error); - } -}; - -export const performSeedlessOnboardingCreate = async (params: {password: string, seedPhrase: string}) => { - try { - const result = await Engine.context.SeedlessOnboardingController.createSeedPhraseBackup(params); - // if (!result) { - // return getErrorMessage(identityErrors.PERFORM_SIGN_IN); - // } - return result; - } catch (error) { - return getErrorMessage(error); - } -}; - -export const performSeedlessOnboardingRehydrate = async (password: string) => { - try { - const result = await Engine.context.SeedlessOnboardingController.fetchAndRestoreSeedPhraseMetadata(password); - // if (!result) { - // return getErrorMessage(identityErrors.PERFORM_SIGN_IN); - // } - return result; - } catch (error) { - return getErrorMessage(error); - } -}; diff --git a/app/constants/navigation/Routes.ts b/app/constants/navigation/Routes.ts index 6401ccfd22ae..911b7690cafc 100644 --- a/app/constants/navigation/Routes.ts +++ b/app/constants/navigation/Routes.ts @@ -68,6 +68,7 @@ const Routes = { STEP_3: 'ManualBackupStep3', }, IMPORT_FROM_SECRET_RECOVERY_PHRASE: 'ImportFromSecretRecoveryPhrase', + CHOOSE_PASSWORD: 'ChoosePassword', }, SEND_FLOW: { SEND_TO: 'SendTo', diff --git a/app/core/Authentication/Authentication.ts b/app/core/Authentication/Authentication.ts index ff7fde486826..e7ae60490f62 100644 --- a/app/core/Authentication/Authentication.ts +++ b/app/core/Authentication/Authentication.ts @@ -37,6 +37,8 @@ import { wordlist } from '@metamask/scure-bip39/dist/wordlists/english'; import { uint8ArrayToMnemonic } from '../../util/mnemonic'; import Logger from '../../util/Logger'; import Oauth2LoginService from '../Oauth2Login/Oauth2loginService'; +import { resetVaultBackup } from '../BackupVault/backupVault'; +import { bytesToString } from '@metamask/utils'; /** * Holds auth data used to determine auth configuration @@ -113,8 +115,6 @@ class AuthenticationService { // eslint-disable-next-line @typescript-eslint/no-explicit-any const { KeyringController }: any = Engine.context; await Engine.resetState(); - // eslint-disable-next-line no-console - console.log('KeyringController.state', KeyringController.state); await KeyringController.createNewVaultAndKeychain(password); password = this.wipeSensitiveData(); }; @@ -501,13 +501,18 @@ class AuthenticationService { Logger.log('SeedlessOnboardingController state', SeedlessOnboardingController.state); - await SeedlessOnboardingController.createSeedPhraseBackup({password, seedPhrase, verifier, verifierID }).catch((error) => { - // should allow user to link account later - // prompt that account linking failed but vault was created - Logger.log('error', error); + await SeedlessOnboardingController.createSeedPhraseBackup({password, seedPhrase, verifier, verifierID }) + .catch(async (error) => { + await this.newWalletAndKeychain(`${Date.now()}`, { + currentAuthType: AUTHENTICATION_TYPE.UNKNOWN, + }); + await resetVaultBackup(); + throw error; + }).finally(() => { + this.dispatchOauth2Reset(); + }); - }); - this.dispatchOauth2Reset(); + Logger.log('SeedlessOnboardingController state', SeedlessOnboardingController.state); }; rehydrateSeedPhrase = async( @@ -521,12 +526,13 @@ class AuthenticationService { throw new Error('Verifier details not found'); } const result = await SeedlessOnboardingController.fetchAndRestoreSeedPhraseMetadata( verifier, verifierID, password); - if (result.secretData !== null) { - await this.newWalletAndRestore(password, authData, result.secretData[0], false); + if (result !== null && result.length > 0) { + const seedPhrase = bytesToString(result.at(-1) ?? new Uint8Array()); + await this.newWalletAndRestore(password, authData, seedPhrase, false); // add in more srps - } else { - // should we throw an error here? - await this.newWalletAndKeychain(password, authData); + } else { + this.dispatchOauth2Reset(); + throw new Error('No account data found'); } this.dispatchOauth2Reset(); // throw error if no secret data diff --git a/app/core/DeeplinkManager/DeeplinkManager.test.ts b/app/core/DeeplinkManager/DeeplinkManager.test.ts index a0865fc09821..1f9fd52bc6c8 100644 --- a/app/core/DeeplinkManager/DeeplinkManager.test.ts +++ b/app/core/DeeplinkManager/DeeplinkManager.test.ts @@ -3,6 +3,7 @@ import DeeplinkManager from './DeeplinkManager'; import handleBrowserUrl from './Handlers/handleBrowserUrl'; import handleEthereumUrl from './Handlers/handleEthereumUrl'; import handleRampUrl from './Handlers/handleRampUrl'; +import handleOauth2RedirectUrl from './Handlers/handleOauth2RedirectUrl'; import switchNetwork from './Handlers/switchNetwork'; import parseDeeplink from './ParseManager/parseDeeplink'; import approveTransaction from './TransactionManager/approveTransaction'; @@ -14,6 +15,7 @@ jest.mock('./Handlers/handleBrowserUrl'); jest.mock('./Handlers/handleRampUrl'); jest.mock('./ParseManager/parseDeeplink'); jest.mock('./Handlers/switchNetwork'); +jest.mock('./Handlers/handleOauth2RedirectUrl'); const mockNavigation = { navigate: jest.fn(), @@ -124,6 +126,19 @@ describe('DeeplinkManager', () => { ); }); + + it('should handle oauth2 redirect url action correctly', () => { + const redirectPath = '/example/path?and=params'; + deeplinkManager._handleOauth2RedirectUrl(redirectPath); + expect(handleOauth2RedirectUrl).toHaveBeenCalledWith( + expect.objectContaining({ + redirectPath, + navigation: mockNavigation, + }), + ); + }); + + it('should parse deeplinks correctly', () => { const url = 'http://example.com'; const browserCallBack = jest.fn(); diff --git a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts index 5574c1ec35dc..ec2809595538 100644 --- a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts +++ b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts @@ -6,6 +6,7 @@ import { showAlert } from '../../../actions/alert'; import { PREVIOUS_SCREEN } from '../../../constants/navigation'; import ReduxService from '../../redux/ReduxService'; import { UserActionType } from '../../../actions/user'; +import Routes from '../../../constants/navigation/Routes'; async function handleOauth2RedirectUrl({ deeplinkManager, @@ -26,21 +27,12 @@ async function handleOauth2RedirectUrl({ const clientId = state.clientId as string; - Logger.log('handleOauth2RedirectUrl: provider', provider); - Logger.log('handleOauth2RedirectUrl: code', code); - - Logger.log('handleOauth2RedirectUrl: state', state); if (code ) { Oauth2LoginService.handleCodeFlow({ code, provider , clientId, redirectUri: state.redirectUri, codeVerifier: Oauth2LoginService.localState.codeVerifier ?? undefined }) .then((result) => { Logger.log('handleOauth2RedirectUrl: result', result); - Logger.log('handleOauth2RedirectUrl: result.existingUser', result.existingUser); if (result.type === 'success') { - // deeplinkManager.dispatch({type: UserActionType.OAUTH2_LOGIN_SUCCESS}); - - Logger.log('handleOauth2RedirectUrl: mode', mode); - Logger.log('handleOauth2RedirectUrl: result.existingUser', result.existingUser); if (mode === 'onboarding') { ReduxService.store.dispatch({ type: UserActionType.OAUTH2_LOGIN_SUCCESS, @@ -49,10 +41,10 @@ async function handleOauth2RedirectUrl({ }, }); if (result.existingUser) { - deeplinkManager.navigation.navigate('Login'); + deeplinkManager.navigation.navigate(Routes.ONBOARDING.LOGIN); } else { - deeplinkManager.navigation.navigate('ChoosePassword', { - [PREVIOUS_SCREEN]: 'onboarding', + deeplinkManager.navigation.navigate(Routes.ONBOARDING.CHOOSE_PASSWORD, { + [PREVIOUS_SCREEN]: Routes.ONBOARDING.ONBOARDING, }); } @@ -61,7 +53,7 @@ async function handleOauth2RedirectUrl({ }); } else if (mode === 'change-password') { - deeplinkManager.navigation.navigate('ChangePassword'); + deeplinkManager.navigation.navigate(Routes.ONBOARDING.CHOOSE_PASSWORD); } return code; } diff --git a/app/core/EngineService/EngineService.ts b/app/core/EngineService/EngineService.ts index b8e074b38875..efb395258b92 100644 --- a/app/core/EngineService/EngineService.ts +++ b/app/core/EngineService/EngineService.ts @@ -55,9 +55,6 @@ export class EngineService { tags: getTraceTags(reduxState), }); const state = reduxState?.engine?.backgroundState ?? {}; - // const state = {}; - // eslint-disable-next-line no-console - console.log('EngineService: rstate', state); const Engine = UntypedEngine; try { Logger.log(`${LOG_TAG}: Initializing Engine:`, { diff --git a/app/core/Oauth2Login/Oauth2loginService.ts b/app/core/Oauth2Login/Oauth2loginService.ts index a41d283c827c..447e2e504765 100644 --- a/app/core/Oauth2Login/Oauth2loginService.ts +++ b/app/core/Oauth2Login/Oauth2loginService.ts @@ -1,4 +1,3 @@ - import { Platform } from 'react-native'; @@ -178,21 +177,20 @@ export class Oauth2LoginService { Logger.log('handleGoogleLogin: result', result); DevLogger.log('handleGoogleLogin: result', result); - if (result.type === 'success') { + if (result.idToken === 'google-signin') { return this.handleCodeFlow({ provider: 'google', - code: result.params.code, // result.params.idToken - idToken: result.params.idToken, + idToken: result.idToken, clientId: AndroidGoogleWebGID, }); } - return result; + throw new Error('Invalid login : ' + provider); } - throw new Error('Invalid provider'); + throw new Error('Invalid provider : ' + provider); } catch (error) { Logger.log('handleGoogleLogin: error', error); DevLogger.log('handleGoogleLogin: error', error); - return {type: 'error', existingUser: false}; + return {type: 'error', existingUser: false, error: error instanceof Error ? error.message : 'Unknown error'}; } }; @@ -238,19 +236,15 @@ export class Oauth2LoginService { endpoints: Object.values(data.endpoints), }); Logger.log('handleCodeFlow: result', result); - Logger.log('handleCodeFlow: SeedlessOnboardingController state', Engine.context.SeedlessOnboardingController.state); return {type: 'success', existingUser: result.hasValidEncKey}; } throw new Error('Failed to authenticate OAuth user : ' + data.message); } catch (error) { - Logger.log('handleCodeFlow: error', error); Logger.error( error as Error, { message: 'handleCodeFlow', } ); - return {type: 'error', existingUser: false}; + return {type: 'error', existingUser: false, error: error instanceof Error ? error.message : 'Unknown error'}; } finally { - // ReduxService.store.dispatch({ - // }); this.localState.codeVerifier = null; this.localState.loginInProgress = false; } diff --git a/package.json b/package.json index 31a5ee607448..079e285ab1c1 100644 --- a/package.json +++ b/package.json @@ -204,7 +204,7 @@ "@metamask/rpc-errors": "^7.0.2", "@metamask/scure-bip39": "^2.1.0", "@metamask/sdk-communication-layer": "0.29.0-wallet", - "@metamask/seedless-onboarding-controller": "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?48cfe50c89299385fbc64cfd0bf548501623267b", + "@metamask/seedless-onboarding-controller": "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?19c8035ca514d04d55e08c0a9e91610ddd9e03a5", "@metamask/selected-network-controller": "^21.0.0", "@metamask/signature-controller": "^23.1.0", "@metamask/slip44": "^4.1.0", @@ -333,7 +333,7 @@ "react-native-fs": "^2.20.0", "react-native-gesture-handler": "^1.10.3", "react-native-get-random-values": "^1.8.0", - "react-native-google-acm": "git+https://github.com/ieow/react-native-google-acm.git#8360769aaaf1ded46c9823c8761df9d3811da63c", + "react-native-google-acm": "git+https://github.com/ieow/react-native-google-acm.git#09de563582649c34069d06d087f22c998b37f436", "react-native-gzip": "^1.1.0", "react-native-i18n": "2.0.15", "react-native-in-app-review": "^4.3.3", diff --git a/yarn.lock b/yarn.lock index 9dd208c4350c..64cca9f4faf9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5357,15 +5357,16 @@ utf-8-validate "^5.0.2" uuid "^8.3.2" -"@metamask/seedless-onboarding-controller@https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?48cfe50c89299385fbc64cfd0bf548501623267b": +"@metamask/seedless-onboarding-controller@https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?19c8035ca514d04d55e08c0a9e91610ddd9e03a5": version "0.0.1" - resolved "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?48cfe50c89299385fbc64cfd0bf548501623267b#847d115723b8c0bed9d3e0e090306e59b489ebd7" + resolved "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?19c8035ca514d04d55e08c0a9e91610ddd9e03a5#6437857369b2901d2b1cb5738a77764feff3d90f" dependencies: "@metamask/base-controller" "^8.0.0" "@metamask/browser-passworder" "^4.3.0" "@metamask/keyring-controller" "^21.0.0" "@metamask/utils" "^11.2.0" "@noble/ciphers" "^0.5.2" + "@noble/curves" "^1.2.0" "@noble/hashes" "^1.4.0" async-mutex "^0.5.0" @@ -24514,9 +24515,9 @@ react-native-get-random-values@^1.8.0: dependencies: fast-base64-decode "^1.0.0" -"react-native-google-acm@git+https://github.com/ieow/react-native-google-acm.git#8360769aaaf1ded46c9823c8761df9d3811da63c": +"react-native-google-acm@git+https://github.com/ieow/react-native-google-acm.git#09de563582649c34069d06d087f22c998b37f436": version "0.1.0" - resolved "git+https://github.com/ieow/react-native-google-acm.git#8360769aaaf1ded46c9823c8761df9d3811da63c" + resolved "git+https://github.com/ieow/react-native-google-acm.git#09de563582649c34069d06d087f22c998b37f436" react-native-gzip@^1.1.0: version "1.1.0" From 0a40bdadece430937a1a60063646526d80991703 Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 9 Apr 2025 12:54:58 +0800 Subject: [PATCH 018/187] fix: onboarding --- app/components/Views/ChoosePassword/index.js | 4 ---- app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/components/Views/ChoosePassword/index.js b/app/components/Views/ChoosePassword/index.js index 0cc283fa6952..cf08829241c7 100644 --- a/app/components/Views/ChoosePassword/index.js +++ b/app/components/Views/ChoosePassword/index.js @@ -446,10 +446,6 @@ class ChoosePassword extends PureComponent { } catch (error) { if (Device.isIos) await this.handleRejectedOsBiometricPrompt(); } - - // get seedphrase from vault - // TODO: seedless onboarding create ( {password, seedPhrase} ); - this.keyringControllerPasswordSet = true; this.props.seedphraseNotBackedUp(); } else { diff --git a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts index ec2809595538..5ea10ffc9091 100644 --- a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts +++ b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts @@ -44,7 +44,7 @@ async function handleOauth2RedirectUrl({ deeplinkManager.navigation.navigate(Routes.ONBOARDING.LOGIN); } else { deeplinkManager.navigation.navigate(Routes.ONBOARDING.CHOOSE_PASSWORD, { - [PREVIOUS_SCREEN]: Routes.ONBOARDING.ONBOARDING, + [PREVIOUS_SCREEN]: 'onboarding', }); } From b3f3f916b58e2bb27027a4702db2b237b852263b Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 9 Apr 2025 12:54:58 +0800 Subject: [PATCH 019/187] fix: onboarding --- app/components/Views/ChoosePassword/index.js | 4 ---- app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/components/Views/ChoosePassword/index.js b/app/components/Views/ChoosePassword/index.js index 5b64d783cac5..c7e01d387a0b 100644 --- a/app/components/Views/ChoosePassword/index.js +++ b/app/components/Views/ChoosePassword/index.js @@ -368,10 +368,6 @@ class ChoosePassword extends PureComponent { } catch (error) { if (Device.isIos) await this.handleRejectedOsBiometricPrompt(); } - - // get seedphrase from vault - // TODO: seedless onboarding create ( {password, seedPhrase} ); - this.keyringControllerPasswordSet = true; this.props.seedphraseNotBackedUp(); } else { diff --git a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts index ec2809595538..5ea10ffc9091 100644 --- a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts +++ b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts @@ -44,7 +44,7 @@ async function handleOauth2RedirectUrl({ deeplinkManager.navigation.navigate(Routes.ONBOARDING.LOGIN); } else { deeplinkManager.navigation.navigate(Routes.ONBOARDING.CHOOSE_PASSWORD, { - [PREVIOUS_SCREEN]: Routes.ONBOARDING.ONBOARDING, + [PREVIOUS_SCREEN]: 'onboarding', }); } From 7e012f223e38f67d48abb3120d009399b24b25fd Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 10 Apr 2025 10:55:29 +0800 Subject: [PATCH 020/187] update: cleanup --- app/core/Oauth2Login/Oauth2loginService.ts | 33 ++++++++++------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/app/core/Oauth2Login/Oauth2loginService.ts b/app/core/Oauth2Login/Oauth2loginService.ts index 447e2e504765..c7149e8c9fc2 100644 --- a/app/core/Oauth2Login/Oauth2loginService.ts +++ b/app/core/Oauth2Login/Oauth2loginService.ts @@ -14,14 +14,12 @@ import { } from 'expo-auth-session'; import {signInWithGoogle} from 'react-native-google-acm'; -import DevLogger from '../SDKConnect/utils/DevLogger'; import { ACTIONS, PREFIXES } from '../../constants/deeplinks'; import { OAuthVerifier } from '@metamask/seedless-onboarding-controller'; import { UserActionType } from '../../actions/user'; - +// to be get from enviroment variable const ByoaServerUrl = 'https://api-develop-torus-byoa.web3auth.io'; -// const ByoaServerUrl = 'https://organic-gannet-privately.ngrok-free.app'; const Web3AuthNetwork = 'sapphire_devnet'; const AppRedirectUri = `${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`; @@ -33,6 +31,7 @@ const AppleServerRedirectUri = `${ByoaServerUrl}/api/v1/oauth/callback`; const AppleWebClientId = 'com.web3auth.appleloginextension'; + export type HandleOauth2LoginResult = ({type: 'pending'} | {type: AuthSessionResult['type'], existingUser: boolean} | {type: 'error', error: string}); export type LoginProvider = 'apple' | 'google'; export type LoginMode = 'onboarding' | 'change-password'; @@ -61,7 +60,6 @@ interface ByoaResponse { export class Oauth2LoginService { public localState: { - loginInProgress: boolean; codeVerifier: string | null; verifier: OAuthVerifier | null; verifierID: string | null; @@ -70,11 +68,13 @@ export class Oauth2LoginService { constructor() { this.localState = { - loginInProgress: false, codeVerifier: null, verifier: null, verifierID: null, }; + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN_RESET, + }); } #iosHandleOauth2Login = async (provider: LoginProvider, mode: LoginMode) : Promise => { @@ -156,26 +156,22 @@ export class Oauth2LoginService { response_mode: 'form_post', } }); - const result = await authRequest.promptAsync({ + + // result return type `dismissed` which is hard to differentiate between dismissed or pending redirect url + const _ = await authRequest.promptAsync({ authorizationEndpoint: 'https://appleid.apple.com/auth/authorize', }); this.localState.codeVerifier = authRequest.codeVerifier ?? null; - Logger.log('handleAppleLogin: result', authRequest.codeVerifier); // Apple login use redirect flow thus no handleCodeFlow here - Logger.log('handleAppleLogin: result', result); - return {type: 'pending'}; } else if (provider === 'google') { - Logger.log('handleGoogleLogin: AndroidGID', AndroidGoogleWebGID); const result = await signInWithGoogle({ serverClientId: AndroidGoogleWebGID, nonce: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15), autoSelectEnabled: true, }); - Logger.log('handleGoogleLogin: result', result); - DevLogger.log('handleGoogleLogin: result', result); if (result.idToken === 'google-signin') { return this.handleCodeFlow({ @@ -184,12 +180,11 @@ export class Oauth2LoginService { clientId: AndroidGoogleWebGID, }); } - throw new Error('Invalid login : ' + provider); + throw new Error('login failed : ' + provider); } throw new Error('Invalid provider : ' + provider); } catch (error) { Logger.log('handleGoogleLogin: error', error); - DevLogger.log('handleGoogleLogin: error', error); return {type: 'error', existingUser: false, error: error instanceof Error ? error.message : 'Unknown error'}; } }; @@ -246,21 +241,23 @@ export class Oauth2LoginService { return {type: 'error', existingUser: false, error: error instanceof Error ? error.message : 'Unknown error'}; } finally { this.localState.codeVerifier = null; - this.localState.loginInProgress = false; } }; handleOauth2Login = async (provider: LoginProvider, mode: LoginMode) : Promise => { - if (this.localState.loginInProgress) { + const state = ReduxService.store.getState(); + if (state.user.oauth2LoginInProgress) { throw new Error('Login already in progress'); } - this.localState.loginInProgress = true; ReduxService.store.dispatch({ type: UserActionType.LOADING_SET, payload: { loadingMsg: 'Logging in...', }, }); + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN, + }); let result; if (Platform.OS === 'ios') { @@ -288,6 +285,7 @@ export class Oauth2LoginService { }, }); } else if (result.type === 'error' && 'error' in result) { + this.clearVerifierDetails(); ReduxService.store.dispatch({ type: UserActionType.OAUTH2_LOGIN_ERROR, payload: { @@ -301,7 +299,6 @@ export class Oauth2LoginService { }); }, 10000); } else { - this.localState.loginInProgress = false; ReduxService.store.dispatch({ type: UserActionType.OAUTH2_LOGIN_RESET, }); From 8eba07c0d9effdfb1a7ef0510db7282c6901ee31 Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 10 Apr 2025 11:33:23 +0800 Subject: [PATCH 021/187] update: cleanup --- app/core/Oauth2Login/Oauth2loginService.ts | 30 +++++++++------------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/app/core/Oauth2Login/Oauth2loginService.ts b/app/core/Oauth2Login/Oauth2loginService.ts index 447e2e504765..14f1283caf36 100644 --- a/app/core/Oauth2Login/Oauth2loginService.ts +++ b/app/core/Oauth2Login/Oauth2loginService.ts @@ -14,14 +14,12 @@ import { } from 'expo-auth-session'; import {signInWithGoogle} from 'react-native-google-acm'; -import DevLogger from '../SDKConnect/utils/DevLogger'; import { ACTIONS, PREFIXES } from '../../constants/deeplinks'; import { OAuthVerifier } from '@metamask/seedless-onboarding-controller'; import { UserActionType } from '../../actions/user'; - +// to be get from enviroment variable const ByoaServerUrl = 'https://api-develop-torus-byoa.web3auth.io'; -// const ByoaServerUrl = 'https://organic-gannet-privately.ngrok-free.app'; const Web3AuthNetwork = 'sapphire_devnet'; const AppRedirectUri = `${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`; @@ -33,6 +31,7 @@ const AppleServerRedirectUri = `${ByoaServerUrl}/api/v1/oauth/callback`; const AppleWebClientId = 'com.web3auth.appleloginextension'; + export type HandleOauth2LoginResult = ({type: 'pending'} | {type: AuthSessionResult['type'], existingUser: boolean} | {type: 'error', error: string}); export type LoginProvider = 'apple' | 'google'; export type LoginMode = 'onboarding' | 'change-password'; @@ -61,7 +60,6 @@ interface ByoaResponse { export class Oauth2LoginService { public localState: { - loginInProgress: boolean; codeVerifier: string | null; verifier: OAuthVerifier | null; verifierID: string | null; @@ -70,7 +68,6 @@ export class Oauth2LoginService { constructor() { this.localState = { - loginInProgress: false, codeVerifier: null, verifier: null, verifierID: null, @@ -156,26 +153,22 @@ export class Oauth2LoginService { response_mode: 'form_post', } }); - const result = await authRequest.promptAsync({ + + // result return type `dismissed` which is hard to differentiate between dismissed or pending redirect url + const _ = await authRequest.promptAsync({ authorizationEndpoint: 'https://appleid.apple.com/auth/authorize', }); this.localState.codeVerifier = authRequest.codeVerifier ?? null; - Logger.log('handleAppleLogin: result', authRequest.codeVerifier); // Apple login use redirect flow thus no handleCodeFlow here - Logger.log('handleAppleLogin: result', result); - return {type: 'pending'}; } else if (provider === 'google') { - Logger.log('handleGoogleLogin: AndroidGID', AndroidGoogleWebGID); const result = await signInWithGoogle({ serverClientId: AndroidGoogleWebGID, nonce: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15), autoSelectEnabled: true, }); - Logger.log('handleGoogleLogin: result', result); - DevLogger.log('handleGoogleLogin: result', result); if (result.idToken === 'google-signin') { return this.handleCodeFlow({ @@ -184,12 +177,11 @@ export class Oauth2LoginService { clientId: AndroidGoogleWebGID, }); } - throw new Error('Invalid login : ' + provider); + throw new Error('login failed : ' + provider); } throw new Error('Invalid provider : ' + provider); } catch (error) { Logger.log('handleGoogleLogin: error', error); - DevLogger.log('handleGoogleLogin: error', error); return {type: 'error', existingUser: false, error: error instanceof Error ? error.message : 'Unknown error'}; } }; @@ -246,21 +238,23 @@ export class Oauth2LoginService { return {type: 'error', existingUser: false, error: error instanceof Error ? error.message : 'Unknown error'}; } finally { this.localState.codeVerifier = null; - this.localState.loginInProgress = false; } }; handleOauth2Login = async (provider: LoginProvider, mode: LoginMode) : Promise => { - if (this.localState.loginInProgress) { + const state = ReduxService.store.getState(); + if (state.user.oauth2LoginInProgress) { throw new Error('Login already in progress'); } - this.localState.loginInProgress = true; ReduxService.store.dispatch({ type: UserActionType.LOADING_SET, payload: { loadingMsg: 'Logging in...', }, }); + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN, + }); let result; if (Platform.OS === 'ios') { @@ -288,6 +282,7 @@ export class Oauth2LoginService { }, }); } else if (result.type === 'error' && 'error' in result) { + this.clearVerifierDetails(); ReduxService.store.dispatch({ type: UserActionType.OAUTH2_LOGIN_ERROR, payload: { @@ -301,7 +296,6 @@ export class Oauth2LoginService { }); }, 10000); } else { - this.localState.loginInProgress = false; ReduxService.store.dispatch({ type: UserActionType.OAUTH2_LOGIN_RESET, }); From b74f86897d1de918cedd3f62f161f9a139b3359f Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 10 Apr 2025 13:01:52 +0800 Subject: [PATCH 022/187] fix: working apple login in android from browser result --- .../ParseManager/handleMetaMaskDeeplink.ts | 5 +-- app/core/Oauth2Login/Oauth2loginService.ts | 36 ++++++++++++++++--- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts b/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts index a291137d9e0d..37bfd5734844 100644 --- a/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts +++ b/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts @@ -152,9 +152,10 @@ export function handleMetaMaskDeeplink({ .replace(`${PREFIXES.METAMASK}${ACTIONS.SELL_CRYPTO}`, '') .replace(`${PREFIXES.METAMASK}${ACTIONS.SELL}`, ''); instance._handleSellCrypto(rampPath); - } else if (url.startsWith(`${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`)) { - instance._handleOauth2RedirectUrl(url); } + // else if (url.startsWith(`${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`)) { + // instance._handleOauth2RedirectUrl(url); + // } } export default handleMetaMaskDeeplink; diff --git a/app/core/Oauth2Login/Oauth2loginService.ts b/app/core/Oauth2Login/Oauth2loginService.ts index 14f1283caf36..8579f15f181d 100644 --- a/app/core/Oauth2Login/Oauth2loginService.ts +++ b/app/core/Oauth2Login/Oauth2loginService.ts @@ -153,15 +153,43 @@ export class Oauth2LoginService { response_mode: 'form_post', } }); + const authUrl = await authRequest.makeAuthUrlAsync({ + authorizationEndpoint: 'https://appleid.apple.com/auth/authorize', + }); + Logger.log('authUrl', authUrl); + + const authRequestProxy = new AuthRequest({ + clientId: AppleWebClientId, + redirectUri: AppRedirectUri, + scopes: ['email', 'name'], + responseType: ResponseType.Code, + codeChallengeMethod: CodeChallengeMethod.S256, + usePKCE: false, + state, + extraParams: { + response_mode: 'form_post', + } + }); // result return type `dismissed` which is hard to differentiate between dismissed or pending redirect url - const _ = await authRequest.promptAsync({ + const result = await authRequestProxy.promptAsync({ authorizationEndpoint: 'https://appleid.apple.com/auth/authorize', + }, { + url: authUrl, }); - this.localState.codeVerifier = authRequest.codeVerifier ?? null; - // Apple login use redirect flow thus no handleCodeFlow here - return {type: 'pending'}; + Logger.log('result ===============', result); + if (result.type === 'success') { + return this.handleCodeFlow({ + provider: 'apple', + code: result.params.code, // result.params.idToken + clientId: AppleWebClientId, + redirectUri: AppleServerRedirectUri, + codeVerifier: authRequest.codeVerifier, + }); + } + this.localState.codeVerifier = authRequest.codeVerifier ?? null; + return {...result, existingUser: false}; } else if (provider === 'google') { const result = await signInWithGoogle({ serverClientId: AndroidGoogleWebGID, From cfa8f77601998cb956f3979517455eaf173cc54f Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 10 Apr 2025 13:35:01 +0800 Subject: [PATCH 023/187] feat: cleanup, remove unused deeplink handler --- app/core/DeeplinkManager/DeeplinkManager.ts | 10 -- .../ParseManager/handleMetaMaskDeeplink.ts | 3 - app/core/Oauth2Login/Oauth2loginService.ts | 121 +++++++----------- 3 files changed, 49 insertions(+), 85 deletions(-) diff --git a/app/core/DeeplinkManager/DeeplinkManager.ts b/app/core/DeeplinkManager/DeeplinkManager.ts index cabca3803363..114d1a60a669 100644 --- a/app/core/DeeplinkManager/DeeplinkManager.ts +++ b/app/core/DeeplinkManager/DeeplinkManager.ts @@ -10,8 +10,6 @@ import switchNetwork from './Handlers/switchNetwork'; import parseDeeplink from './ParseManager/parseDeeplink'; import approveTransaction from './TransactionManager/approveTransaction'; import { RampType } from '../../reducers/fiatOrders/types'; -import handleOauth2RedirectUrl from './Handlers/handleOauth2RedirectUrl'; -import { ACTIONS, PREFIXES } from '../../constants/deeplinks'; class DeeplinkManager { public navigation: NavigationProp; @@ -89,14 +87,6 @@ class DeeplinkManager { rampType: RampType.SELL, }); } - // handle oauth2 redirect url - _handleOauth2RedirectUrl(url: string) { - handleOauth2RedirectUrl({ - deeplinkManager: this, - url, - base: PREFIXES.METAMASK + ACTIONS.OAUTH2_REDIRECT, - }); - } parse( url: string, diff --git a/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts b/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts index 37bfd5734844..544186f29d63 100644 --- a/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts +++ b/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts @@ -153,9 +153,6 @@ export function handleMetaMaskDeeplink({ .replace(`${PREFIXES.METAMASK}${ACTIONS.SELL}`, ''); instance._handleSellCrypto(rampPath); } - // else if (url.startsWith(`${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`)) { - // instance._handleOauth2RedirectUrl(url); - // } } export default handleMetaMaskDeeplink; diff --git a/app/core/Oauth2Login/Oauth2loginService.ts b/app/core/Oauth2Login/Oauth2loginService.ts index 8579f15f181d..e9a538554700 100644 --- a/app/core/Oauth2Login/Oauth2loginService.ts +++ b/app/core/Oauth2Login/Oauth2loginService.ts @@ -60,20 +60,55 @@ interface ByoaResponse { export class Oauth2LoginService { public localState: { - codeVerifier: string | null; verifier: OAuthVerifier | null; verifierID: string | null; }; - constructor() { this.localState = { - codeVerifier: null, verifier: null, verifierID: null, }; } + #dispatchLogin = () =>{ + ReduxService.store.dispatch({ + type: UserActionType.LOADING_SET, + payload: { + loadingMsg: 'Logging in...', + }, + }); + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN, + }); + }; + + #dispatchPostLogin = (result: HandleOauth2LoginResult) => { + if (result.type === 'success') { + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN_SUCCESS, + payload: { + existingUser: result.existingUser, + }, + }); + } else if (result.type === 'error' && 'error' in result) { + this.clearVerifierDetails(); + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN_ERROR, + payload: { + error: result.error, + }, + }); + } else { + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN_RESET, + }); + } + ReduxService.store.dispatch({ + type: UserActionType.LOADING_UNSET, + }); + }; + #iosHandleOauth2Login = async (provider: LoginProvider, mode: LoginMode) : Promise => { try { if (provider === 'apple') { @@ -153,12 +188,13 @@ export class Oauth2LoginService { response_mode: 'form_post', } }); + // generate the auth url const authUrl = await authRequest.makeAuthUrlAsync({ authorizationEndpoint: 'https://appleid.apple.com/auth/authorize', }); - Logger.log('authUrl', authUrl); - - const authRequestProxy = new AuthRequest({ + + // create a dummy auth request so that the auth-session can return result on appRedirectUrl + const authRequestDummy = new AuthRequest({ clientId: AppleWebClientId, redirectUri: AppRedirectUri, scopes: ['email', 'name'], @@ -171,24 +207,22 @@ export class Oauth2LoginService { } }); - // result return type `dismissed` which is hard to differentiate between dismissed or pending redirect url - const result = await authRequestProxy.promptAsync({ + // prompt the auth request using generated auth url instead of the dummy auth request + const result = await authRequestDummy.promptAsync({ authorizationEndpoint: 'https://appleid.apple.com/auth/authorize', }, { url: authUrl, }); - Logger.log('result ===============', result); if (result.type === 'success') { return this.handleCodeFlow({ provider: 'apple', - code: result.params.code, // result.params.idToken + code: result.params.code, clientId: AppleWebClientId, redirectUri: AppleServerRedirectUri, codeVerifier: authRequest.codeVerifier, }); } - this.localState.codeVerifier = authRequest.codeVerifier ?? null; return {...result, existingUser: false}; } else if (provider === 'google') { const result = await signInWithGoogle({ @@ -264,8 +298,6 @@ export class Oauth2LoginService { message: 'handleCodeFlow', } ); return {type: 'error', existingUser: false, error: error instanceof Error ? error.message : 'Unknown error'}; - } finally { - this.localState.codeVerifier = null; } }; @@ -274,73 +306,18 @@ export class Oauth2LoginService { if (state.user.oauth2LoginInProgress) { throw new Error('Login already in progress'); } - ReduxService.store.dispatch({ - type: UserActionType.LOADING_SET, - payload: { - loadingMsg: 'Logging in...', - }, - }); - ReduxService.store.dispatch({ - type: UserActionType.OAUTH2_LOGIN, - }); + this.#dispatchLogin(); let result; if (Platform.OS === 'ios') { result = await this.#iosHandleOauth2Login(provider, mode); } else if (Platform.OS === 'android') { result = await this.#androidHandleOauth2Login(provider, mode); - } - - if (result === undefined) { - this.localState.loginInProgress = false; - ReduxService.store.dispatch({ - type: UserActionType.OAUTH2_LOGIN_ERROR, - payload: { - error: 'Invalid platform', - }, - }); - throw new Error('Invalid platform'); - } - - if (result.type === 'success') { - ReduxService.store.dispatch({ - type: UserActionType.OAUTH2_LOGIN_SUCCESS, - payload: { - existingUser: result.existingUser, - }, - }); - } else if (result.type === 'error' && 'error' in result) { - this.clearVerifierDetails(); - ReduxService.store.dispatch({ - type: UserActionType.OAUTH2_LOGIN_ERROR, - payload: { - error: result.error, - }, - }); - } else if (result.type === 'pending') { - setTimeout(() => { - ReduxService.store.dispatch({ - type: UserActionType.OAUTH2_LOGIN_COMPLETE, - }); - }, 10000); } else { - ReduxService.store.dispatch({ - type: UserActionType.OAUTH2_LOGIN_RESET, - }); - } - - if ( result.type !== 'pending') { - ReduxService.store.dispatch({ - type: UserActionType.LOADING_UNSET, - }); - } else { - setTimeout(() => { - ReduxService.store.dispatch({ - type: UserActionType.LOADING_UNSET, - }); - }, 10000); + this.#dispatchPostLogin({type: 'error', existingUser: false, error: 'Invalid platform'}); + throw new Error('Invalid platform'); } - + this.#dispatchPostLogin(result); return result; }; From 97d11fae8f2385c8dac99e7733e04a304bc417a6 Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 10 Apr 2025 13:01:52 +0800 Subject: [PATCH 024/187] fix: working apple login in android from browser result --- .../ParseManager/handleMetaMaskDeeplink.ts | 5 +-- app/core/Oauth2Login/Oauth2loginService.ts | 36 ++++++++++++++++--- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts b/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts index a291137d9e0d..37bfd5734844 100644 --- a/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts +++ b/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts @@ -152,9 +152,10 @@ export function handleMetaMaskDeeplink({ .replace(`${PREFIXES.METAMASK}${ACTIONS.SELL_CRYPTO}`, '') .replace(`${PREFIXES.METAMASK}${ACTIONS.SELL}`, ''); instance._handleSellCrypto(rampPath); - } else if (url.startsWith(`${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`)) { - instance._handleOauth2RedirectUrl(url); } + // else if (url.startsWith(`${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`)) { + // instance._handleOauth2RedirectUrl(url); + // } } export default handleMetaMaskDeeplink; diff --git a/app/core/Oauth2Login/Oauth2loginService.ts b/app/core/Oauth2Login/Oauth2loginService.ts index c7149e8c9fc2..0183854400e2 100644 --- a/app/core/Oauth2Login/Oauth2loginService.ts +++ b/app/core/Oauth2Login/Oauth2loginService.ts @@ -156,15 +156,43 @@ export class Oauth2LoginService { response_mode: 'form_post', } }); + const authUrl = await authRequest.makeAuthUrlAsync({ + authorizationEndpoint: 'https://appleid.apple.com/auth/authorize', + }); + Logger.log('authUrl', authUrl); + + const authRequestProxy = new AuthRequest({ + clientId: AppleWebClientId, + redirectUri: AppRedirectUri, + scopes: ['email', 'name'], + responseType: ResponseType.Code, + codeChallengeMethod: CodeChallengeMethod.S256, + usePKCE: false, + state, + extraParams: { + response_mode: 'form_post', + } + }); // result return type `dismissed` which is hard to differentiate between dismissed or pending redirect url - const _ = await authRequest.promptAsync({ + const result = await authRequestProxy.promptAsync({ authorizationEndpoint: 'https://appleid.apple.com/auth/authorize', + }, { + url: authUrl, }); - this.localState.codeVerifier = authRequest.codeVerifier ?? null; - // Apple login use redirect flow thus no handleCodeFlow here - return {type: 'pending'}; + Logger.log('result ===============', result); + if (result.type === 'success') { + return this.handleCodeFlow({ + provider: 'apple', + code: result.params.code, // result.params.idToken + clientId: AppleWebClientId, + redirectUri: AppleServerRedirectUri, + codeVerifier: authRequest.codeVerifier, + }); + } + this.localState.codeVerifier = authRequest.codeVerifier ?? null; + return {...result, existingUser: false}; } else if (provider === 'google') { const result = await signInWithGoogle({ serverClientId: AndroidGoogleWebGID, From 93c86e550c3968fcefebfb91b07eed6302cb9be6 Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 10 Apr 2025 13:35:01 +0800 Subject: [PATCH 025/187] feat: cleanup, remove unused deeplink handler --- app/core/DeeplinkManager/DeeplinkManager.ts | 10 -- .../ParseManager/handleMetaMaskDeeplink.ts | 3 - app/core/Oauth2Login/Oauth2loginService.ts | 121 +++++++----------- 3 files changed, 49 insertions(+), 85 deletions(-) diff --git a/app/core/DeeplinkManager/DeeplinkManager.ts b/app/core/DeeplinkManager/DeeplinkManager.ts index cabca3803363..114d1a60a669 100644 --- a/app/core/DeeplinkManager/DeeplinkManager.ts +++ b/app/core/DeeplinkManager/DeeplinkManager.ts @@ -10,8 +10,6 @@ import switchNetwork from './Handlers/switchNetwork'; import parseDeeplink from './ParseManager/parseDeeplink'; import approveTransaction from './TransactionManager/approveTransaction'; import { RampType } from '../../reducers/fiatOrders/types'; -import handleOauth2RedirectUrl from './Handlers/handleOauth2RedirectUrl'; -import { ACTIONS, PREFIXES } from '../../constants/deeplinks'; class DeeplinkManager { public navigation: NavigationProp; @@ -89,14 +87,6 @@ class DeeplinkManager { rampType: RampType.SELL, }); } - // handle oauth2 redirect url - _handleOauth2RedirectUrl(url: string) { - handleOauth2RedirectUrl({ - deeplinkManager: this, - url, - base: PREFIXES.METAMASK + ACTIONS.OAUTH2_REDIRECT, - }); - } parse( url: string, diff --git a/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts b/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts index 37bfd5734844..544186f29d63 100644 --- a/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts +++ b/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts @@ -153,9 +153,6 @@ export function handleMetaMaskDeeplink({ .replace(`${PREFIXES.METAMASK}${ACTIONS.SELL}`, ''); instance._handleSellCrypto(rampPath); } - // else if (url.startsWith(`${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`)) { - // instance._handleOauth2RedirectUrl(url); - // } } export default handleMetaMaskDeeplink; diff --git a/app/core/Oauth2Login/Oauth2loginService.ts b/app/core/Oauth2Login/Oauth2loginService.ts index 0183854400e2..592962ec7ad7 100644 --- a/app/core/Oauth2Login/Oauth2loginService.ts +++ b/app/core/Oauth2Login/Oauth2loginService.ts @@ -60,15 +60,12 @@ interface ByoaResponse { export class Oauth2LoginService { public localState: { - codeVerifier: string | null; verifier: OAuthVerifier | null; verifierID: string | null; }; - constructor() { this.localState = { - codeVerifier: null, verifier: null, verifierID: null, }; @@ -77,6 +74,44 @@ export class Oauth2LoginService { }); } + #dispatchLogin = () =>{ + ReduxService.store.dispatch({ + type: UserActionType.LOADING_SET, + payload: { + loadingMsg: 'Logging in...', + }, + }); + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN, + }); + }; + + #dispatchPostLogin = (result: HandleOauth2LoginResult) => { + if (result.type === 'success') { + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN_SUCCESS, + payload: { + existingUser: result.existingUser, + }, + }); + } else if (result.type === 'error' && 'error' in result) { + this.clearVerifierDetails(); + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN_ERROR, + payload: { + error: result.error, + }, + }); + } else { + ReduxService.store.dispatch({ + type: UserActionType.OAUTH2_LOGIN_RESET, + }); + } + ReduxService.store.dispatch({ + type: UserActionType.LOADING_UNSET, + }); + }; + #iosHandleOauth2Login = async (provider: LoginProvider, mode: LoginMode) : Promise => { try { if (provider === 'apple') { @@ -156,12 +191,13 @@ export class Oauth2LoginService { response_mode: 'form_post', } }); + // generate the auth url const authUrl = await authRequest.makeAuthUrlAsync({ authorizationEndpoint: 'https://appleid.apple.com/auth/authorize', }); - Logger.log('authUrl', authUrl); - - const authRequestProxy = new AuthRequest({ + + // create a dummy auth request so that the auth-session can return result on appRedirectUrl + const authRequestDummy = new AuthRequest({ clientId: AppleWebClientId, redirectUri: AppRedirectUri, scopes: ['email', 'name'], @@ -174,24 +210,22 @@ export class Oauth2LoginService { } }); - // result return type `dismissed` which is hard to differentiate between dismissed or pending redirect url - const result = await authRequestProxy.promptAsync({ + // prompt the auth request using generated auth url instead of the dummy auth request + const result = await authRequestDummy.promptAsync({ authorizationEndpoint: 'https://appleid.apple.com/auth/authorize', }, { url: authUrl, }); - Logger.log('result ===============', result); if (result.type === 'success') { return this.handleCodeFlow({ provider: 'apple', - code: result.params.code, // result.params.idToken + code: result.params.code, clientId: AppleWebClientId, redirectUri: AppleServerRedirectUri, codeVerifier: authRequest.codeVerifier, }); } - this.localState.codeVerifier = authRequest.codeVerifier ?? null; return {...result, existingUser: false}; } else if (provider === 'google') { const result = await signInWithGoogle({ @@ -267,8 +301,6 @@ export class Oauth2LoginService { message: 'handleCodeFlow', } ); return {type: 'error', existingUser: false, error: error instanceof Error ? error.message : 'Unknown error'}; - } finally { - this.localState.codeVerifier = null; } }; @@ -277,73 +309,18 @@ export class Oauth2LoginService { if (state.user.oauth2LoginInProgress) { throw new Error('Login already in progress'); } - ReduxService.store.dispatch({ - type: UserActionType.LOADING_SET, - payload: { - loadingMsg: 'Logging in...', - }, - }); - ReduxService.store.dispatch({ - type: UserActionType.OAUTH2_LOGIN, - }); + this.#dispatchLogin(); let result; if (Platform.OS === 'ios') { result = await this.#iosHandleOauth2Login(provider, mode); } else if (Platform.OS === 'android') { result = await this.#androidHandleOauth2Login(provider, mode); - } - - if (result === undefined) { - this.localState.loginInProgress = false; - ReduxService.store.dispatch({ - type: UserActionType.OAUTH2_LOGIN_ERROR, - payload: { - error: 'Invalid platform', - }, - }); - throw new Error('Invalid platform'); - } - - if (result.type === 'success') { - ReduxService.store.dispatch({ - type: UserActionType.OAUTH2_LOGIN_SUCCESS, - payload: { - existingUser: result.existingUser, - }, - }); - } else if (result.type === 'error' && 'error' in result) { - this.clearVerifierDetails(); - ReduxService.store.dispatch({ - type: UserActionType.OAUTH2_LOGIN_ERROR, - payload: { - error: result.error, - }, - }); - } else if (result.type === 'pending') { - setTimeout(() => { - ReduxService.store.dispatch({ - type: UserActionType.OAUTH2_LOGIN_COMPLETE, - }); - }, 10000); } else { - ReduxService.store.dispatch({ - type: UserActionType.OAUTH2_LOGIN_RESET, - }); - } - - if ( result.type !== 'pending') { - ReduxService.store.dispatch({ - type: UserActionType.LOADING_UNSET, - }); - } else { - setTimeout(() => { - ReduxService.store.dispatch({ - type: UserActionType.LOADING_UNSET, - }); - }, 10000); + this.#dispatchPostLogin({type: 'error', existingUser: false, error: 'Invalid platform'}); + throw new Error('Invalid platform'); } - + this.#dispatchPostLogin(result); return result; }; From 891ef9928bcfba6cc67c97f58f3895f8dba9dc0d Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 10 Apr 2025 14:50:22 +0800 Subject: [PATCH 026/187] update: cleanup --- app/core/Authentication/Authentication.ts | 4 - .../DeeplinkManager/DeeplinkManager.test.ts | 15 ---- .../Handlers/handleOauth2RedirectUrl.ts | 84 ------------------- .../handleMetaMaskDeeplink.test.ts | 22 ----- app/core/EngineService/EngineService.ts | 1 - 5 files changed, 126 deletions(-) delete mode 100644 app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts diff --git a/app/core/Authentication/Authentication.ts b/app/core/Authentication/Authentication.ts index e7ae60490f62..eec213301fe6 100644 --- a/app/core/Authentication/Authentication.ts +++ b/app/core/Authentication/Authentication.ts @@ -185,11 +185,7 @@ class AuthenticationService { // eslint-disable-next-line @typescript-eslint/no-explicit-any const { KeyringController }: any = Engine.context; // Restore vault with empty password - // eslint-disable-next-line no-console - console.log('resetVault: KeyringController', KeyringController); await KeyringController.submitPassword(''); - // eslint-disable-next-line no-console - console.log('resetVault: KeyringController after submitPassword', KeyringController); await this.resetPassword(); }; diff --git a/app/core/DeeplinkManager/DeeplinkManager.test.ts b/app/core/DeeplinkManager/DeeplinkManager.test.ts index 1f9fd52bc6c8..a0865fc09821 100644 --- a/app/core/DeeplinkManager/DeeplinkManager.test.ts +++ b/app/core/DeeplinkManager/DeeplinkManager.test.ts @@ -3,7 +3,6 @@ import DeeplinkManager from './DeeplinkManager'; import handleBrowserUrl from './Handlers/handleBrowserUrl'; import handleEthereumUrl from './Handlers/handleEthereumUrl'; import handleRampUrl from './Handlers/handleRampUrl'; -import handleOauth2RedirectUrl from './Handlers/handleOauth2RedirectUrl'; import switchNetwork from './Handlers/switchNetwork'; import parseDeeplink from './ParseManager/parseDeeplink'; import approveTransaction from './TransactionManager/approveTransaction'; @@ -15,7 +14,6 @@ jest.mock('./Handlers/handleBrowserUrl'); jest.mock('./Handlers/handleRampUrl'); jest.mock('./ParseManager/parseDeeplink'); jest.mock('./Handlers/switchNetwork'); -jest.mock('./Handlers/handleOauth2RedirectUrl'); const mockNavigation = { navigate: jest.fn(), @@ -126,19 +124,6 @@ describe('DeeplinkManager', () => { ); }); - - it('should handle oauth2 redirect url action correctly', () => { - const redirectPath = '/example/path?and=params'; - deeplinkManager._handleOauth2RedirectUrl(redirectPath); - expect(handleOauth2RedirectUrl).toHaveBeenCalledWith( - expect.objectContaining({ - redirectPath, - navigation: mockNavigation, - }), - ); - }); - - it('should parse deeplinks correctly', () => { const url = 'http://example.com'; const browserCallBack = jest.fn(); diff --git a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts b/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts deleted file mode 100644 index 5ea10ffc9091..000000000000 --- a/app/core/DeeplinkManager/Handlers/handleOauth2RedirectUrl.ts +++ /dev/null @@ -1,84 +0,0 @@ -import DeeplinkManager from '../DeeplinkManager'; -import Oauth2LoginService, { LoginMode, LoginProvider } from '../../Oauth2Login/Oauth2loginService'; -import Logger from '../../../util/Logger'; -import { strings } from '../../../../locales/i18n'; -import { showAlert } from '../../../actions/alert'; -import { PREVIOUS_SCREEN } from '../../../constants/navigation'; -import ReduxService from '../../redux/ReduxService'; -import { UserActionType } from '../../../actions/user'; -import Routes from '../../../constants/navigation/Routes'; - -async function handleOauth2RedirectUrl({ - deeplinkManager, - url, - base, -}: { - deeplinkManager: DeeplinkManager; - url: string; - base: string; -}) { - const minusBase = url.replace(base, ''); - const params = new URLSearchParams(minusBase); - - const state = JSON.parse(params.get('state') ?? '{}'); - const code = params.get('code') ?? undefined; - const mode = state.mode as LoginMode; - const provider = state.provider as LoginProvider; - - const clientId = state.clientId as string; - - if (code ) { - Oauth2LoginService.handleCodeFlow({ code, provider , clientId, redirectUri: state.redirectUri, codeVerifier: Oauth2LoginService.localState.codeVerifier ?? undefined }) - .then((result) => { - Logger.log('handleOauth2RedirectUrl: result', result); - - if (result.type === 'success') { - if (mode === 'onboarding') { - ReduxService.store.dispatch({ - type: UserActionType.OAUTH2_LOGIN_SUCCESS, - payload: { - existingUser: result.existingUser, - }, - }); - if (result.existingUser) { - deeplinkManager.navigation.navigate(Routes.ONBOARDING.LOGIN); - } else { - deeplinkManager.navigation.navigate(Routes.ONBOARDING.CHOOSE_PASSWORD, { - [PREVIOUS_SCREEN]: 'onboarding', - }); - } - - ReduxService.store.dispatch({ - type: UserActionType.LOADING_UNSET, - }); - - } else if (mode === 'change-password') { - deeplinkManager.navigation.navigate(Routes.ONBOARDING.CHOOSE_PASSWORD); - } - return code; - } - }).catch((error) => { - Logger.log('handleOauth2RedirectUrl: error', error); - ReduxService.store.dispatch({ - type: UserActionType.OAUTH2_LOGIN_ERROR, - payload: { - error: error.message, - }, - }); - ReduxService.store.dispatch({ - type: UserActionType.LOADING_UNSET, - }); - }); - } else { - deeplinkManager.dispatch( - showAlert({ - isVisible: true, - autodismiss: 5000, - content: 'clipboard-alert', - data: { msg: strings('oauth2-login-failed')}, - }), - ); - } -} - -export default handleOauth2RedirectUrl; diff --git a/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.test.ts b/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.test.ts index 52db6b8e9304..ee6bb369fbe0 100644 --- a/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.test.ts +++ b/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.test.ts @@ -22,7 +22,6 @@ jest.mock('../../../core/NativeModules', () => ({ describe('handleMetaMaskProtocol', () => { const mockParse = jest.fn(); - const mockHandleOauth2RedirectUrl = jest.fn(); const mockHandleBuyCrypto = jest.fn(); const mockHandleSellCrypto = jest.fn(); const mockHandleBrowserUrl = jest.fn(); @@ -43,7 +42,6 @@ describe('handleMetaMaskProtocol', () => { _handleBuyCrypto: mockHandleBuyCrypto, _handleSellCrypto: mockHandleSellCrypto, _handleBrowserUrl: mockHandleBrowserUrl, - _handleOauth2RedirectUrl: mockHandleOauth2RedirectUrl, } as unknown as DeeplinkManager; const handled = jest.fn(); @@ -397,24 +395,4 @@ describe('handleMetaMaskProtocol', () => { expect(mockHandleSellCrypto).toHaveBeenCalled(); }); }); - - - describe('when url start with ${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}', () => { - beforeEach(() => { - url = `${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`; - }); - - it('should call _handleOauth2RedirectUrl', () => { - handleMetaMaskDeeplink({ - instance, - handled, - params, - url, - origin, - wcURL, - }); - - expect(mockHandleOauth2RedirectUrl).toHaveBeenCalled(); - }); - }); }); diff --git a/app/core/EngineService/EngineService.ts b/app/core/EngineService/EngineService.ts index efb395258b92..6f6c6e46a040 100644 --- a/app/core/EngineService/EngineService.ts +++ b/app/core/EngineService/EngineService.ts @@ -47,7 +47,6 @@ export class EngineService { */ start = async () => { const reduxState = ReduxService.store.getState(); - // ReduxService.store.dispatch({ type: UPDATE_BG_STATE_KEY }); trace({ name: TraceName.EngineInitialization, op: TraceOperation.EngineInitialization, From df5b35d6111b7df3d6c5bbec700e20786c0e9153 Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 10 Apr 2025 15:46:06 +0800 Subject: [PATCH 027/187] fix: remove redux.dispatch on oauth2login constructor --- app/core/Oauth2Login/Oauth2loginService.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/core/Oauth2Login/Oauth2loginService.ts b/app/core/Oauth2Login/Oauth2loginService.ts index 592962ec7ad7..e9a538554700 100644 --- a/app/core/Oauth2Login/Oauth2loginService.ts +++ b/app/core/Oauth2Login/Oauth2loginService.ts @@ -69,9 +69,6 @@ export class Oauth2LoginService { verifier: null, verifierID: null, }; - ReduxService.store.dispatch({ - type: UserActionType.OAUTH2_LOGIN_RESET, - }); } #dispatchLogin = () =>{ From 74f4333545837486eaac882a429a2200b54f7197 Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 10 Apr 2025 16:25:35 +0800 Subject: [PATCH 028/187] feat: refactor --- app/core/Oauth2Login/Oauth2loginInterface.ts | 43 ++++ app/core/Oauth2Login/Oauth2loginService.ts | 238 ++++--------------- app/core/Oauth2Login/android/apple.ts | 71 ++++++ app/core/Oauth2Login/android/google.ts | 23 ++ app/core/Oauth2Login/ios/apple.ts | 22 ++ app/core/Oauth2Login/ios/google.ts | 41 ++++ 6 files changed, 245 insertions(+), 193 deletions(-) create mode 100644 app/core/Oauth2Login/Oauth2loginInterface.ts create mode 100644 app/core/Oauth2Login/android/apple.ts create mode 100644 app/core/Oauth2Login/android/google.ts create mode 100644 app/core/Oauth2Login/ios/apple.ts create mode 100644 app/core/Oauth2Login/ios/google.ts diff --git a/app/core/Oauth2Login/Oauth2loginInterface.ts b/app/core/Oauth2Login/Oauth2loginInterface.ts new file mode 100644 index 000000000000..983a41c09a59 --- /dev/null +++ b/app/core/Oauth2Login/Oauth2loginInterface.ts @@ -0,0 +1,43 @@ +import { AuthSessionResult } from 'expo-auth-session'; +import { ACTIONS , PREFIXES } from '../../constants/deeplinks'; + + +// to be get from enviroment variable +export const ByoaServerUrl = 'https://api-develop-torus-byoa.web3auth.io'; +export const Web3AuthNetwork = 'sapphire_devnet'; +export const AppRedirectUri = `${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`; + +export const IosGID = '882363291751-nbbp9n0o307cfil1lup766g1s99k0932.apps.googleusercontent.com'; +export const IosGoogleRedirectUri = 'com.googleusercontent.apps.882363291751-nbbp9n0o307cfil1lup766g1s99k0932:/oauth2redirect/google'; + +export const AndroidGoogleWebGID = '882363291751-2a37cchrq9oc1lfj1p419otvahnbhguv.apps.googleusercontent.com'; +export const AppleServerRedirectUri = `${ByoaServerUrl}/api/v1/oauth/callback`; +export const AppleWebClientId = 'com.web3auth.appleloginextension'; + + + +export type HandleOauth2LoginResult = ({type: 'pending'} | {type: AuthSessionResult['type'], existingUser: boolean} | {type: 'error', error: string}); +export type LoginProvider = 'apple' | 'google'; +export type LoginMode = 'onboarding' | 'change-password'; + + +export interface HandleFlowParams { + provider: LoginProvider; + code?: string; + idToken?: string; + clientId: string; + redirectUri?: string; + codeVerifier?: string; + web3AuthNetwork?: string; +} + +export interface ByoaResponse { + id_token: string; + verifier: string; + verifier_id: string; + indexes: Record; + endpoints: Record; + success: boolean; + message: string; + jwt_tokens: Record; +} diff --git a/app/core/Oauth2Login/Oauth2loginService.ts b/app/core/Oauth2Login/Oauth2loginService.ts index e9a538554700..118f2e9a6297 100644 --- a/app/core/Oauth2Login/Oauth2loginService.ts +++ b/app/core/Oauth2Login/Oauth2loginService.ts @@ -5,58 +5,13 @@ import Engine from '../Engine'; import Logger from '../../util/Logger'; import ReduxService from '../redux'; -import { signInAsync, AppleAuthenticationScope } from 'expo-apple-authentication'; -import { - AuthRequest, - ResponseType, - CodeChallengeMethod, - AuthSessionResult, - } from 'expo-auth-session'; -import {signInWithGoogle} from 'react-native-google-acm'; - -import { ACTIONS, PREFIXES } from '../../constants/deeplinks'; import { OAuthVerifier } from '@metamask/seedless-onboarding-controller'; import { UserActionType } from '../../actions/user'; - -// to be get from enviroment variable -const ByoaServerUrl = 'https://api-develop-torus-byoa.web3auth.io'; -const Web3AuthNetwork = 'sapphire_devnet'; -const AppRedirectUri = `${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`; - -const IosGID = '882363291751-nbbp9n0o307cfil1lup766g1s99k0932.apps.googleusercontent.com'; -const IosGoogleRedirectUri = 'com.googleusercontent.apps.882363291751-nbbp9n0o307cfil1lup766g1s99k0932:/oauth2redirect/google'; - -const AndroidGoogleWebGID = '882363291751-2a37cchrq9oc1lfj1p419otvahnbhguv.apps.googleusercontent.com'; -const AppleServerRedirectUri = `${ByoaServerUrl}/api/v1/oauth/callback`; -const AppleWebClientId = 'com.web3auth.appleloginextension'; - - - -export type HandleOauth2LoginResult = ({type: 'pending'} | {type: AuthSessionResult['type'], existingUser: boolean} | {type: 'error', error: string}); -export type LoginProvider = 'apple' | 'google'; -export type LoginMode = 'onboarding' | 'change-password'; - - -interface HandleFlowParams { - provider: LoginProvider; - code?: string; - idToken?: string; - clientId: string; - redirectUri?: string; - codeVerifier?: string; - web3AuthNetwork?: string; -} - -interface ByoaResponse { - id_token: string; - verifier: string; - verifier_id: string; - indexes: Record; - endpoints: Record; - success: boolean; - message: string; - jwt_tokens: Record; -} +import { handleAndroidAppleLogin } from './android/apple'; +import { handleAndroidGoogleLogin } from './android/google'; +import { ByoaResponse, HandleFlowParams, ByoaServerUrl, HandleOauth2LoginResult, LoginMode, LoginProvider, Web3AuthNetwork } from './Oauth2loginInterface'; +import { handleIosGoogleLogin } from './ios/google'; +import { handleIosAppleLogin } from './ios/apple'; export class Oauth2LoginService { public localState: { @@ -109,143 +64,27 @@ export class Oauth2LoginService { }); }; - #iosHandleOauth2Login = async (provider: LoginProvider, mode: LoginMode) : Promise => { - try { - if (provider === 'apple') { - const credential = await signInAsync({ - requestedScopes: [ - AppleAuthenticationScope.FULL_NAME, - AppleAuthenticationScope.EMAIL, - ], - }); - if (credential.identityToken) { - return await this.handleCodeFlow({ - provider: 'apple', - idToken: credential.identityToken, - clientId: IosGID, - } ); - } - return {type: 'dismiss', existingUser: false}; - } else if (provider === 'google') { - const state = JSON.stringify({ - mode, - random: Math.random().toString(36).substring(2, 15), - }); - const authRequest = new AuthRequest({ - clientId: IosGID, - redirectUri: IosGoogleRedirectUri, - scopes: ['email', 'profile'], - responseType: ResponseType.Code, - codeChallengeMethod: CodeChallengeMethod.S256, - usePKCE: true, - state, - }); - const result = await authRequest.promptAsync({ - authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth', - }); - - if (result.type === 'success') { - return this.handleCodeFlow({ - provider: 'google', - code: result.params.code, // result.params.idToken - clientId: IosGID, - redirectUri: IosGoogleRedirectUri, - codeVerifier: authRequest.codeVerifier, - }); - } - return {...result, existingUser: false}; - } - throw new Error('Invalid provider : ' + provider); - } catch (error) { - Logger.error( error as Error, { - message: 'iosHandleOauth2Login', - provider, - } ); - return {type: 'error', existingUser: false}; + #iosHandleOauth2Login = async (provider: LoginProvider, mode: LoginMode) : Promise => { + if (provider === 'apple') { + const result = await handleIosAppleLogin(); + return result; + } else if (provider === 'google') { + const result = await handleIosGoogleLogin(mode); + return result; } + throw new Error('Invalid provider : ' + provider); }; - #androidHandleOauth2Login = async (provider: LoginProvider, mode: LoginMode) : Promise => { - try { - if (provider === 'apple') { - const state = JSON.stringify({ - provider: 'apple', - client_redirect_back_uri: AppRedirectUri, - redirectUri: AppleServerRedirectUri, - clientId: AppleWebClientId, - random: Math.random().toString(36).substring(2, 15), - mode, - }); - const authRequest = new AuthRequest({ - clientId: AppleWebClientId, - redirectUri: AppleServerRedirectUri, - scopes: ['email', 'name'], - responseType: ResponseType.Code, - codeChallengeMethod: CodeChallengeMethod.S256, - usePKCE: false, - state, - extraParams: { - response_mode: 'form_post', - } - }); - // generate the auth url - const authUrl = await authRequest.makeAuthUrlAsync({ - authorizationEndpoint: 'https://appleid.apple.com/auth/authorize', - }); - - // create a dummy auth request so that the auth-session can return result on appRedirectUrl - const authRequestDummy = new AuthRequest({ - clientId: AppleWebClientId, - redirectUri: AppRedirectUri, - scopes: ['email', 'name'], - responseType: ResponseType.Code, - codeChallengeMethod: CodeChallengeMethod.S256, - usePKCE: false, - state, - extraParams: { - response_mode: 'form_post', - } - }); - - // prompt the auth request using generated auth url instead of the dummy auth request - const result = await authRequestDummy.promptAsync({ - authorizationEndpoint: 'https://appleid.apple.com/auth/authorize', - }, { - url: authUrl, - }); - - if (result.type === 'success') { - return this.handleCodeFlow({ - provider: 'apple', - code: result.params.code, - clientId: AppleWebClientId, - redirectUri: AppleServerRedirectUri, - codeVerifier: authRequest.codeVerifier, - }); - } - return {...result, existingUser: false}; - } else if (provider === 'google') { - const result = await signInWithGoogle({ - serverClientId: AndroidGoogleWebGID, - nonce: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15), - autoSelectEnabled: true, - }); - Logger.log('handleGoogleLogin: result', result); - - if (result.idToken === 'google-signin') { - return this.handleCodeFlow({ - provider: 'google', - idToken: result.idToken, - clientId: AndroidGoogleWebGID, - }); - } - throw new Error('login failed : ' + provider); - } - throw new Error('Invalid provider : ' + provider); - } catch (error) { - Logger.log('handleGoogleLogin: error', error); - return {type: 'error', existingUser: false, error: error instanceof Error ? error.message : 'Unknown error'}; + #androidHandleOauth2Login = async (provider: LoginProvider, mode: LoginMode) : Promise => { + if (provider === 'apple') { + const result = await handleAndroidAppleLogin(mode); + return result; + } else if (provider === 'google') { + const result = await handleAndroidGoogleLogin(mode); + return result; } + throw new Error('Invalid provider : ' + provider); + }; handleCodeFlow = async (params : HandleFlowParams) : Promise<{type: 'success' | 'error', error?: string, existingUser : boolean}> => { @@ -308,17 +147,30 @@ export class Oauth2LoginService { } this.#dispatchLogin(); - let result; - if (Platform.OS === 'ios') { - result = await this.#iosHandleOauth2Login(provider, mode); - } else if (Platform.OS === 'android') { - result = await this.#androidHandleOauth2Login(provider, mode); - } else { - this.#dispatchPostLogin({type: 'error', existingUser: false, error: 'Invalid platform'}); - throw new Error('Invalid platform'); + try { + let result; + if (Platform.OS === 'ios') { + result = await this.#iosHandleOauth2Login(provider, mode); + } else if (Platform.OS === 'android') { + result = await this.#androidHandleOauth2Login(provider, mode); + } else { + throw new Error('Invalid platform'); + } + + if (result) { + const handleCodeFlowResult = await this.handleCodeFlow(result); + this.#dispatchPostLogin(handleCodeFlowResult); + return handleCodeFlowResult; + } + this.#dispatchPostLogin({type: 'dismiss', existingUser: false}); + return {type: 'dismiss', existingUser: false}; + } catch (error) { + Logger.error( error as Error, { + message: 'handleOauth2Login', + } ); + this.#dispatchPostLogin({type: 'error', existingUser: false, error: error instanceof Error ? error.message : 'Unknown error'}); + return {type: 'error', existingUser: false, error: error instanceof Error ? error.message : 'Unknown error'}; } - this.#dispatchPostLogin(result); - return result; }; getVerifierDetails = () => ({ diff --git a/app/core/Oauth2Login/android/apple.ts b/app/core/Oauth2Login/android/apple.ts new file mode 100644 index 000000000000..c0be148b0704 --- /dev/null +++ b/app/core/Oauth2Login/android/apple.ts @@ -0,0 +1,71 @@ +import { CodeChallengeMethod, ResponseType } from "expo-auth-session"; +import { AuthRequest } from "expo-auth-session"; +import { AppRedirectUri, AppleServerRedirectUri, AppleWebClientId, HandleFlowParams, LoginMode } from "../Oauth2loginInterface"; + + + +const AppleAuthorizeEndpoint = 'https://appleid.apple.com/auth/authorize'; + +export const handleAndroidAppleLogin = async (mode: LoginMode): Promise => { + const state = JSON.stringify({ + provider: 'apple', + client_redirect_back_uri: AppRedirectUri, + redirectUri: AppleServerRedirectUri, + clientId: AppleWebClientId, + random: Math.random().toString(36).substring(2, 15), + mode, + }); + const authRequest = new AuthRequest({ + clientId: AppleWebClientId, + redirectUri: AppleServerRedirectUri, + scopes: ['email', 'name'], + responseType: ResponseType.Code, + codeChallengeMethod: CodeChallengeMethod.S256, + usePKCE: false, + state, + extraParams: { + response_mode: 'form_post', + } + }); + // generate the auth url + const authUrl = await authRequest.makeAuthUrlAsync({ + authorizationEndpoint: AppleAuthorizeEndpoint, + }); + + // create a dummy auth request so that the auth-session can return result on appRedirectUrl + const authRequestDummy = new AuthRequest({ + clientId: AppleWebClientId, + redirectUri: AppRedirectUri, + scopes: ['email', 'name'], + responseType: ResponseType.Code, + codeChallengeMethod: CodeChallengeMethod.S256, + usePKCE: false, + state, + extraParams: { + response_mode: 'form_post', + } + }); + + // prompt the auth request using generated auth url instead of the dummy auth request + const result = await authRequestDummy.promptAsync({ + authorizationEndpoint: AppleAuthorizeEndpoint, + }, { + url: authUrl, + }); + if (result.type === 'success') { + return { + provider: 'apple', + code: result.params.code, + clientId: AppleWebClientId, + redirectUri: AppleServerRedirectUri, + codeVerifier: authRequest.codeVerifier, + }; + } + if (result.type === 'error') { + if (result.error) { + throw result.error; + } + throw new Error('handleAndroidAppleLogin: Unknown error'); + } + return undefined; +}; diff --git a/app/core/Oauth2Login/android/google.ts b/app/core/Oauth2Login/android/google.ts new file mode 100644 index 000000000000..fa22d8e56bac --- /dev/null +++ b/app/core/Oauth2Login/android/google.ts @@ -0,0 +1,23 @@ +import Logger from "../../../util/Logger"; +import { AndroidGoogleWebGID } from "../Oauth2loginInterface"; +import { HandleFlowParams } from "../Oauth2loginInterface"; +import { LoginMode } from "../Oauth2loginInterface"; +import { signInWithGoogle } from "react-native-google-acm"; + +export const handleAndroidGoogleLogin = async (mode: LoginMode): Promise => { + const result = await signInWithGoogle({ + serverClientId: AndroidGoogleWebGID, + nonce: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15), + autoSelectEnabled: true, + }); + Logger.log('handleGoogleLogin: result', result); + + if (result.idToken === 'google-signin') { + return { + provider: 'google', + idToken: result.idToken, + clientId: AndroidGoogleWebGID, + }; + } + return undefined; +}; \ No newline at end of file diff --git a/app/core/Oauth2Login/ios/apple.ts b/app/core/Oauth2Login/ios/apple.ts new file mode 100644 index 000000000000..d32d818e4469 --- /dev/null +++ b/app/core/Oauth2Login/ios/apple.ts @@ -0,0 +1,22 @@ +import { HandleFlowParams } from "../Oauth2loginInterface"; +import { signInAsync } from "expo-apple-authentication"; +import { AppleAuthenticationScope } from "expo-apple-authentication"; +import { IosGID } from "../Oauth2loginInterface"; + + +export const handleIosAppleLogin = async (): Promise => { + const credential = await signInAsync({ + requestedScopes: [ + AppleAuthenticationScope.FULL_NAME, + AppleAuthenticationScope.EMAIL, + ], + }); + if (credential.identityToken) { + return { + provider: 'apple', + idToken: credential.identityToken, + clientId: IosGID, + }; + } + return undefined; +}; diff --git a/app/core/Oauth2Login/ios/google.ts b/app/core/Oauth2Login/ios/google.ts new file mode 100644 index 000000000000..08644a28ddd4 --- /dev/null +++ b/app/core/Oauth2Login/ios/google.ts @@ -0,0 +1,41 @@ +import { HandleFlowParams } from "../Oauth2loginInterface"; +import { LoginMode } from "../Oauth2loginInterface"; +import { AuthRequest, CodeChallengeMethod, ResponseType } from "expo-auth-session"; +import { IosGoogleRedirectUri } from "../Oauth2loginInterface"; +import { IosGID } from "../Oauth2loginInterface"; + +export const handleIosGoogleLogin = async (mode: LoginMode): Promise => { + const state = JSON.stringify({ + mode, + random: Math.random().toString(36).substring(2, 15), + }); + const authRequest = new AuthRequest({ + clientId: IosGID, + redirectUri: IosGoogleRedirectUri, + scopes: ['email', 'profile'], + responseType: ResponseType.Code, + codeChallengeMethod: CodeChallengeMethod.S256, + usePKCE: true, + state, + }); + const result = await authRequest.promptAsync({ + authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth', + }); + + if (result.type === 'success') { + return { + provider: 'google', + code: result.params.code, // result.params.idToken + clientId: IosGID, + redirectUri: IosGoogleRedirectUri, + codeVerifier: authRequest.codeVerifier, + }; + } + if (result.type === 'error') { + if (result.error) { + throw result.error; + } + throw new Error('handleIosGoogleLogin: Unknown error'); + } + return undefined; +}; From 12d19fd3841eee1ea55f6f18a55dd66289767334 Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 11 Apr 2025 12:17:45 +0800 Subject: [PATCH 029/187] fix: move loginInProgress from redux state to local state --- app/actions/user/types.ts | 8 ---- app/core/Oauth2Login/Oauth2loginInterface.ts | 5 +-- app/core/Oauth2Login/Oauth2loginService.ts | 46 ++++++++++++++------ app/reducers/user/index.ts | 16 ------- app/reducers/user/types.ts | 1 - 5 files changed, 34 insertions(+), 42 deletions(-) diff --git a/app/actions/user/types.ts b/app/actions/user/types.ts index 37009d4e6373..daca64881c26 100644 --- a/app/actions/user/types.ts +++ b/app/actions/user/types.ts @@ -25,9 +25,7 @@ export enum UserActionType { CHECKED_AUTH = 'CHECKED_AUTH', SET_APP_SERVICES_READY = 'SET_APP_SERVICES_READY', - OAUTH2_LOGIN = 'OAUTH2_LOGIN', OAUTH2_LOGIN_RESET = 'OAUTH2_LOGIN_RESET', - OAUTH2_LOGIN_COMPLETE = 'OAUTH2_LOGIN_COMPLETE', OAUTH2_LOGIN_SUCCESS = 'OAUTH2_LOGIN_SUCCESS', OAUTH2_LOGIN_ERROR = 'OAUTH2_LOGIN_ERROR', } @@ -95,14 +93,10 @@ export type CheckedAuthAction = Action & { export type SetAppServicesReadyAction = Action; -export type OAuth2LoginAction = Action; - export type OAuth2LoginSuccessAction = Action & { payload: { existingUser: boolean } }; export type OAuth2LoginErrorAction = Action & { payload: { error: string } }; -export type OAuth2LoginCompleteAction = Action; - export type OAuth2LoginResetAction = Action; /** @@ -130,8 +124,6 @@ export type UserAction = | SetAppThemeAction | CheckedAuthAction | SetAppServicesReadyAction - | OAuth2LoginAction | OAuth2LoginSuccessAction | OAuth2LoginErrorAction - | OAuth2LoginCompleteAction | OAuth2LoginResetAction; diff --git a/app/core/Oauth2Login/Oauth2loginInterface.ts b/app/core/Oauth2Login/Oauth2loginInterface.ts index 983a41c09a59..1b785aa94996 100644 --- a/app/core/Oauth2Login/Oauth2loginInterface.ts +++ b/app/core/Oauth2Login/Oauth2loginInterface.ts @@ -4,7 +4,7 @@ import { ACTIONS , PREFIXES } from '../../constants/deeplinks'; // to be get from enviroment variable export const ByoaServerUrl = 'https://api-develop-torus-byoa.web3auth.io'; -export const Web3AuthNetwork = 'sapphire_devnet'; +export const DefaultWeb3AuthNetwork = 'sapphire_devnet'; export const AppRedirectUri = `${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`; export const IosGID = '882363291751-nbbp9n0o307cfil1lup766g1s99k0932.apps.googleusercontent.com'; @@ -19,7 +19,7 @@ export const AppleWebClientId = 'com.web3auth.appleloginextension'; export type HandleOauth2LoginResult = ({type: 'pending'} | {type: AuthSessionResult['type'], existingUser: boolean} | {type: 'error', error: string}); export type LoginProvider = 'apple' | 'google'; export type LoginMode = 'onboarding' | 'change-password'; - +export type Web3AuthNetwork = 'sapphire_devnet' | 'sapphire_mainnet'; export interface HandleFlowParams { provider: LoginProvider; @@ -28,7 +28,6 @@ export interface HandleFlowParams { clientId: string; redirectUri?: string; codeVerifier?: string; - web3AuthNetwork?: string; } export interface ByoaResponse { diff --git a/app/core/Oauth2Login/Oauth2loginService.ts b/app/core/Oauth2Login/Oauth2loginService.ts index 118f2e9a6297..5eb38d48ba32 100644 --- a/app/core/Oauth2Login/Oauth2loginService.ts +++ b/app/core/Oauth2Login/Oauth2loginService.ts @@ -9,36 +9,44 @@ import { OAuthVerifier } from '@metamask/seedless-onboarding-controller'; import { UserActionType } from '../../actions/user'; import { handleAndroidAppleLogin } from './android/apple'; import { handleAndroidGoogleLogin } from './android/google'; -import { ByoaResponse, HandleFlowParams, ByoaServerUrl, HandleOauth2LoginResult, LoginMode, LoginProvider, Web3AuthNetwork } from './Oauth2loginInterface'; +import { ByoaResponse, HandleFlowParams, ByoaServerUrl, HandleOauth2LoginResult, LoginMode, LoginProvider, Web3AuthNetwork, DefaultWeb3AuthNetwork } from './Oauth2loginInterface'; import { handleIosGoogleLogin } from './ios/google'; import { handleIosAppleLogin } from './ios/apple'; export class Oauth2LoginService { public localState: { + loginInProgress: boolean; verifier: OAuthVerifier | null; verifierID: string | null; }; - constructor() { + public config : { + web3AuthNetwork: Web3AuthNetwork; + }; + + constructor(config: {web3AuthNetwork: Web3AuthNetwork}) { this.localState = { + loginInProgress: false, verifier: null, verifierID: null, }; + this.config = { + web3AuthNetwork: config.web3AuthNetwork, + }; } #dispatchLogin = () =>{ + this.updateLocalState({loginInProgress: true}); ReduxService.store.dispatch({ type: UserActionType.LOADING_SET, payload: { loadingMsg: 'Logging in...', }, }); - ReduxService.store.dispatch({ - type: UserActionType.OAUTH2_LOGIN, - }); }; #dispatchPostLogin = (result: HandleOauth2LoginResult) => { + this.updateLocalState({loginInProgress: false}); if (result.type === 'success') { ReduxService.store.dispatch({ type: UserActionType.OAUTH2_LOGIN_SUCCESS, @@ -87,7 +95,7 @@ export class Oauth2LoginService { }; - handleCodeFlow = async (params : HandleFlowParams) : Promise<{type: 'success' | 'error', error?: string, existingUser : boolean}> => { + handleCodeFlow = async (params : HandleFlowParams & {web3AuthNetwork: Web3AuthNetwork}) : Promise<{type: 'success' | 'error', error?: string, existingUser : boolean}> => { const {code, idToken, provider, clientId, redirectUri, codeVerifier, web3AuthNetwork} = params; const pathname = code ? 'api/v1/oauth/token' : 'api/v1/oauth/id_token'; @@ -95,14 +103,14 @@ export class Oauth2LoginService { code, client_id: clientId, login_provider: provider, - network: web3AuthNetwork ?? Web3AuthNetwork, + network: web3AuthNetwork, redirect_uri: redirectUri, code_verifier: codeVerifier, } : { id_token: idToken, client_id: clientId, login_provider: provider, - network: web3AuthNetwork ?? Web3AuthNetwork, + network: web3AuthNetwork, }; Logger.log('handleCodeFlow: body', body); @@ -118,8 +126,10 @@ export class Oauth2LoginService { const data = await res.json() as ByoaResponse; Logger.log('handleCodeFlow: data', data); if (data.success) { - this.localState.verifier = data.verifier as OAuthVerifier; - this.localState.verifierID = data.verifier_id; + this.updateLocalState({ + verifier: data.verifier as OAuthVerifier, + verifierID: data.verifier_id, + }); const result = await Engine.context.SeedlessOnboardingController.authenticateOAuthUser({ idTokens: Object.values(data.jwt_tokens), @@ -141,8 +151,9 @@ export class Oauth2LoginService { }; handleOauth2Login = async (provider: LoginProvider, mode: LoginMode) : Promise => { - const state = ReduxService.store.getState(); - if (state.user.oauth2LoginInProgress) { + const web3AuthNetwork = this.config.web3AuthNetwork; + + if (this.localState.loginInProgress) { throw new Error('Login already in progress'); } this.#dispatchLogin(); @@ -158,7 +169,7 @@ export class Oauth2LoginService { } if (result) { - const handleCodeFlowResult = await this.handleCodeFlow(result); + const handleCodeFlowResult = await this.handleCodeFlow({...result , web3AuthNetwork}); this.#dispatchPostLogin(handleCodeFlowResult); return handleCodeFlowResult; } @@ -173,6 +184,13 @@ export class Oauth2LoginService { } }; + updateLocalState = (newState: Partial) => { + this.localState = { + ...this.localState, + ...newState, + }; + }; + getVerifierDetails = () => ({ verifier: this.localState.verifier, verifierID: this.localState.verifierID, @@ -184,4 +202,4 @@ export class Oauth2LoginService { }; } -export default new Oauth2LoginService(); +export default new Oauth2LoginService({web3AuthNetwork: DefaultWeb3AuthNetwork}); diff --git a/app/reducers/user/index.ts b/app/reducers/user/index.ts index 1b35847aa2e3..2ab45b9c812a 100644 --- a/app/reducers/user/index.ts +++ b/app/reducers/user/index.ts @@ -23,7 +23,6 @@ export const userInitialState: UserState = { appTheme: AppThemeKey.os, ambiguousAddressEntries: {}, appServicesReady: false, - oauth2LoginInProgress: false, oauth2LoginSuccess: false, oauth2LoginError: null, oauth2LoginExistingUser: false, @@ -120,36 +119,21 @@ const userReducer = ( appServicesReady: true, }; - case UserActionType.OAUTH2_LOGIN: - return { - ...state, - oauth2LoginInProgress: true, - oauth2LoginSuccess: false, - oauth2LoginError: null, - }; - case UserActionType.OAUTH2_LOGIN_COMPLETE: - return { - ...state, - oauth2LoginInProgress: false, - }; case UserActionType.OAUTH2_LOGIN_SUCCESS: return { ...state, - oauth2LoginInProgress: false, oauth2LoginSuccess: true, oauth2LoginExistingUser: action.payload.existingUser, }; case UserActionType.OAUTH2_LOGIN_ERROR: return { ...state, - oauth2LoginInProgress: false, oauth2LoginSuccess: false, oauth2LoginError: action.payload.error, }; case UserActionType.OAUTH2_LOGIN_RESET: return { ...state, - oauth2LoginInProgress: false, oauth2LoginSuccess: false, oauth2LoginError: null, }; diff --git a/app/reducers/user/types.ts b/app/reducers/user/types.ts index 2ec740a55dc3..74f5c47d22be 100644 --- a/app/reducers/user/types.ts +++ b/app/reducers/user/types.ts @@ -17,7 +17,6 @@ export interface UserState { appTheme: AppThemeKey; ambiguousAddressEntries: Record; appServicesReady: boolean; - oauth2LoginInProgress: boolean; oauth2LoginError: string | null; oauth2LoginSuccess: boolean; oauth2LoginExistingUser: boolean; From 3da10b5ceedbea79533ca650a7f89dd4979cb509 Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 11 Apr 2025 12:17:45 +0800 Subject: [PATCH 030/187] fix: move loginInProgress from redux state to local state --- app/actions/user/types.ts | 8 ---- app/core/Oauth2Login/Oauth2loginInterface.ts | 5 +-- app/core/Oauth2Login/Oauth2loginService.ts | 46 ++++++++++++++------ app/reducers/user/index.ts | 16 ------- app/reducers/user/types.ts | 1 - 5 files changed, 34 insertions(+), 42 deletions(-) diff --git a/app/actions/user/types.ts b/app/actions/user/types.ts index 37009d4e6373..daca64881c26 100644 --- a/app/actions/user/types.ts +++ b/app/actions/user/types.ts @@ -25,9 +25,7 @@ export enum UserActionType { CHECKED_AUTH = 'CHECKED_AUTH', SET_APP_SERVICES_READY = 'SET_APP_SERVICES_READY', - OAUTH2_LOGIN = 'OAUTH2_LOGIN', OAUTH2_LOGIN_RESET = 'OAUTH2_LOGIN_RESET', - OAUTH2_LOGIN_COMPLETE = 'OAUTH2_LOGIN_COMPLETE', OAUTH2_LOGIN_SUCCESS = 'OAUTH2_LOGIN_SUCCESS', OAUTH2_LOGIN_ERROR = 'OAUTH2_LOGIN_ERROR', } @@ -95,14 +93,10 @@ export type CheckedAuthAction = Action & { export type SetAppServicesReadyAction = Action; -export type OAuth2LoginAction = Action; - export type OAuth2LoginSuccessAction = Action & { payload: { existingUser: boolean } }; export type OAuth2LoginErrorAction = Action & { payload: { error: string } }; -export type OAuth2LoginCompleteAction = Action; - export type OAuth2LoginResetAction = Action; /** @@ -130,8 +124,6 @@ export type UserAction = | SetAppThemeAction | CheckedAuthAction | SetAppServicesReadyAction - | OAuth2LoginAction | OAuth2LoginSuccessAction | OAuth2LoginErrorAction - | OAuth2LoginCompleteAction | OAuth2LoginResetAction; diff --git a/app/core/Oauth2Login/Oauth2loginInterface.ts b/app/core/Oauth2Login/Oauth2loginInterface.ts index 983a41c09a59..1b785aa94996 100644 --- a/app/core/Oauth2Login/Oauth2loginInterface.ts +++ b/app/core/Oauth2Login/Oauth2loginInterface.ts @@ -4,7 +4,7 @@ import { ACTIONS , PREFIXES } from '../../constants/deeplinks'; // to be get from enviroment variable export const ByoaServerUrl = 'https://api-develop-torus-byoa.web3auth.io'; -export const Web3AuthNetwork = 'sapphire_devnet'; +export const DefaultWeb3AuthNetwork = 'sapphire_devnet'; export const AppRedirectUri = `${PREFIXES.METAMASK}${ACTIONS.OAUTH2_REDIRECT}`; export const IosGID = '882363291751-nbbp9n0o307cfil1lup766g1s99k0932.apps.googleusercontent.com'; @@ -19,7 +19,7 @@ export const AppleWebClientId = 'com.web3auth.appleloginextension'; export type HandleOauth2LoginResult = ({type: 'pending'} | {type: AuthSessionResult['type'], existingUser: boolean} | {type: 'error', error: string}); export type LoginProvider = 'apple' | 'google'; export type LoginMode = 'onboarding' | 'change-password'; - +export type Web3AuthNetwork = 'sapphire_devnet' | 'sapphire_mainnet'; export interface HandleFlowParams { provider: LoginProvider; @@ -28,7 +28,6 @@ export interface HandleFlowParams { clientId: string; redirectUri?: string; codeVerifier?: string; - web3AuthNetwork?: string; } export interface ByoaResponse { diff --git a/app/core/Oauth2Login/Oauth2loginService.ts b/app/core/Oauth2Login/Oauth2loginService.ts index 118f2e9a6297..5eb38d48ba32 100644 --- a/app/core/Oauth2Login/Oauth2loginService.ts +++ b/app/core/Oauth2Login/Oauth2loginService.ts @@ -9,36 +9,44 @@ import { OAuthVerifier } from '@metamask/seedless-onboarding-controller'; import { UserActionType } from '../../actions/user'; import { handleAndroidAppleLogin } from './android/apple'; import { handleAndroidGoogleLogin } from './android/google'; -import { ByoaResponse, HandleFlowParams, ByoaServerUrl, HandleOauth2LoginResult, LoginMode, LoginProvider, Web3AuthNetwork } from './Oauth2loginInterface'; +import { ByoaResponse, HandleFlowParams, ByoaServerUrl, HandleOauth2LoginResult, LoginMode, LoginProvider, Web3AuthNetwork, DefaultWeb3AuthNetwork } from './Oauth2loginInterface'; import { handleIosGoogleLogin } from './ios/google'; import { handleIosAppleLogin } from './ios/apple'; export class Oauth2LoginService { public localState: { + loginInProgress: boolean; verifier: OAuthVerifier | null; verifierID: string | null; }; - constructor() { + public config : { + web3AuthNetwork: Web3AuthNetwork; + }; + + constructor(config: {web3AuthNetwork: Web3AuthNetwork}) { this.localState = { + loginInProgress: false, verifier: null, verifierID: null, }; + this.config = { + web3AuthNetwork: config.web3AuthNetwork, + }; } #dispatchLogin = () =>{ + this.updateLocalState({loginInProgress: true}); ReduxService.store.dispatch({ type: UserActionType.LOADING_SET, payload: { loadingMsg: 'Logging in...', }, }); - ReduxService.store.dispatch({ - type: UserActionType.OAUTH2_LOGIN, - }); }; #dispatchPostLogin = (result: HandleOauth2LoginResult) => { + this.updateLocalState({loginInProgress: false}); if (result.type === 'success') { ReduxService.store.dispatch({ type: UserActionType.OAUTH2_LOGIN_SUCCESS, @@ -87,7 +95,7 @@ export class Oauth2LoginService { }; - handleCodeFlow = async (params : HandleFlowParams) : Promise<{type: 'success' | 'error', error?: string, existingUser : boolean}> => { + handleCodeFlow = async (params : HandleFlowParams & {web3AuthNetwork: Web3AuthNetwork}) : Promise<{type: 'success' | 'error', error?: string, existingUser : boolean}> => { const {code, idToken, provider, clientId, redirectUri, codeVerifier, web3AuthNetwork} = params; const pathname = code ? 'api/v1/oauth/token' : 'api/v1/oauth/id_token'; @@ -95,14 +103,14 @@ export class Oauth2LoginService { code, client_id: clientId, login_provider: provider, - network: web3AuthNetwork ?? Web3AuthNetwork, + network: web3AuthNetwork, redirect_uri: redirectUri, code_verifier: codeVerifier, } : { id_token: idToken, client_id: clientId, login_provider: provider, - network: web3AuthNetwork ?? Web3AuthNetwork, + network: web3AuthNetwork, }; Logger.log('handleCodeFlow: body', body); @@ -118,8 +126,10 @@ export class Oauth2LoginService { const data = await res.json() as ByoaResponse; Logger.log('handleCodeFlow: data', data); if (data.success) { - this.localState.verifier = data.verifier as OAuthVerifier; - this.localState.verifierID = data.verifier_id; + this.updateLocalState({ + verifier: data.verifier as OAuthVerifier, + verifierID: data.verifier_id, + }); const result = await Engine.context.SeedlessOnboardingController.authenticateOAuthUser({ idTokens: Object.values(data.jwt_tokens), @@ -141,8 +151,9 @@ export class Oauth2LoginService { }; handleOauth2Login = async (provider: LoginProvider, mode: LoginMode) : Promise => { - const state = ReduxService.store.getState(); - if (state.user.oauth2LoginInProgress) { + const web3AuthNetwork = this.config.web3AuthNetwork; + + if (this.localState.loginInProgress) { throw new Error('Login already in progress'); } this.#dispatchLogin(); @@ -158,7 +169,7 @@ export class Oauth2LoginService { } if (result) { - const handleCodeFlowResult = await this.handleCodeFlow(result); + const handleCodeFlowResult = await this.handleCodeFlow({...result , web3AuthNetwork}); this.#dispatchPostLogin(handleCodeFlowResult); return handleCodeFlowResult; } @@ -173,6 +184,13 @@ export class Oauth2LoginService { } }; + updateLocalState = (newState: Partial) => { + this.localState = { + ...this.localState, + ...newState, + }; + }; + getVerifierDetails = () => ({ verifier: this.localState.verifier, verifierID: this.localState.verifierID, @@ -184,4 +202,4 @@ export class Oauth2LoginService { }; } -export default new Oauth2LoginService(); +export default new Oauth2LoginService({web3AuthNetwork: DefaultWeb3AuthNetwork}); diff --git a/app/reducers/user/index.ts b/app/reducers/user/index.ts index 1b35847aa2e3..2ab45b9c812a 100644 --- a/app/reducers/user/index.ts +++ b/app/reducers/user/index.ts @@ -23,7 +23,6 @@ export const userInitialState: UserState = { appTheme: AppThemeKey.os, ambiguousAddressEntries: {}, appServicesReady: false, - oauth2LoginInProgress: false, oauth2LoginSuccess: false, oauth2LoginError: null, oauth2LoginExistingUser: false, @@ -120,36 +119,21 @@ const userReducer = ( appServicesReady: true, }; - case UserActionType.OAUTH2_LOGIN: - return { - ...state, - oauth2LoginInProgress: true, - oauth2LoginSuccess: false, - oauth2LoginError: null, - }; - case UserActionType.OAUTH2_LOGIN_COMPLETE: - return { - ...state, - oauth2LoginInProgress: false, - }; case UserActionType.OAUTH2_LOGIN_SUCCESS: return { ...state, - oauth2LoginInProgress: false, oauth2LoginSuccess: true, oauth2LoginExistingUser: action.payload.existingUser, }; case UserActionType.OAUTH2_LOGIN_ERROR: return { ...state, - oauth2LoginInProgress: false, oauth2LoginSuccess: false, oauth2LoginError: action.payload.error, }; case UserActionType.OAUTH2_LOGIN_RESET: return { ...state, - oauth2LoginInProgress: false, oauth2LoginSuccess: false, oauth2LoginError: null, }; diff --git a/app/reducers/user/types.ts b/app/reducers/user/types.ts index 2ec740a55dc3..74f5c47d22be 100644 --- a/app/reducers/user/types.ts +++ b/app/reducers/user/types.ts @@ -17,7 +17,6 @@ export interface UserState { appTheme: AppThemeKey; ambiguousAddressEntries: Record; appServicesReady: boolean; - oauth2LoginInProgress: boolean; oauth2LoginError: string | null; oauth2LoginSuccess: boolean; oauth2LoginExistingUser: boolean; From 6b9fc5139f12546b6d026da64a88f4291647760a Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 11 Apr 2025 14:58:24 +0800 Subject: [PATCH 031/187] feat: routing to AccountNotFound and AccountAlreadExist --- app/components/Nav/App/App.tsx | 10 +++ app/components/Views/AccountStatus/index.tsx | 21 +++++- app/components/Views/Onboarding/index.js | 69 ++++++++++---------- 3 files changed, 63 insertions(+), 37 deletions(-) diff --git a/app/components/Nav/App/App.tsx b/app/components/Nav/App/App.tsx index ec07fe09dbe8..9140bf2c382a 100644 --- a/app/components/Nav/App/App.tsx +++ b/app/components/Nav/App/App.tsx @@ -181,6 +181,14 @@ const OnboardingSuccessComponentNoSRP = () => { ); }; +const AccountAlreadyExists = () => (); + +const AccountNotFound = () => (); + const OnboardingSuccessFlow = () => ( ( /> + + ); diff --git a/app/components/Views/AccountStatus/index.tsx b/app/components/Views/AccountStatus/index.tsx index f17aeffe1f87..28e6e79b4ea9 100644 --- a/app/components/Views/AccountStatus/index.tsx +++ b/app/components/Views/AccountStatus/index.tsx @@ -5,7 +5,7 @@ import { TextColor, TextVariant, } from '../../../component-library/components/Texts/Text/Text.types'; -import { useNavigation } from '@react-navigation/native'; +import { useNavigation, useRoute } from '@react-navigation/native'; import { strings } from '../../../../locales/i18n'; import { getTransparentOnboardingNavbarOptions } from '../../UI/Navbar'; import { useTheme } from '../../../util/theme'; @@ -20,16 +20,24 @@ const account_status_img = require('../../../images/account_status.png'); // esl interface AccountStatusProps { type?: 'found' | 'not_exist'; +} + +interface AccountRouteParams { accountName?: string; + onContinue?: () => void; } const AccountStatus = ({ type = 'not_exist', - accountName = 'username@gmail.com', }: AccountStatusProps) => { const navigation = useNavigation(); + const route = useRoute(); const { colors } = useTheme(); + const accountName = (route.params as AccountRouteParams)?.accountName; + const onContinue = (route.params as AccountRouteParams)?.onContinue; + + useLayoutEffect(() => { navigation.setOptions(getTransparentOnboardingNavbarOptions(colors)); }, [navigation, colors]); @@ -64,7 +72,14 @@ const AccountStatus = ({ variant={ButtonVariants.Primary} size={ButtonSize.Lg} width={ButtonWidthTypes.Full} - onPress={() => navigation.navigate('Onboarding')} + onPress={() => { + if (onContinue) { + onContinue(); + } else { + // better handling if no onContinue is provided + navigation.navigate('Onboarding'); + } + }} label={ type === 'found' ? strings('account_status.log_in') diff --git a/app/components/Views/Onboarding/index.js b/app/components/Views/Onboarding/index.js index fc8afc03fb34..d9f6315199d0 100644 --- a/app/components/Views/Onboarding/index.js +++ b/app/components/Views/Onboarding/index.js @@ -224,22 +224,6 @@ class Onboarding extends PureComponent { * Metrics injected by withMetricsAwareness HOC */ metrics: PropTypes.object, - /** - * oauth2LoginInProgress - */ - oauth2LoginInProgress: PropTypes.bool, - /** - * oauth2LoginError - */ - oauth2LoginError: PropTypes.string, - /** - * oauth2LoginSuccess - */ - oauth2LoginSuccess: PropTypes.bool, - /** - * oauth2LoginExistingUser - */ - oauth2LoginExistingUser: PropTypes.bool, }; notificationAnimated = new Animated.Value(100); detailsYAnimated = new Animated.Value(0); @@ -401,7 +385,7 @@ class Onboarding extends PureComponent { }; - metricNavigationWrapper = (targetRoute, previousScreen) => { + metricNavigationWrapper = (targetRoute, previousScreen, metricEvent) => { const { metrics } = this.props; if (metrics.isEnabled()) { this.props.navigation.push( @@ -410,7 +394,7 @@ class Onboarding extends PureComponent { [PREVIOUS_SCREEN]: previousScreen, } ); - this.track(MetaMetricsEvents.WALLET_IMPORT_STARTED); + this.track(metricEvent === 'import' ? MetaMetricsEvents.WALLET_IMPORT_STARTED : MetaMetricsEvents.WALLET_SETUP_STARTED); } else { this.props.navigation.navigate('OptinMetrics', { onContinue: () => { @@ -420,12 +404,42 @@ class Onboarding extends PureComponent { [PREVIOUS_SCREEN]: previousScreen, } ); - this.track(MetaMetricsEvents.WALLET_IMPORT_STARTED); + this.track(metricEvent === 'import' ? MetaMetricsEvents.WALLET_IMPORT_STARTED : MetaMetricsEvents.WALLET_SETUP_STARTED); }, }); } }; + handlePostSocialLogin = (result) => { + if (result.type === 'success') { + if (this.state.createWallet) { + if (result.existingUser) { + this.props.navigation.navigate('AccountAlreadyExists', { + accountName: result.accountName, + onContinue: () => { + this.metricNavigationWrapper('Login', ONBOARDING, 'create'); + }, + }); + } else { + this.metricNavigationWrapper('ChoosePassword', ONBOARDING, 'create'); + } + } else if (!this.state.createWallet) { + if (result.existingUser) { + this.metricNavigationWrapper('Login', ONBOARDING, 'import'); + } else { + this.props.navigation.navigate('AccountNotFound', { + accountName: result.accountName, + onContinue: () => { + this.metricNavigationWrapper('ChoosePassword', ONBOARDING, 'import'); + }, + }); + } + } + } else { + // handle error: show error message in the UI + } + }; + onPressContinueWithApple = async () => { const action = async () => { const result = await Oauth2loginService.handleOauth2Login('apple', 'onboarding').catch((e) => { @@ -433,14 +447,7 @@ class Onboarding extends PureComponent { return {type: 'error', error: e, existingUser: false}; }); - if (result.type === 'success') { - - if (result.existingUser) { - this.metricNavigationWrapper('Login', ONBOARDING); - } else { - this.metricNavigationWrapper('ChoosePassword', ONBOARDING); - } - } + this.handlePostSocialLogin(result); }; this.handleExistingUser(action); }; @@ -452,13 +459,7 @@ class Onboarding extends PureComponent { return {type: 'error', error: e, existingUser: false}; }); - if (result.type === 'success') { - if (result.existingUser) { - this.metricNavigationWrapper('Login', ONBOARDING); - } else { - this.metricNavigationWrapper('ChoosePassword', ONBOARDING); - } - } + this.handlePostSocialLogin(result); }; this.handleExistingUser(action); }; From e548824326d77e5c4b9942cc4b191a77103b69c5 Mon Sep 17 00:00:00 2001 From: ieow Date: Mon, 14 Apr 2025 12:35:22 +0800 Subject: [PATCH 032/187] feat: decode jwt, return accountName for social user login --- app/core/Oauth2Login/Oauth2loginService.ts | 10 ++++++++-- package.json | 1 + yarn.lock | 5 +++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/core/Oauth2Login/Oauth2loginService.ts b/app/core/Oauth2Login/Oauth2loginService.ts index 5eb38d48ba32..41a9d099aaee 100644 --- a/app/core/Oauth2Login/Oauth2loginService.ts +++ b/app/core/Oauth2Login/Oauth2loginService.ts @@ -12,6 +12,7 @@ import { handleAndroidGoogleLogin } from './android/google'; import { ByoaResponse, HandleFlowParams, ByoaServerUrl, HandleOauth2LoginResult, LoginMode, LoginProvider, Web3AuthNetwork, DefaultWeb3AuthNetwork } from './Oauth2loginInterface'; import { handleIosGoogleLogin } from './ios/google'; import { handleIosAppleLogin } from './ios/apple'; +import { jwtDecode, JwtPayload } from "jwt-decode"; export class Oauth2LoginService { public localState: { @@ -95,7 +96,7 @@ export class Oauth2LoginService { }; - handleCodeFlow = async (params : HandleFlowParams & {web3AuthNetwork: Web3AuthNetwork}) : Promise<{type: 'success' | 'error', error?: string, existingUser : boolean}> => { + handleCodeFlow = async (params : HandleFlowParams & {web3AuthNetwork: Web3AuthNetwork}) : Promise<{type: 'success' | 'error', error?: string, existingUser : boolean, accountName?: string}> => { const {code, idToken, provider, clientId, redirectUri, codeVerifier, web3AuthNetwork} = params; const pathname = code ? 'api/v1/oauth/token' : 'api/v1/oauth/id_token'; @@ -139,7 +140,11 @@ export class Oauth2LoginService { endpoints: Object.values(data.endpoints), }); Logger.log('handleCodeFlow: result', result); - return {type: 'success', existingUser: result.hasValidEncKey}; + + const jwtPayload = jwtDecode(data.id_token) as JwtPayload & {email: string}; + const accountName = jwtPayload.email ?? ''; + + return {type: 'success', existingUser: result.hasValidEncKey, accountName}; } throw new Error('Failed to authenticate OAuth user : ' + data.message); } catch (error) { @@ -168,6 +173,7 @@ export class Oauth2LoginService { throw new Error('Invalid platform'); } + Logger.log('handleOauth2Login: result', result); if (result) { const handleCodeFlowResult = await this.handleCodeFlow({...result , web3AuthNetwork}); this.#dispatchPostLogin(handleCodeFlowResult); diff --git a/package.json b/package.json index 7a25f0846c93..c042fa79cebf 100644 --- a/package.json +++ b/package.json @@ -292,6 +292,7 @@ "human-standard-token-abi": "^2.0.0", "humanize-duration": "^3.27.2", "is-url": "^1.2.4", + "jwt-decode": "^4.0.0", "lodash": "^4.17.21", "lottie-ios": "3.4.1", "lottie-react-native": "5.1.5", diff --git a/yarn.lock b/yarn.lock index 1e7c74e6eee8..ae221f41fe30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20444,6 +20444,11 @@ junit-report-builder@^3.0.0: make-dir "^3.1.0" xmlbuilder "^15.1.1" +jwt-decode@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-4.0.0.tgz#2270352425fd413785b2faf11f6e755c5151bd4b" + integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA== + keccak@3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.2.tgz#4c2c6e8c54e04f2670ee49fa734eb9da152206e0" From a2c57beeeda810313a0e49785261c8b2308e0f5c Mon Sep 17 00:00:00 2001 From: ieow Date: Mon, 14 Apr 2025 18:11:05 +0800 Subject: [PATCH 033/187] fix : dispatch passwordSet for rehydrate flow --- app/core/Authentication/Authentication.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/core/Authentication/Authentication.ts b/app/core/Authentication/Authentication.ts index eec213301fe6..be5a7bfca494 100644 --- a/app/core/Authentication/Authentication.ts +++ b/app/core/Authentication/Authentication.ts @@ -526,7 +526,9 @@ class AuthenticationService { const seedPhrase = bytesToString(result.at(-1) ?? new Uint8Array()); await this.newWalletAndRestore(password, authData, seedPhrase, false); // add in more srps - } else { + + this.dispatchPasswordSet(); + } else { this.dispatchOauth2Reset(); throw new Error('No account data found'); } From 2c8b43304879dbdc78e7ba8d21e06f112bb71c15 Mon Sep 17 00:00:00 2001 From: ieow Date: Mon, 14 Apr 2025 20:31:36 +0800 Subject: [PATCH 034/187] fix: fix undefined useProfileSyncing fix height --- app/components/Views/ImportFromSecretRecoveryPhrase/index.js | 4 ++-- app/components/Views/ImportFromSecretRecoveryPhrase/styles.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/components/Views/ImportFromSecretRecoveryPhrase/index.js b/app/components/Views/ImportFromSecretRecoveryPhrase/index.js index 845cb9659da7..ec2c0861232b 100644 --- a/app/components/Views/ImportFromSecretRecoveryPhrase/index.js +++ b/app/components/Views/ImportFromSecretRecoveryPhrase/index.js @@ -66,7 +66,7 @@ import navigateTermsOfUse from '../../../util/termsOfUse/termsOfUse'; import { ImportFromSeedSelectorsIDs } from '../../../../e2e/selectors/Onboarding/ImportFromSeed.selectors'; import { ChoosePasswordSelectorsIDs } from '../../../../e2e/selectors/Onboarding/ChoosePassword.selectors'; import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; -import { useProfileSyncing } from '../../../util/identity/hooks/useProfileSyncing'; +import { useEnableProfileSyncing} from '../../../util/identity/hooks/useProfileSyncing'; import { MetricsEventBuilder } from '../../../core/Analytics/MetricsEventBuilder'; import { SecurityOptionToggle } from '../../UI/SecurityOptionToggle'; import Checkbox from '../../../component-library/components/Checkbox'; @@ -112,7 +112,7 @@ const ImportFromSecretRecoveryPhrase = ({ setOnboardingWizardStep, route, }) => { - const { enableProfileSyncing } = useProfileSyncing(); + const { enableProfileSyncing } = useEnableProfileSyncing(); const { colors, themeAppearance } = useTheme(); const styles = createStyles(colors); diff --git a/app/components/Views/ImportFromSecretRecoveryPhrase/styles.ts b/app/components/Views/ImportFromSecretRecoveryPhrase/styles.ts index 814a51d433f7..8411b940a789 100644 --- a/app/components/Views/ImportFromSecretRecoveryPhrase/styles.ts +++ b/app/components/Views/ImportFromSecretRecoveryPhrase/styles.ts @@ -61,7 +61,7 @@ const createStyles = (colors: any) => ...fontStyles.normal, backgroundColor: colors.background.muted, paddingHorizontal: 0, - height: 44, + height: 66, }, seedPhraseInputContainer: { flexDirection: 'row', From 486e5c2d4d7b96d728667612adedb4d061171089 Mon Sep 17 00:00:00 2001 From: ieow Date: Mon, 14 Apr 2025 12:35:22 +0800 Subject: [PATCH 035/187] feat: decode jwt, return accountName for social user login --- app/core/Oauth2Login/Oauth2loginService.ts | 10 ++++++++-- package.json | 1 + yarn.lock | 5 +++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/core/Oauth2Login/Oauth2loginService.ts b/app/core/Oauth2Login/Oauth2loginService.ts index 5eb38d48ba32..41a9d099aaee 100644 --- a/app/core/Oauth2Login/Oauth2loginService.ts +++ b/app/core/Oauth2Login/Oauth2loginService.ts @@ -12,6 +12,7 @@ import { handleAndroidGoogleLogin } from './android/google'; import { ByoaResponse, HandleFlowParams, ByoaServerUrl, HandleOauth2LoginResult, LoginMode, LoginProvider, Web3AuthNetwork, DefaultWeb3AuthNetwork } from './Oauth2loginInterface'; import { handleIosGoogleLogin } from './ios/google'; import { handleIosAppleLogin } from './ios/apple'; +import { jwtDecode, JwtPayload } from "jwt-decode"; export class Oauth2LoginService { public localState: { @@ -95,7 +96,7 @@ export class Oauth2LoginService { }; - handleCodeFlow = async (params : HandleFlowParams & {web3AuthNetwork: Web3AuthNetwork}) : Promise<{type: 'success' | 'error', error?: string, existingUser : boolean}> => { + handleCodeFlow = async (params : HandleFlowParams & {web3AuthNetwork: Web3AuthNetwork}) : Promise<{type: 'success' | 'error', error?: string, existingUser : boolean, accountName?: string}> => { const {code, idToken, provider, clientId, redirectUri, codeVerifier, web3AuthNetwork} = params; const pathname = code ? 'api/v1/oauth/token' : 'api/v1/oauth/id_token'; @@ -139,7 +140,11 @@ export class Oauth2LoginService { endpoints: Object.values(data.endpoints), }); Logger.log('handleCodeFlow: result', result); - return {type: 'success', existingUser: result.hasValidEncKey}; + + const jwtPayload = jwtDecode(data.id_token) as JwtPayload & {email: string}; + const accountName = jwtPayload.email ?? ''; + + return {type: 'success', existingUser: result.hasValidEncKey, accountName}; } throw new Error('Failed to authenticate OAuth user : ' + data.message); } catch (error) { @@ -168,6 +173,7 @@ export class Oauth2LoginService { throw new Error('Invalid platform'); } + Logger.log('handleOauth2Login: result', result); if (result) { const handleCodeFlowResult = await this.handleCodeFlow({...result , web3AuthNetwork}); this.#dispatchPostLogin(handleCodeFlowResult); diff --git a/package.json b/package.json index 079e285ab1c1..b1a9b09f03d7 100644 --- a/package.json +++ b/package.json @@ -292,6 +292,7 @@ "human-standard-token-abi": "^2.0.0", "humanize-duration": "^3.27.2", "is-url": "^1.2.4", + "jwt-decode": "^4.0.0", "lodash": "^4.17.21", "lottie-ios": "3.4.1", "lottie-react-native": "5.1.5", diff --git a/yarn.lock b/yarn.lock index 64cca9f4faf9..376f859f2260 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20444,6 +20444,11 @@ junit-report-builder@^3.0.0: make-dir "^3.1.0" xmlbuilder "^15.1.1" +jwt-decode@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-4.0.0.tgz#2270352425fd413785b2faf11f6e755c5151bd4b" + integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA== + keccak@3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.2.tgz#4c2c6e8c54e04f2670ee49fa734eb9da152206e0" From ffd77facc125ffa182acef3e9b7da94cbb19e49f Mon Sep 17 00:00:00 2001 From: ieow Date: Mon, 14 Apr 2025 18:11:05 +0800 Subject: [PATCH 036/187] fix : dispatch passwordSet for rehydrate flow --- app/core/Authentication/Authentication.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/core/Authentication/Authentication.ts b/app/core/Authentication/Authentication.ts index eec213301fe6..be5a7bfca494 100644 --- a/app/core/Authentication/Authentication.ts +++ b/app/core/Authentication/Authentication.ts @@ -526,7 +526,9 @@ class AuthenticationService { const seedPhrase = bytesToString(result.at(-1) ?? new Uint8Array()); await this.newWalletAndRestore(password, authData, seedPhrase, false); // add in more srps - } else { + + this.dispatchPasswordSet(); + } else { this.dispatchOauth2Reset(); throw new Error('No account data found'); } From 2f0355df58ec331d4820a9b56e93b8aa5363b087 Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 15 Apr 2025 14:41:55 +0800 Subject: [PATCH 037/187] fix: use web3auth rn-google-acm package --- package.json | 2 +- yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index b1a9b09f03d7..1d9b774626bd 100644 --- a/package.json +++ b/package.json @@ -334,7 +334,7 @@ "react-native-fs": "^2.20.0", "react-native-gesture-handler": "^1.10.3", "react-native-get-random-values": "^1.8.0", - "react-native-google-acm": "git+https://github.com/ieow/react-native-google-acm.git#09de563582649c34069d06d087f22c998b37f436", + "react-native-google-acm": "git+https://github.com/Web3Auth/react-native-google-acm.git#71367b39a22cb6684d79752cce0680b63c25c9ea", "react-native-gzip": "^1.1.0", "react-native-i18n": "2.0.15", "react-native-in-app-review": "^4.3.3", diff --git a/yarn.lock b/yarn.lock index 376f859f2260..4c843ce27088 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24520,9 +24520,9 @@ react-native-get-random-values@^1.8.0: dependencies: fast-base64-decode "^1.0.0" -"react-native-google-acm@git+https://github.com/ieow/react-native-google-acm.git#09de563582649c34069d06d087f22c998b37f436": +"react-native-google-acm@git+https://github.com/Web3Auth/react-native-google-acm.git#71367b39a22cb6684d79752cce0680b63c25c9ea": version "0.1.0" - resolved "git+https://github.com/ieow/react-native-google-acm.git#09de563582649c34069d06d087f22c998b37f436" + resolved "git+https://github.com/Web3Auth/react-native-google-acm.git#71367b39a22cb6684d79752cce0680b63c25c9ea" react-native-gzip@^1.1.0: version "1.1.0" From 3f137529275d8f65d17b4f1878f1f17cb8450f1f Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 15 Apr 2025 18:24:35 +0800 Subject: [PATCH 038/187] feat: integrate toprf sdk --- app/core/Authentication/Authentication.ts | 39 +++++++++++----- app/core/Oauth2Login/Oauth2loginInterface.ts | 5 +++ app/core/Oauth2Login/Oauth2loginService.ts | 47 ++++++++++---------- 3 files changed, 57 insertions(+), 34 deletions(-) diff --git a/app/core/Authentication/Authentication.ts b/app/core/Authentication/Authentication.ts index be5a7bfca494..80952bcbac09 100644 --- a/app/core/Authentication/Authentication.ts +++ b/app/core/Authentication/Authentication.ts @@ -38,7 +38,6 @@ import { uint8ArrayToMnemonic } from '../../util/mnemonic'; import Logger from '../../util/Logger'; import Oauth2LoginService from '../Oauth2Login/Oauth2loginService'; import { resetVaultBackup } from '../BackupVault/backupVault'; -import { bytesToString } from '@metamask/utils'; /** * Holds auth data used to determine auth configuration @@ -485,19 +484,24 @@ class AuthenticationService { password: string, ): Promise => { const { SeedlessOnboardingController } = Engine.context; - const { verifier, verifierID } = Oauth2LoginService.getVerifierDetails(); - if (!verifier || !verifierID) { + const { authConnectionId, groupedAuthConnectionId, userId } = Oauth2LoginService.getVerifierDetails(); + if (!authConnectionId || !groupedAuthConnectionId || !userId) { this.dispatchOauth2Reset(); throw new Error('Verifier details not found'); } // rollback on fail ( reset wallet ) await this.createWalletVaultAndKeychain(password); - const uint8ArrayMnemonic = await getSeedPhrase(password); - const seedPhrase = uint8ArrayToMnemonic(uint8ArrayMnemonic, wordlist); + const seedPhrase = await getSeedPhrase(password); Logger.log('SeedlessOnboardingController state', SeedlessOnboardingController.state); - await SeedlessOnboardingController.createSeedPhraseBackup({password, seedPhrase, verifier, verifierID }) + await SeedlessOnboardingController.createToprfKeyAndBackupSeedPhrase({ + password, + seedPhrase, + authConnectionId, + groupedAuthConnectionId, + userId + }) .catch(async (error) => { await this.newWalletAndKeychain(`${Date.now()}`, { currentAuthType: AUTHENTICATION_TYPE.UNKNOWN, @@ -516,17 +520,30 @@ class AuthenticationService { authData: AuthData, ): Promise => { const { SeedlessOnboardingController } = Engine.context; - const { verifier, verifierID } = Oauth2LoginService.getVerifierDetails(); - if (!verifier || !verifierID) { + const { authConnectionId, groupedAuthConnectionId, userId } = Oauth2LoginService.getVerifierDetails(); + if (!authConnectionId || !groupedAuthConnectionId || !userId) { this.dispatchOauth2Reset(); throw new Error('Verifier details not found'); } - const result = await SeedlessOnboardingController.fetchAndRestoreSeedPhraseMetadata( verifier, verifierID, password); + + const result = await SeedlessOnboardingController.fetchAllSeedPhrases({ + authConnectionId, + groupedAuthConnectionId, + userId, + password, + }); + if (result !== null && result.length > 0) { - const seedPhrase = bytesToString(result.at(-1) ?? new Uint8Array()); + const seedPhrase = uint8ArrayToMnemonic(result.at(-1) ?? new Uint8Array(), wordlist); await this.newWalletAndRestore(password, authData, seedPhrase, false); // add in more srps - + if (result.length > 1) { + for (const item of result.slice(0, -1)) { + // vault add new seedphrase + // const { KeyringController } = Engine.context; + // await KeyringController.addSRP(item, password); + } + } this.dispatchPasswordSet(); } else { this.dispatchOauth2Reset(); diff --git a/app/core/Oauth2Login/Oauth2loginInterface.ts b/app/core/Oauth2Login/Oauth2loginInterface.ts index 1b785aa94996..430918f6d46a 100644 --- a/app/core/Oauth2Login/Oauth2loginInterface.ts +++ b/app/core/Oauth2Login/Oauth2loginInterface.ts @@ -40,3 +40,8 @@ export interface ByoaResponse { message: string; jwt_tokens: Record; } + + +export const AuthConnectionId = 'byoa-server'; + +export const GroupedAuthConnectionId = 'mm-seedless-onboarding'; diff --git a/app/core/Oauth2Login/Oauth2loginService.ts b/app/core/Oauth2Login/Oauth2loginService.ts index 41a9d099aaee..a4ce7ab273d6 100644 --- a/app/core/Oauth2Login/Oauth2loginService.ts +++ b/app/core/Oauth2Login/Oauth2loginService.ts @@ -5,34 +5,36 @@ import Engine from '../Engine'; import Logger from '../../util/Logger'; import ReduxService from '../redux'; -import { OAuthVerifier } from '@metamask/seedless-onboarding-controller'; import { UserActionType } from '../../actions/user'; import { handleAndroidAppleLogin } from './android/apple'; import { handleAndroidGoogleLogin } from './android/google'; -import { ByoaResponse, HandleFlowParams, ByoaServerUrl, HandleOauth2LoginResult, LoginMode, LoginProvider, Web3AuthNetwork, DefaultWeb3AuthNetwork } from './Oauth2loginInterface'; +import { ByoaResponse, HandleFlowParams, ByoaServerUrl, HandleOauth2LoginResult, LoginMode, LoginProvider, Web3AuthNetwork, DefaultWeb3AuthNetwork, GroupedAuthConnectionId, AuthConnectionId } from './Oauth2loginInterface'; import { handleIosGoogleLogin } from './ios/google'; import { handleIosAppleLogin } from './ios/apple'; -import { jwtDecode, JwtPayload } from "jwt-decode"; +import { jwtDecode, JwtPayload } from 'jwt-decode'; export class Oauth2LoginService { public localState: { loginInProgress: boolean; - verifier: OAuthVerifier | null; - verifierID: string | null; + authConnectionId: string; + groupedAuthConnectionId: string; + userId: string | null; }; public config : { web3AuthNetwork: Web3AuthNetwork; }; - constructor(config: {web3AuthNetwork: Web3AuthNetwork}) { + constructor(config: {web3AuthNetwork: Web3AuthNetwork , authConnectionId: string, groupedAuthConnectionId: string}) { + const { web3AuthNetwork, authConnectionId, groupedAuthConnectionId} = config; this.localState = { loginInProgress: false, - verifier: null, - verifierID: null, + authConnectionId, + groupedAuthConnectionId, + userId: null, }; this.config = { - web3AuthNetwork: config.web3AuthNetwork, + web3AuthNetwork, }; } @@ -127,24 +129,23 @@ export class Oauth2LoginService { const data = await res.json() as ByoaResponse; Logger.log('handleCodeFlow: data', data); if (data.success) { + const jwtPayload = jwtDecode(data.id_token) as JwtPayload & {email: string}; + const userId = jwtPayload.sub ?? ''; this.updateLocalState({ - verifier: data.verifier as OAuthVerifier, - verifierID: data.verifier_id, + userId, }); - const result = await Engine.context.SeedlessOnboardingController.authenticateOAuthUser({ + const result = await Engine.context.SeedlessOnboardingController.authenticate({ idTokens: Object.values(data.jwt_tokens), - verifier: data.verifier as OAuthVerifier, - verifierID: data.verifier_id, - indexes: Object.values(data.indexes), - endpoints: Object.values(data.endpoints), + authConnectionId: this.localState.authConnectionId, + groupedAuthConnectionId: this.localState.groupedAuthConnectionId, + userId, }); Logger.log('handleCodeFlow: result', result); - const jwtPayload = jwtDecode(data.id_token) as JwtPayload & {email: string}; const accountName = jwtPayload.email ?? ''; - return {type: 'success', existingUser: result.hasValidEncKey, accountName}; + return {type: 'success', existingUser: !result.isNewUser, accountName}; } throw new Error('Failed to authenticate OAuth user : ' + data.message); } catch (error) { @@ -198,14 +199,14 @@ export class Oauth2LoginService { }; getVerifierDetails = () => ({ - verifier: this.localState.verifier, - verifierID: this.localState.verifierID, + authConnectionId: this.localState.authConnectionId, + groupedAuthConnectionId: this.localState.groupedAuthConnectionId, + userId: this.localState.userId, }); clearVerifierDetails = () => { - this.localState.verifier = null; - this.localState.verifierID = null; + this.localState.userId = null; }; } -export default new Oauth2LoginService({web3AuthNetwork: DefaultWeb3AuthNetwork}); +export default new Oauth2LoginService({web3AuthNetwork: DefaultWeb3AuthNetwork, authConnectionId: AuthConnectionId, groupedAuthConnectionId: GroupedAuthConnectionId}); From f1a16ae5364a67b8ad1b1cb58175d88cccb35330 Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 15 Apr 2025 18:25:01 +0800 Subject: [PATCH 039/187] feat: add local toprf packages --- auth-network-utils.tgz | Bin 0 -> 45922 bytes ios/Podfile.lock | 2 +- package-seedless.tgz | Bin 0 -> 34198 bytes package.json | 8 +- seedless-onboarding-controller.tgz | Bin 0 -> 123594 bytes toprf-secure-backup.tgz | Bin 0 -> 88975 bytes yarn.lock | 156 +++++++++++++++++++++++++++-- 7 files changed, 155 insertions(+), 11 deletions(-) create mode 100644 auth-network-utils.tgz create mode 100644 package-seedless.tgz create mode 100644 seedless-onboarding-controller.tgz create mode 100644 toprf-secure-backup.tgz diff --git a/auth-network-utils.tgz b/auth-network-utils.tgz new file mode 100644 index 0000000000000000000000000000000000000000..bc74a456274d9f755f15c885ab775464c7aa7c2a GIT binary patch literal 45922 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 B8u0)C literal 0 HcmV?d00001 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f6e7c88dbbc9..0301c4b85a44 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1354,7 +1354,7 @@ SPEC CHECKSUMS: FlipperKit: 2efad7007d6745a3f95e4034d547be637f89d3f6 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b - GoogleAcm: 8bb9e2d7b8effe2f178d3212fbb90719e8d52ab8 + GoogleAcm: c34f3645441b02e92bd6a1ff3d85358169a3331d GoogleAppMeasurement: f9de05ee17401e3355f68e8fc8b5064d429f5918 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 diff --git a/package-seedless.tgz b/package-seedless.tgz new file mode 100644 index 0000000000000000000000000000000000000000..1e4283bb47fdcb1d99dbccbe810f1012165dcb07 GIT binary patch literal 34198 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 literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 1d9b774626bd..dc2780649976 100644 --- a/package.json +++ b/package.json @@ -142,7 +142,10 @@ "nanoid": "^3.3.8", "undici": "5.28.5", "**/@ethersproject/signing-key/elliptic": "^6.6.1", - "**/@walletconnect/utils/elliptic": "^6.6.1" + "**/@walletconnect/utils/elliptic": "^6.6.1", + "@metamask/auth-network-utils": "file:./auth-network-utils.tgz", + "@metamask/toprf-secure-backup": "file:./toprf-secure-backup.tgz", + "@metamask/seedless-onboarding-controller": "file:./seedless-onboarding-controller.tgz" }, "dependencies": { "@config-plugins/detox": "^8.0.0", @@ -155,6 +158,7 @@ "@metamask/address-book-controller": "^6.0.3", "@metamask/approval-controller": "^7.1.0", "@metamask/assets-controllers": "^51.0.2", + "@metamask/auth-network-utils": "./auth-network-utils.tgz", "@metamask/base-controller": "^8.0.0", "@metamask/bitcoin-wallet-snap": "^0.9.0", "@metamask/bridge-controller": "^7.0.0", @@ -204,7 +208,7 @@ "@metamask/rpc-errors": "^7.0.2", "@metamask/scure-bip39": "^2.1.0", "@metamask/sdk-communication-layer": "0.29.0-wallet", - "@metamask/seedless-onboarding-controller": "https://gitpkg.vercel.app/Web3Auth/core/packages/seedless-onboarding-controller?19c8035ca514d04d55e08c0a9e91610ddd9e03a5", + "@metamask/seedless-onboarding-controller": "file:./package-seedless.tgz", "@metamask/selected-network-controller": "^21.0.0", "@metamask/signature-controller": "^23.1.0", "@metamask/slip44": "^4.1.0", diff --git a/seedless-onboarding-controller.tgz b/seedless-onboarding-controller.tgz new file mode 100644 index 0000000000000000000000000000000000000000..70a1f678048cd627de77583e1097a6359bd1f5c2 GIT binary patch literal 123594 zcmZ6S1B@?0*XC#L*ybJEwr$(CZQHi)*tV_z+_7!j#@_ec?3dl`oOIIFNq1G{q^s-s zod-V}66n7N%B8Qh`v%+mt>1HrT+^DZyi|0;hz5~r zQsV*H_svb_04}KLgj>#|=7%s*QojKZ>@m2389JfF3lZ}+fzcHK1&fD{|6d``Mw(cF4`_< z|HMA~NPhTz6fQ|QHMMR)$gm_Hjywg{{zc3o6ktqI%*^sW58l}cPnPU3BZ&cPx{PJo zQlc;e4NDYdT}zV_TbW5}-ZCMXf_O|&OgL%yu&*m+S|We_qlR(%L37!%O`b9rO!%&s zRi@MyyNzAzqUPyY%b7Gn7$uwlXCYGHIO-ZJ}pNU7R;mv~Z7Ia(3Bcrsf z^Eq4V`re7D`#AyW>+@gzp|Mq|{(-na_@i~Oa$p?dITn0rU)(#o5o7he*oi@1m6EE%OpifDha7R_A!Lb&=UL( zEOU-U+9(Oa_7K=01R>Gi7ZRJ{Yzym&z#y* z=orhEC{LD2cF+R!NBMHu#*Zhm(J?s$DS$8Uq*;>5BT{%Z#IHFy#l(e46N{`V!VL7I zvVMpu;2jp6<$9LYwbKInXJ*(tyEF4~L4_}dOk2*ci4{>|I0NdU3 zPSVzZU!COpkKbLTzlQ|8UJdNC9uNMbw=C1@%b&h$xMz2XmnWn(eh#7;2=Cw`zjz#6 zM2m!A>_-IPRlOfH3pQ~*_gf!_$JUNUUXFE$j17po2ys1+x;XIfi<52Y`~9aX42W>w z{i~Pp?}2Xv|BodD|FXW79e%6C0lqbUf-n8|5Anm>%$HA2mX`eCcmGgE~pC?Yy>yF)ax3nfE%ie9WP^ZqMa# z_1U~Od7d8&hkAsqTZHF$J=Hc_pWDO_k)%4gZ=(< zP=;Xp<^Oe7#QpX!e|M60eppL98Nf=|idb@HX}bOlk4Yv3)pzsD?DNQyp=5pcHa21m z4?vNQ4cw+onp21hjFM4Ke0z#1JH4KRQ7s2^{0#B7_j32I1NCFf<1j7Vj zjT}=ZjqM&3?)4Qj>%od;;+cx}Hd4U%Qfgaz;kT)l`A?azg!w8VoD_UHOKbK%E~9K| z^nT>?_3zKgXj!j@5 zZzzcUf4HgQXx$rF@6S-Mri^ujixcMwW2Y_FNi^{SDt*vpj+-UriZE@$SnnM}5rqno zEc>Y&TUtcb%BrkM@XG+4k!PHoof&g}S!9;RscoXAEcoPvH2WL?fG2jrn31dP0x*pR zSWF+5Y5;7QaF(g0)O2u6hzsfnONyJ208`cp%aZmuy%`HugIzSM+ zug|+kFIFP_8#uo90YP$6lUh&?{%~P%%B0Cj*%*uIGn!-umII$0lavFW1ZF4)4I!@F zY|uE^I_1=fr48J)yvZUqcyc70Du=jrv9Q=H8a@EsTb8SuyC&v1l*;;Z?0CYIlJaVi zxFpv~NlQYQo%xDf-)UV?ZncR|D)~(HL|5N{p`ln9T{%ehm(jU=bA|rGF;`X)?P^hx z0iUC6jrf<}qB z9$`}=y!mQP#e~tJN24@yOVt79{(S>6WfiyQW6m!H)lg3EkLpm0NcS^QRkT|a5lue^ zX>nGmGM9{;xn_I>Yt&=jr~O+*7fy-~qa1LoEAXL(x9Y|pmRKGm8G1SL4q=kno5dlLz-6}tUNWeX$x$y!Ro?D;E}%pf65aL<4uT~+>ZrmW-O%X9b+KBm%bLQk%;LnmA3Pz)Pdt85!2#Tfbp;ZbIQCUU5W zud`{!ux8u_$W6-ew_UJJV#+sTywbXc>N_9m0;-RJZMR=mjycCf%sYC30qIRu60}s_ ziK?QNtE}3ve=tWI*PPW~1OtfjumDgq ziIt$ztEds=UqT;UH)`YVHkSa4q?~_q%rOp?LUcL~w#6bj=ZF)2h|@i-VC9`Rv*(FDbGcNKF9Fo-2_7qSw)1wf1F%5OvDvo~fjkvj9l?s7BlMLx;OW^_$btor zgAah(C+W=~dx!zHG7Nw7qHsW&<7^yJ1wn1b{RZ;Kp&rf7x^J;55f&RDInbwD^S@%_ z>cY0Z) z_Lmh{_O+j5zNFF<0EhWnRk6?(#30gDWIsief_Ut)zUb#^*RIt*T*8HccNVVol`VB@_Nay{V0mDT4+B`Uxp{0XniJCbp6`W`fu=fj=+JCTOv`=2T+wH&T(!`u?!4j3PHh=%&e$3 z0L~MpMwv{e$rzA?gHzHxN;y-??b0c;zNi=vM;1#glav#pJ>^sqAd`c~lm#v%ecpyI z(YEc*U#As34vr1>9T}`=g2cC!%a_FHI6Jh!A}Pty2ANMd-6R^}trUa}`vnQx#T4cK zOu1l5Gy$A&ZQr&ls^AR;UL!nj4uOSOyV1YKlEIqgbpdXqqf&*{@nDo3y@oSp!@QT|vci$nguc6z>-HDKxSdE9U6nxS^k1*hbR0XAioiNKkL9Tmf0ImhKxm=a*kwp;%_T5-3pvqws;OXj;_B^7PU?Dvp{2U06-l< zxBOYCv{O@We(AsErJKVftMdMXjgO8#E8A%@OT=}9>0#ZLNz&lpI7s1kaWDbh(abr` zdLjQ6a)pp$hZ-`4ahs%DxSRyF81>@O*&^4LMWay4=%(3a-8PUo+nq-&NgJhdh76W1 zO8Og!R-mk3ASX-@zO@SBNuuhH8wcK%qx|>*(4(5j&8bm z++58EwM2892*hun4QZ4Z3v5m#x#^0nT^qz@#(6jd_iz~Q%Zw82hx{@@{4J+TV?a{+ z*C=byS`2u_Ts#lq!7l7Fx=kP10|)uoLyo7}!7vBtBu=)z+4evDq5rSui)NG0n7#HiHvo{j}6TIf}eWFbi2c>;N zGfvPCl%|e(kkuPZ8|A9D9Dq8N`lYkVI3)&%t6oAK#QhndZu@8P$OOhr?Zij|dfTK> zg2$Ny1Pr7zOm)xYYim77&!K)qW75K{y_*gDN6TQy2kO{5r&lXqjz$f}+A@u9%dugL zv~?q#a^ykaNKZmEqVSTSY+|ANmtg{xNqw893pYQ)w4JFcGsF6Wnm78lcK3NWEL4&nv1@^bFWd}i;Q^!63D}t; zk`O~hVcw4ks^%O(!cxh6wS5*bjKWmdDQwRWiwnX~=9Ihal6+#_XmIJsWSe=K- z^zQ^|a)h+V{129;Azl^j zp2u>62KlV?YaZZ(3VotX-ZXk1&LRKbpdcW*(9hEFDJT@o*YsT028Jqy!?EoLQKZq` zop1y`b;1$otKQlP6It2p+M)Fugi20oSm?EBucvV+qe zO*u=k)UULDlHr}S8rKmT0lt&O`RMuxJ;1o%UI^2o`q~;D1g%VtWni|>({I#QWtkB-k^0zUr5sy%^c{T+B=ly> zIdtzVRqtZhSWs0i5Cz-(#F}gwc%6!yG9~QP0WfxU3!KwZq1)CL+k~~|+VOTmE4063 z&VjSNHI$bhue6b1buE;wyFJ^#~#>?uaF*tMkG!aLl?|Qy6UliKVxiyb3{&;`~FQl z6|{jD<)K?SvfORY&(6o;!51HDkdYiP54cbVPp&icHty4i@QQ360@CfThS5HWJD-+4 zR%)@uir8kt&PBOx%vLW6nSCwS)p34qKU?Qtp=fm23U z;>*XQe&%LgoOp`@p_xI_bhN|xb<#fc03$u@-OKu*VuXrjZJN3h73ZKOM5?+)!zk5i z-dZ!Q`?4ZpW|&tOmGsj1Ze$70)u#XaODT&r7*Q|N(2Ci_L1LwwTFv8c$VVocEngVZ z<#GvCzQ2*w%ZY;Jo3ruW?eE^Q^{DLrj@87%X*Fxf-_8ZJ=O?47EI|W#9fQH>cO>d06Os%QZ{T6Y`xGd}@r2PDkw9N(G#-TDIx@)*tB~9h2R3mp zL55ee1{}-R*auw{)0Wt<4>F8+e*KT!Kmdl5H?w?YXSa&4AqS?JetLhr)w1RQE=DY1 z<6n&s{c@Nr3P|M53>u1I*K!TK)R_H=-PKe%;J*&h(z+PE;Q}j71%wGd{os(@NAbap z>?eRLg>74*6p?`QDe~3;WC| z*l~01qY26kkjF1`a0nkL0lBz31x}~tx=oT5CJW{~WJtWjJzToTa`Tz2Fd;G9m)3F4 zp!w?L&$~i{DApdjZu%bEE_2oydw`6qT@fRT^?+Moc~N-bQQ)N_M1cFp$A0q?=d84B ziUhw-LSER|%ng}}4l5vh$UtjbsUZnczGk^rv@bbRzxrbpO<$olf&(-h?3}l?TH9$% zHW^@asw95IV5&U^2HDLfpc*HsoZnztBLK*qH0>TW{taA>q(cUz{O&YL!;8Pj)S9@vn{@d)l)$ik-m~D&enE}ExWfP zRl7QNoRS2j+?rT@dkPS4X~jXP(Y}l&QOiIVG7W!LX~*o+8zj{@=U{vUpH4i06TsyM z3qdE?@pJ$-qFCSbz5egR*C0{eO$n36U&khsenDfA9v;2#UB`B{UL%Q$Ld%$_?PFn{ zk{);jDP-$;7<$^V^uK-+F$z^-NL2?oE8>LGC>}uZ{hh{ls^M zI~2ly-k$HaCpemp=X1@L?~eBdYT0vrD110PJadN!!a||?r+(ve`vu(JAh&rV<~^?s z-W8tR3pp6~#sLDFA0^)V_LSfH+H+gax#z<3V(U2W+q-(xI`1{leShetp)N|Wrihdfj@O=oI$X=KF#c=L`(q9Y+7}0Omkuk+R&(oi zOx%0^gt+6j#M9(ozf}Ae5p5<49iEr}35#p>{#e?68J9)gJI_7ts=3Q4PLJRj;`Ui| z@2KFUFUZ{gp=5>Mg74gOyZf&~j+<*re!rx9a8aGb8SU&;H;i+LKHjSU<*P4(9+6jg z-cK_O?V|>v$Y<#K|`M79~9 zX%~vcj`=Cbr!8CjDle-dxlP_3yNlJde^n!Q9{z)Q;5m}F2*oPC9!i&MAqB()-&Mn4 z9q$b*wN1eKY2(R4?MbeiCU8jfR=+qW`a02YCdZlAck$RZyuoRKoQKeQqT{W-pa2MMOZ;*R@X zyu%{7_F2vC-cY_OfVx-#cn8{LKhHVF(XLs_$u-hb*=C>i))ccHw+7?&LJ*@k&1lbz znE!QsxMGIjg(waIUsd&L_JRL#4cVo7W(wp+)-X^EySw+~q*u$of9mMh<4mU!*!xEi z2}drAdv~yZQwdzsYd1t6|6?;+l^-RvqO>?8L9~FOD*^xg&v4-3uruU^u4GF(fla^( zZ@|vCFrNdq@aGu3o72pL65s>unj%;c%~&ht*DI+Z>}D9FE2hhlizsMDskbASRs_+PN7#efIz(2D>k zagoL0T)f>O9p8Q%553|z8ygflc!Y8B(Hwf09^1q=PSqlZD8nRAYqNc!jv!=D;iIXE z6A2Bo@Y;Ex?>^MG?f&RZahDZi40O^ zq6aTQ(9@sRNf{!Oe_T>U?swdeRzEK_nj!KlwT6o5KX~n5z;5Icb<7}dI}@`U0RN;7!8aU^VArDN%Nc83{@39epg8v!t4vr=-`faDoTrG51^ZyD(5BEI=wnjsczmFQ@rAqzx+6<9241QK)(Bb(fJ)Pu`MC86KJ zREv6?zVQ#yyvU3OY-$W0e&EhOe}tOcjPCjI)^ZY|SO?=srU_%Mr-ah93=^}ao#UH>q>Z&#Q3*yFkRG{C^` zePNB#4ssquPL3!7;pS!!0;dFU{QP5m2J4gai_rbu*TOk}GBk^r(!nl} zS_0nJ=&{g$A@8LHp<@BAgO(2K3Nhx1mG*>89@A8v&h^);LStE4wg|b(4dW;`kbZ^B z+Twwp_eZ{UIYV7wfKE$Pf#^ze652JRwpDES?~AUj65?EXxf3QYD(>uBA-UxY^IC|g z$pfDLmUo4uNt+=2AXJks-}pq9L0kyo3tq^yXca=6R~3oIW?M-}vv<2ud5X!CCPk3B zuDNaVCj3D6JXbr6j{h!$yk1%s`7Uh6hui~b%Fg!vF@P?MJdZ^lR%us`QxH-!{^4tD z4TBtXJ8^^x#0f?a>q-yI6w>s4<~kN-n$QY84e55Q_2etCk?*KcFB>-_V{D5Q5d1ri z^pLU3@P749?|jj5JN!?lWqq0DihVy_ctwt@y+kT5g{qo6jAYrGrFspo^8UAi1Dhj| z6J--2>d{z_6LRt~!|wxT{bc<1bN!$K#q6(Z4+!|>zc$r@ZQKtqQU%%*4d?SE6S|E$ z(us?9R1(I$d8sp_tT0x*!NLP86BPO!)nRs|4Voyap00yPvb>Mkk~0rUc=|4`x#srLzUESc4UFmpDZWMHt)1RM z_2gjorjc?6P(hSdqvcxf>*dmP&YsMDZIwaxR*tX_^ctjlafnAbYCHYV zh|ryaQ1=I=(vA(_SbZT{-C&O<{lRlR`zexM6yZwk&Yw{^wTs|>$MRNn;M%L(pB0X^ zu|N;{x{r4BvP`dXF+3~K{Po#=v;9s&87N$Om{kXvCKxL2F-(x1sj6PMUUy}&(Q$R- z^*5S^KB=>*>ljyR_gH#^xqvyBw=(fRW^OpZRq7`RVDuhW4`0pS-&d;4-!!O6DqRoD zx)fa(M?kC+Gel5{dt0b2RaDj}FG1GtL2)qWQScNZfqr9*Z*9alr~2 z4f)(H#bRyiw@_uJEE_2dX58fhcDGG`))%CFG|KnU#J)Je(M8H7@;no3i)5iiY!xfM z*6PapQGpw86)TYHrP>T->fHMcI(e1?MV1?o+CL5h`f0F>%aq4E(rV0yb)uWYha;I= zr2V^jqH{m@?xDA!k-FOT+Tp9Q}0xX8lSkll$fv2p~LS=Jat{c zv*s|e!Gbj~B%a&%g5>-~y2h6AMdD{UUTD7A+U8@XbyIS%B znM1e}Wdfdl`oghT%JbaMdTpX;=|)T?Fw|&I@V@@&qJKi^7T`N~?T)&VpewQUV7~b~ z^REegbghqm_tv5#yWv1-oo>+H0-D!fhhDn*ee4pe*gOo?f0qfkO?J{X52zacp}{eZ|-xKCkiJ$C1sr)7vGT2`naSy}SGX4MN8M-7_k$eLs=Q72Bg8Wy3~J(ZVh zv%K_na(+<+SX|F!*+CVPZnLbCc6}x+_`~V~_Q{s-soO!{Kl>3-{!BnrcUssQW0v~^ z`KP##qdGUi`ruW>RXF=QG{=u};`~IHChUODEGEZ;(hvNN7Eh;UEG!St|ySjhl+mDuI0NJBC0nE14+i#a>2gfCmQ^wQ5cVktWh_}ZjHzvcxKgTbGWK|yIh~-yg%4hEN5JDJ6de7tE4Un+d;n$Jq~daA zgTup8*)+^t$bfg;8&BTQqLh3X;*XIiwpYZ78pZM)OgYl~xHEeY&Jg^;!Y@Cs%oeIz z_RwP2wtlbE7wCMqkFBU0g`b6@4vip3RLqn4rZsWw@IEzqB#C@jtXt(?JOE@~JocXp;38V(mf`(Fi`A;_oqP zq9sUV6eBW-++qYu>`ntF<$Vy5%77K74ROGR4OXW4rIlQgNk*9?D0_+Nq$+w5-EZ;- zNl4&%6Qy76n@HrGLD}Z}6XQ3ZA2%i}P;zXJc0?gAfs9>*!`QLT7Iz7w4lqH1mQ0v7 zIY4rVzgPh>E)B*~(f(@4hw&yb638p?hU!0D`;igbZnt6j%H65$ zuHzILCi_FAKl))RaVY3YobsRqBw}h*6aOKPutfa&b(|kr&PZ8$e}T$ahw^oaev}yt z?)Wr>20_s^Y{HdbZbxTnq%2=iI%fRMD0qdEhG{~$M&rUP=Kl|VTn|FWS$W~m=mDdN zn%mGs&Xm!+tNc~7)6qq>Yr`j8N=@$?=nx1N&=TB4kOex10!<7+ZUJ~?V-8^hYL%y` z_QZH6BgH6udRJ+nRuK;k2w8;w!y=ibvEIpnPS_7|lxp#)>qv$CV*O2YB`p!j?hb=| zsD-IqQ-+ao$&~Jl6b<)F97pHh806={#jUI)L?TUy z@|Za(5?&x!K^j#R(6xBITID^>(UpNQs;!Mw8sB21Y@%KfvEaL#0t6=p9UMy!vOLz( z;ol^*YSQFdWjzK&f(-c68T$7Ni*S-zN5UB1Q?nczOT^+arAi8| zpma-yx4+oK|MU3FgzcwTY27l|(g-7(ankDd4 zf1`AnP6RgZ@w>IDjK!DjCCqD2T7_?D{(QyOp>-V;0{!SPh45>Rtjb`~(;p>`bX6Dy zM&=j&DmcEk1SLeT01_9O7eMKi&=PD-16-^9G65Rc)Lc-5VnG+ii`vKf3SblwnbBxQ z_#XcCNLg)41Y@?qmQu5kP-;2E5J_6x(!v@!dXyEKC`qN-ApsjTSC7JLkFF0VbX8JV zxi4Wc5`gqyfH&k}I8R2;!v8ILOh-8@A_)i-5tvV=@?3L)Ro1)|fzx&$wq6<6Dq*uF zP+Fp@qK9cWQ`y{iQ$Y=ymUAVlluJTkZi$2^;S;n{layUt&6xXDn=vK=Z5V#Y66AnS z%A8jXFG3(MT4;*4M^oFIPYrg*S+b~3Z)&Gfs&-Umgw*cnH4_Xq>@#%8Jim4bs+Fo0 zHg%Dtqv*?KNWXgf^FR|R1Q6P=O_M|zEEamqNw%@FfaZI0ts5NTd-y8o=nP~=EH+L=wt8&8p+MpZER50^7CbS;#L&f|tIj6`hx zhvno!2!clddYo#f{Zf>#OoT#;DKi~Is5!MH2Gx`qQx*x8c!y9}5a#s(rf&5IobecV z?L}>upSTCaZ5-h*PR+m(A$T6OOAnwmZ0JAwD zC5ZwwF#wx(9Et)MDbkR&alFxj9sCl6BU33xs3_U8i`Juw83de}YW7PvMpX}IQZy31 zxNla^X1YHdH~SuU4xT&iNWm=I+i=|bbliDVG&uESvczl=MG_3ln|biA^K3Gz)DZJ} z;Q`t0@c2H>0XYd+pMLktJkCTN^OQdi6KPP^O`jbsR7V?|GR-vfeII9Qj`AS+#NCXhW)%MC!Lu1!bB_ zZ*wAx5<}W+ENbkjCOUJdjYK%zoF380n*8lc)u=!6p0WS%ZUfz@zBq)V1)=Lx!g^Lj zDL{9-P-tvRSf`^VZi+^%H7>!k26v}m^(qc*OALPh3$ESU_ zcJS9-_+>9`WQp6_YmrO2hQ;_~T2yrn+93MXod30cU~xKdBrRXdtZc1*bcDooNKV(V z9QgiK7&7$2it5Q03a*U4c8Kc>^a%ad5w+Exced`B;F`)=1#Va%Y8@vTs`&9+t!Q|HQ5p-WzbY;22wTUW3Hn)xJPru_C}ohc`sN z-s@?e84MY^E-=vA$^?7>P?w432YhQVa6? zE5`WZ-fV^MpqQ~@`;RSS1-;Yy=+K zon1*osNr#*LQ1>JGL|Bl0vSU=*+R<{<9U3)U69s>VPM=TMWD)#P6v=h@rm~8ycqMBz6w$4H*nc z`u3FDmrG7^(1Xr4&zwvO$DfxS<*IIRJg`$W{bHGA0<0P>l&2c#eH_zi?O%sviamLx z4fz>!K7*W?#N%WRNLga2_tC&4(F<1Bi!xpPm4C8-RYKQzk)qpKl64*FI?QtP0mPng zlH<@6oP@+Aq+-?Hn%C3?UUbJcbt`OZgbQm;T0bGO4Lj&!rev6tb;~Cyd5j&cZQj{yo$Xszmq~_7-*A+N8O?uZr z-U>LFTBR0s-Wky6Uh-QW4?^+IVzaXqpU_j2`qjIK6rFSrl*DC8Ek7~p>j>76}^}Xw)tshk676SL<;dN-AV#uv(`au z>2S;hcC6O+yVjMC1?9pdU2m7CU3`-jT~swXn5muFtjl}|Kel=bnvhfavh^rF%!)_q zhU6e9Vkszi1^$y-g?8)L-0R~K$8zHa;7@8z`>~RS$bNK{v{m*JcBE-48#oEg5lI{+ z?Ixm=yg8}2g9;6=g=Mu@?jc{W+h}-*c%uhWu_vmG)rb}$K0T`i7f$JNDwX2riV9DSl)=1QN_sHxtLT&GR7OALs}KNi;J6Yy@yfMdN9 zcB_ke!U7lOS;`T~we^VGf83F-s121L@9b6}6Ov1G68JGbq4bvXgruUGdv(-J(BSNN zhaG+JXAs_3+)>2g}%%U1w#Z{m1&zRBO1`V|9;>}h;e z$(7QHr>x87qOmRO)AaSmg%+|^7rqb*Y3KP7ClAYbzugS!C(vEY)y$QHWhn%ca!iJ} zxu&svm7?=@Z#pen&MTl%T7;z}+Q$CLY>p!{wQ|&WFgu*&+POhDEAmDTr=u9%)naTO z0|&>Xp}jMTMadq6)*>Z_c=>}v9=&Ajk(!PWli?&J%UcVjh#z)s?L$1`Vs9~)eA6w) zV9c@pTPegXQVl|hg{WK1CZ?CQ&8)TGf@`VyRfYztBONgY)tgQoLFD96~%WIN^GX|*j@U9 zxPR-LW>x=ft4*(ip^w$PSbgq#gpzB}*pa$bCY-yslVT#)gB8b;tLW&tUJaH)ThFx_ z2WgyktDS3qzR8N)zo$_LV9SJ8N@Gy?U=T=V!Mz1#EF6DI!Ga&CIMP2j8?^}80(#g% z?c^c)X(UISC<=8Fy0AG!v<=oNo?+qe$K;gu{A@CVU1Y+`h#=Q#WTy)iblwIWEMx-q zCWJW03zIuuv^8kOyf4|o$x4P6lpF!sz>=DCs73Xn)3nR-A(Thq`NV4U%CuRou7s~} zN!rvCw?E*^8o@PQpQN<_TU%Q!jH5IR+7Bqa;XT4ss=?RA?2;g^2hQWMo+nkZUsGoe zQz6KQ>$JE%hFzN76&U&w7YuWs=e47-2$(6FJ;D55MW z%2F(at060e0e9Y9lQ$jb;Eaa)rp&S4Qk$C~Lt2VTT{WDv*3Gq0bQnaU8gw>MNwpEF z#6H$=mpCmhR}IK+23Rz|G-dPB@++$dT#=S8FfHhStc*NrOz8nzo^(e_2=G^XN``zm z#(UE2)dK|wA1+*f={YK?G+dtS+7E%j;PcyxnSpC!pX(Z2K4{!TI#&a>fj}mTj;WkQ z&>gc#{?7DgvqZ{+I*DU2TSbVW0yhHW|3vugR{vnrI$IYaEEMlx(8%>Xaxsq*nMq?pg{p;TWXf&MxyKo$8tsA$ds+7JsLO%-D`;PgGf zbd%5F8c`IxK&G?1YCw(opE_i!15E4EYR4;4gN0WMAQU@Grhj-3Fwxm4BTQbfv_tpP zdbmS_nFmhZj4P(OttT>EQ_nitWIAxL^Zx#xEFKT}+1J}^_tV#KcRRqP>Hq3*04RDQ zzxaJ&@-^^21c3n9S*}}LPx}AtLQy8vRV4s5HFot%=Jaa1Nu9!A24l^??^>;~%eTIz zRuPX(EsX|?Qh|!Zd3#a_Vc>cwj``%Tsbvn}P9JOo{^NP6s>T-NztTkfwvQ=$kMyS12nI!4Mp zocieen^1**K#uNDReFey!rJ@Op}+fWbr_Lo>D|Xf_PuK;_Hm?s|G0}Inu~4<4j;A6 zXX7@C938=H&Ufwv-aHjS#q6+AalB>uUFSLBR@7sr!$?e8(>AzG{HrMJ{DDchnyOv6 z7*%o>uE79vT1#zLF)=a@T#k_P$tAFm<&7c4Bkyc^lGQ0oJ4{a!7zUdT2v38y{)8qJ z+=A&bQ<(ilyM<_}_hJ_VxY!~Tl)fH7JuY>ct^3{V1cledVReMt^_8`DE{yaMWswZr@nq=4<$|0jWKDa=yU(R^iX4ULSmEdZ|T3T{}1#2+@ywiH2k=K4J4N# z(Ei+vj*bR%|J>Mq`s(loYfwpcuY0cQ;EJMju4~qmf@HpZs3sj&<*KGa;V1>PXN6ZTU}l-lS2Fz!-t_XAZGr&-3BKF^l52de&A5#%W-Z$P~ zP|~k{zX<3)U$)3n-O?Y63RxoscUT6b}=-7iesAby>?+&$Gc_`I|u#EZA#%a}^`GX2B z{*?Z*tN1c=T`I_8?@9k!+5B;P%REc}EJ$l`FjRf<*$8uKxbSlPv!gLC8 zbL*!kx37tS^k#d{?f|B>pbd9U(nEsKg=(~uOa+)ppieg`>45;nQF!(=%!U&fV3D_=Vjp-@)p+{0nsoX^^%fc7XPg7A20kbr zBN6U1v_BUYEE6}a1?A+27!5Bpj4=0h#yBF7!_)}FiBF!nN&?Ra6tT_~=M=~m&u9Su zA1_ak2gth0-6|C>(_kG<>lAvtPkq?<6{}nhHS@luc`T;Y`|~gdh6IC_|UDp1k4kky8%GMztgO zXULcf;^E3WEU^EN4`$y5&Mx)8y7a1v0{j`(|1rUAR-r)xDZv^@NaDzNk_<4fnn)O3 zN-mS!Fb{^267M8?qUR{w^TwyJ@@5|23qHD|hqMc#ny%&iBC=(qGct?F!Y-_lcbjyO zzv?g~g?+>q$RCQCWJYhI1?DBdPDDDDbNa}%Bv|9flowj%N#rbg6J!Y}h41>B=Kqls zOWrj>F%QPI?COt29XA%yG`JJS7QUncF98_ro&seM$n8g4GR=wH(pPH-cSeDL@4nPZC~>V+mx^i02T(jADI}?M3n7EL=88O z7Plu&wRD~#+f~Br=($(ahWC0qs5YtUZ`S^gEc`%bHm54?c+8x|w#fWh0badgqw9)Z z&DjM0y3O6n!YE~O`FJlrZ`JasXgdfqYE7pa`EX(E6ZS~YtZ z2fv^dlfDDm8O;ROEPRIlKZ$c=A#xfG;T z;#9VE0w@p!VCRZdEC?xCTW_n*$f6>IXYu(@#}zym{n60z`5vCPz#@pqd(FmDfipE_ zEp}aGpkah^kUfbU-tUAKb>`DoEw*JRYl*1EOfx%67u5JHmD|7=tJ1*cSPy|8)aMw+ z2nAsmJA1pMXQK28%&V+~B{55)fKjE(h7s?eG1h9(zm}=3eQS`iP3upxQpdv7G+-=< zu;_qCqyw0n7P1SV&Cgv39H8h(Q6crPCa@>PZVR;&sNK{`e(rF-a2{i@|4AmZf)}s(NZO!U`#O;XKcPPA*`HH zYlBnFvQX7sKq^dvAopOiu`GD5_|Fs_eZ&&MM9X_j5kZ#4VB0ppjMY!5vNwtp7LCI0 z!dQZ;6I!p2bL4y-3+hh>g}DN0#*AGo#!n!t?*MKM!$zF!nUj3LIrH$FJmEJmCQP-+ z)#lfgd`}00*<`cip{eyMMYG1$Ue%Vz7=|9wWb_*&**Vh#W*QyiwS?zlmNDhOkQXO{Tw3PDujb#Nk(|TR@zmo!SS@o!=#X4xI1I^ZhF@4 zJ>1}b4G+aUC|oDMrVGX3S#uB6ym7DXGCJ%s7=zo+^D>jl(;!W={~j-4Op4>l$KZ^E zRcNwXf7&Lub!giqG*85S^$|p0$SBZ4>}>T6a(^5$?@%9B_(<7foXbcv!tKLL<&cUg zxQ8xTnreCy;U(wOLQ`S4xkRB8@gh+AOvgQJ$OOtbE?6 zJ-K6E2J^~!b)z8-!=ugCW6N1aYGvu=W8*jaBeDv(ap*zE1YT$XkZ?57;cS=T75-tA z2#KuVxjh#2Rns}kdAI6G0}~RS`Naehk9*_ax#Q{I=#3uD)A_hTABK}1iOT9@3jRjvf$ZsJz7`9;!6r)DTk{XjZnjTH2>t%DY<1>(kYB zX(sWt#@Fc&BLTXnP@@Cwobk6q7IS|z7~>;#zw;!UUJHVFy-55&-o_c@z*5ui=9kTf z1Q#-UQ}e7{zyZnt@4(V{m-PpQyc5rcB1J;#7I(LK9n9KxQwNm@ z`%5LVP5r%JIt>C+6AhgyN_~On_oOtb^=s!WO5N|JAnwd@rzbn?bLl6Mnw=38=KhXG zyWO(0@17mCp4jw+D=Xx`cFJRb@2ig3!M!k?5++29gdVL>}@O%VeEe~tSdl(uvm)I`2A8KyZ0!B{6Sue@C9l^6!%7l?hg zULUbWfI*3b=CKJYk7f4$1ETOrjf7$Qk=;V+&12%!_Rr)$MuI|aHa!;n4%+BuKO?&q!SJnhrtfJ zigSdwU{TXYm-%eal@eMD*dUKPVk;MuEG2+w5fIc<+TDl8Vv)k3y0M3seb$$U&ORjs z+HQ24-eAVQRF{-i>wZs6@sfhG8NBMl&v8p%{!;nwkO%ng8(-Xvth*{-i3mlbU7_^l?FgniS2oBY3|HEfdQr z)UGoN>s*XG=!%=6>o)k*!KlK**7<7!%XwuO*Tp|{Gs@AjnqcWigje{Op3|VoO1UbH zlNoUY^nw6NoT0L-#2@0OUthVceBHv#HkOldQ`~K=m2z^fSPcj=4S(VB>nJ<8l9cD3 zYpcJYp8n07PU^RrQFYT&d~iOiaaR6R={8;WC&6^pOT_s5zQa}3ez$)9X2zwbef6@b z>)tz!K=Hkoc+s#HKW=Y@5noL3c*tq6#YF%K+VJ`77)N&qHuBx5**VYo=W~KB7&SS; zxDhzij(Xw06&D*LEQ@0q!N)Ac_54t5o#R>xi1CSQ2kf5{b83a{|NdKn@O#`kh^Dw8 z1abLkfst8CQ~J5X7N#Q+URb>8g^5lgyb7fMGIG_E_J0oiMB>)Qt+ixGtjeNhI{Mpi zmSqQ6N4sEFGl*TRnQAiL_{TbtEr9Ogq=>=%J%+8;x#I2Xj|WA&c~l7>>xjz9q+Uwn zn_9hH7MzREI<-&0F@Rd8!9VQ3IGW9(kqYDa#3+&5uJnsK8?aM@3AkRZ28wH0C{F64 ze6?S(P%Y#VP8fRGtvo@QXGtuM;`x(!WTW(@ILHY_4D`JWM>&`&F~v$rB16~aw9vQF zO?rMn2%GYhB{ecxd=vjxS9ptL=&oMpbnImjsN9Zyg6jhWq@210(DdgrxmtaW4>zj0uug&tL-viQqbsCgxna%l6Ft77iX^1as#2f>zFll`OhwRdV=*F7S5VM+Ugbtiyx8}~ z;Njbi)$}l2he7#Fzx&^s{R={urm1Mc{t0@+77Rw<9Z`YE@otdsgxdlWMg5~N@6bcs zj<(`U0Q_|}R>8^&FmnBCR61Et7VdppHB|`w70;QB+vP#i?yGe*=TF^XBB13{+uv&z zt~R&+kQt_wR;V;QHob$PFpzkLz6L8XI5x|nBi5l~*Ll66k(88qK_m8;Rs8y_&IfbX z;@GAV9o`&_!tQN2P%$6(e46Rb_b)q#i<9tmNfGMm`%U{&uz zHcZ#``Z}|&!xwUKqGBdPy(85VcDpFmI0qj8Ruo#Zi+*M5IU*EWu;m;bA1gSvj^?{) zXBuxNX?*63_m3gogrSyHw(P9&%|C6f&w%B05hBz+}W^mHo4_UVCn zB$!zQI)9i5lB)jKL~vFkDF1Ki1cQ)!yC1IaTyH~7CAEp6WK9?|Ds+5V>VG^0uxFRo z=>PE$DB+(=4Ov)zcnFrhET!2O)VLdHJzB-ARfh$75dHm64^nD9Np)Ba!|h;Phms{x z=wc-6B)($)YZlBIKAn+A(v_W^kyN*D(*lpd%KERO(Hl)oO`JXD?+^9Xq?r z!T4wa0b)fKU-~@m{Ju^I#6`!xWltlcQcN3;tws2&ZqTllXT?y)c5W4o*M?)+h=C&6Tjt~B}$|p5STwlcP%Pn&htkG zitO-L7kb2q45-;E+m;6=HvLpPxRwwC(pkjffaX zYRC9xm8O54a2lO?neO+weOfHUte8oXSA?kpG?2C)h1%NrG?(LrFgZ_FoBuO!+Xfa{ z)U@6-vVP&2+1wdY@Kh1W?}LR`Z1)&Aw|P|qhme%O;ZZmITj(lS=<0>+my{5?FvO04 z=(rMh#PO>o!qxXYzn7Y${3oKoDp?p{;QNp5c@K=Hg|+F03S#-zFZ46Nr)Llz*9~~h zdC2|zEV9?uMh3g~pBnO%fb_WJSjhL0%LC4Ku+Y}|?o@|%4PB%-3-GE%lfDS}YQAIF z>Ivbk|6J)*IzHNRN~(*=gmtUAJ8XYCk;6i4-(e8DE(J~8(FgS^mrxv>C*{Rn*Mz0e zz)c%Fk2eHP$a>4ycl)&?ZSkZ^9a$EJrJgpH9}mB`^r<6%agI8H?`Mj~goy4wtp?oJ zVI$|LaJ{fc(rohktOt{>^zQ*H^GrJx1WY6Cfd57J@jlS~vv(GDV`LN{)31zyo( zoOFK5-$(Es$bF4J%({kVNfDF0XN}Cv;yN;5hx_+KBI)^`yra^TRFn<%qKdrhp*M(a z{qomoL)jJLK##BMH%mXS%{!lL*Kf#n`}dvx{3%~~kgp5sOLZby6rRrUDrJvOx*R`0 z9Wjt5^7a8XaYW|7d~;MA-C9&b!n#o_kIZCW-gfcddWf?0Czc+e9=LYH8jdBM_777| zNbm6v$tn>ce*Ves4E7Yh#E5^Qy5wF4QNSPnTGmZa6d?(+u%4wk`Xti0v79>$_~IK4 zb2E3A*qMOD#^Bx2vvU19EZxVDjTbMC!UVdhlefU;0MBWM41>4REpM7EXUT$y?9mc` zpFjNC6m7rKvQ%SXz?1zcO?R4W(7p6Ujd1C!XpT3(*_qIjJqXT>gAW73)q?VKC7x9%ps2C z54n9dH$DD_6von&{##pdIL-? z3oV_8grg&ABb6_%6Awa{s^N-d`&8^J7022<<%?J8U18ISokI&Koz~~XT#3A6ltiqo z_hV0-yrD1KX3be$PN4JkwNJ@ZAFf}*Y=?%u&mEiYr#vEl+p99azdallOK zz+rYkFIA&&BHK~d0!`yO1;VcSDl55_wJ^JAQ2o2)5z}I%VV5@5r;Bw9@il%U94&)} zXJ+p1rrIW61N%rb6+6-mI9^fLENZs#iRAW6Uyp6OgrLTC!!43kX^(-_!zgj2jb6HA ziZeHnZo!v5&mO@>J?SKGfevRsyfDhNBhgMi6~4#{c|8RP`Fh?1!q|PDjLV+0=?=b>iVm^v}pE zJH^Fu0uRx83iME_ z&_R#ruW_ZEVGElh}7?WsM$;S85}bdg(C+39x=HG&Dm zyqh6St1hiBypJUG4=^Bjhy6R@H73)OL91QUgyIGfk4>8974uBj( zZi{z6FdLF^N-zm@8k^tk8BPT2%`J~@-not^U}TbicO@1YwhBv6E!poaXH3POE3{nKM+Jr>S<+psiV^MET2f{3+OaN|$&|D@uU-kB40#<4jSM4C4a zT)xS#5?@)W(2!RPZ&Bmp3kbF4cjBBa#;L(Z@(s2nNBt!^lwQ8#JwYEtN@H$WapUM< zshzF*iz{%8&yzO1WePm8Wf5whXjoTUmPl=-ra1LbvtWO8EWcWwm`LKBxd^Z?<>AIc zpJYW`4PM6hUQ*&6UK3tQ*6`(2z531jE}^LO`aI;IQmOgyHT%JR3l9;x+zQ4)Xs86?q!JnHeXr0zeyL&DVD`*@y$pe*xo|O6*!!?NLXJZuBCou< z(p+{r4CDf)6|*!pej4ti+O_m1nsTHXw)CQOuI+s83_2oM+xa2i66{hgQu|GK8Uj_m z(0Lg5QIkz^DPk7eHm8*#-qn)_DEClkNJDYe(s54Krpq041gXa^`)Mt(eDgQm%>CWM z_U)yqY#+)lnhz~wm{m0NmRyp!u`Rmg605EV zby&xNaFyKSq~o>aHahk?Rv(9VP=${0@B!bG*zx^0e80T(0pCL*{U7)qat9}K5kc(s zU+}%(z<R;nd~d1of8cvms)(Tjvw(hQbkP3|-{UtnS>6bJvy#zOIdKK% z{`S9mL_*?xhs9`g&LvT`%_&WG`1}#&rT*PI4=eoI=;f7dJ5=HhE4rLwyD*$L|w9YS1T=-}HXR0?w^P>Ip8otOc0}dlDhKXDCyjy8XZ-zvU!s#fYcm2Qt_bKN7C3Xs!J0Iyy^&XA?UW| z%w*poXKGsc2!4E7*I>g4d+UbFMmNsa$R<{)DHeWjD>DQ_5|JFQ?=p^I_ zfYZbhKW9Oa_!xGm1QNh3y3pPsq#al{Y*VFF-ywQH8$IrCl?{cj{ zG;V(}z55%e^P-NO5ZE|*2MwbQBjZBk=|qhFL9E*jK<2v{H&Orz3#-y1$NaqRDGH|B zyKhaG)&zwoEvT*rT&Qcb|3vZFi0XQ1X_U-@2fd?O0c@(1|D6E#Ibx2QMt-92hozBS z6!+%;C?G7hy>5!hp^Jt+MZneX*2$xPYFe3Qo)`OmKu;ZVS#|tVAx0$71Lz5YP>SST zg$mhIZecQT-GpStOys=3+v_0xd^cC&me{=+w2|+LSV2K4#CoPIcgxZrn-PTqb>ki< zx!>vevS9slc%?p5PzYs@rYHDgNLW=_Op|fUo*YBqzqghtQy@_CFU;eyv%zM0rU>{Y z1{sGfo-1Q2(l><{q@gKEr9gvoojWQXJRn*@m{b$eICDDN^mR|vo+JMeTRa;-#1=Yi*q8P&DEtSZgRE*s-Z1r+g|Ji> z9`|2|w8S17QJjmwwyZVA9vq zrZ|Jnwq@M0wyxZtq+wfmLJ{=S(M!-E4GygWfkM)eXo)W~zQE;Gj| zX;hABTByZEd_VSWdbG#1XT#IRla=@cALlB8u**g=gp$hZTlgR4rcOSXPC)^k20A5A zYjnKKGHc9&(|)Cc+OAV(iK)d7Cb>#S|EqovPw}2aXK@jxr#BI8zDo|D6za=DMgt+eWS19y2yJT3YB#xV;dDpHDlI;RQ~(7 zc?dX&qLKU#B~7D9%}ms2Im@)AWn=S&k}!@W(XGgRX?Ock+zN$ES8wAEEg z5hwprL`Pg3jaYP%KDTL@&dtR~+Y4W(T^kh6QPvfMAP7VCdwX4yCiT0JqmRVIll5wcBIv>ia(KcB;iv)C1=~;6WsRbmg00h9Na}zcz{NX{p1sBOf(c zuI`E)6$1>?9N$`*vXh!4C-~`@M&oa*Y)QWqMA^1{K0q?!D0QV9?=GYjPR=gM%`P5c zP-yx!NQESwji3GnR|<#Haox!^A6^VTwyDd#dL}qsazrPr09cqx^{Wd}Q)~WtWBq=C?`D%QQ)J6ML|j5C>Ia&iQX=dzAK8 zZVl#f_Pas;j$+Lr`svZV9~;W+xZHvCD4Cp#^4ngx$5;Rax}6YkxgxlsiZZLq-) z>vMBu{ZY$Li%CpxgBqI5l>v6tb|oIn*Tjq;xjVcz$g4@{#zkFHF-`e9m%8x~8mj&G zC^t9bxb6h>y%~wqQ_2<|EalYBU((?Tosn(shNMacagL;vHwLu78s<$ioNTfT(lFM) zU&-qXD>?Mia5bKQQW*5$5$7*Y+LOIKILlY(rYNaLlTZo$=;xhv$_8&=!x@*%{@sFF z9G^F9=u>B^zJCY49c6G^83S_yj$da5G#j}rPRC_*H%Dz^pY6n7T83w5 zLdP@nj6Rnw)sBr*TaBn1S=7MXy+|WPUOF)Rb3{ffW9FO`dX~NWy?e~6%>n7kk-x;%F^@lR1^Pn zr(Mg&pc%InESSP@On}4pYR|LW%PAWfM#fC1avA6FbhDHo6jVZJ>wlq|nTB952deg8RF@_WRNxWD`+CVFXZ@-dthi_h?I z?m2-4jJFO)sZ6=}HzZr{;?Kk)x4&lS%bp;DX;N~GBk!7z7U>AHI+);UC8WG>_wu)s zNcDGft)HJ6Iy-UykD?SzA5M!&eG^sIR+%>!#~IEU4#yK+rk?PW_!x|_BB65Fe)}Uz zvN;^f9ix-lDx&qTXYmBvH&F2-3Mn%>Lt$cNWrS)D@A4G{qJ`{3P zwsL37^zTV+y_*9TtV)4hr9Mr%isg)m(+#Ufa<$nvO-DsKtNj1ixqm_vTjxP9nk|+U zSn9v-feiQVQ*)&5{4xeYEM#twj?CgHa(bj~iPYs<;FFkDeza?H{e}J!=!{&!)O?z* z*H$|ElJTWieE*e>cu~_%Ku2s8iI9?!$KYVz#uRGv3#rGKgS{5?=#IwAf8>r42OaFZ zT;{H`{E5CRdh|qx9B(n);qH})5}CKbu4skZeGRGSr1L1>2A6NCOLUbmSNXz6O5+Q1 z6@+1a>{Do@wEE0O^o>&9Op=XY3??^d7ah(;HbnhKsIeAhyOnd<*odq8S&;_&dyh0B z<%}p(z=BHk4lmN$g9GJ;fTY(dn~HRpP1(j>Zk{1a3aqib$-bjQVWCcvn^?S?<@AgE z=BB$)jgZ8#F`_rp|Fe&rmRFC{IrENJxUnD~>=(Z~PHQXqf-O!z1wuzl$zpfiS%Z>` z=7U2%|4_9}I0pWD(?(i-v+*fX`B1J*M)sc_o31q;h1q)aowv|Oy~2C74GdKVBx$|h z!6HH{E)BbhGD|k$Nx8J%>;GgWdB{q?q_~L>*!T82AiU#MWjF`h20Yu7IIRBtGd-qb z=>&k_tXT&Pl<$HINr2URIpZLLR)x)^wWSs1t8ci}8#fAE>h_5ZyETf)l zuMBz|?$H?*okNwXnoUM)C`QXpGzRS%XB7aceAf6>)A3AWHA?3mQW_F*be_qbMd@+< z&kGqM-mibKw&ko$)Ecd2Rvg&Vl#ddtN*pu~{FJ@dX7?|6ZWWzYPYiGz1;{1RO8HML z_me`a!#*o==>)Ig}!BYM}y$k5?_#s?@zUgb8Xga=iij8Iks*`1)pt%DL`b zpR{6zq4^Tzmu<}jTX6JA+TDj(9W~I%*xNv(yH}pLrzIxr>*|bI!{u)7WOefKdrL+V zoED=|+&3E+I zP4`{baz^uUd?P1I510BDcs(}oA2JAkH>uD4)u~jrbSKrRF$*IOJ%n{y{Vd(A>HOb((6 z(*eJUi@c1~-`k|Jm7>uJ~1pXXj7BOGwpgDw;AC7=+ z8`?!d*W3o459#&A%N>6s(XYWI?%5)S{#g!VmQiBCkE_l)VvI&o#KP``>mL!3Xh~n` zD%v_BE>PC5i3RJFLHAjmq4w1b#7)=ZYF$WTH9NZT!cvW2EPPK4lRCF+SK0PwGN`25 zHb#Ag>InagzAx>cqjt*Lv^o?@H_VMpVrpaxLEh`*o-$= zjJ>NQ1w8YB(@72S?@uH*`&-XUIKhXWZ8j1}?te29w(2*Tw-#dAO1!efNQ}tp_~P7A z|DqHsQ?A*_PX5TMbOBze<|Ctp|5y6C=2$bO4(wv#pGRTR!v`a;(5(8p#_Sx-mv&~ZzY2UmARy7kWA5k>FVqn&3q(r)e&1;5GW_+KPlzQC41vbEVlrPdk304Av&X@%R<{utdU~)lGdUT_reeoqaz`PIqzmo8;CQSc} znEeKHw*-MnlHv~emGAJ|qCOub*VE3PY5&8)dXpKQ9t#p9`$)i#_x_p{yxhUG#cc7G4B4jTOWxHX`OlsU?~pms_)rY)D5R=sOL0coQtM^$fR;Pj3cw z>MVY*PwH9Dr&m-cs{p^B;;mPQ=(eCAIsyzpkd987BIme+Zx21em~TneBs(4yw|^nIlVi@M8|gSJo-ZOt__0 zWD?kLzf|Lf=&K{|^=htMHd&LG5#Uhf%1G^q2rE_QL3y@s7Oue#pOISDML&9nl-4{22R<_v`GoXF@s^O(iZ#}nhEeTvjp~!%qEPahR+MK!y z9(l(1ne;I8=nYjOXu;#hjC`hxK{e46ZkF6HPQ`v*KzG7H{%~U;I>_} zITKkHT1&qD8{Tcx)(D4rBrjCvWE2@aO74>R4(cK5{i*e#rl-j0qD7t>6IcUqdi!#X z?$n;B{@%vow59xr24Rj4B++5?kE|^}=Z+>QBRqxQ?PTcq-b%TV-yv*?7l^N59&J1x z-uLdZ2>470ME2uiasSCm(NK6TsL7EBP(&?=yXPUu;ij~bui!*$m7q65_~0t{-nY|llH0m~9^?fLKtX}J9-v2} zJK8plACOhd4hRy@h7MwUm0m{IYn=mz^T7wPH`kH|p~)5p#6wXaKJONU$b=UZuJzKw zumb=RYMBy`P{5Rr8Ttb$D2Y%2wlo+3i2Z;w(qopqVaTzi1s>pF&=pV^VW|cLNDdV+ zL-n9krSOfB^q8r)0?bglno8apU;u)fcmNw(G}c$_XD7)QBuH>4R4twn>`*s!Pgrqw zP%q)GOhXW@dDadHI*1wpR2aI`GkFmi+6N7U&c!KcNhX9H!YPf00tk^x!x90g3d+p5 z)VZOMX$ep@r!ZM&v0-o3+_AlTx<(a^sxx?rwC4J}IpMmAea zLhA_xzpQ(y$WbF~MIIa9P0;u-jr4|rS@45 z%@e1lXhu$~{k!c|Hoh<%Obxe;I3BmZJR+f%`%^(mFHmM#oL0Bd3A;6j|0AH)9TWDG zRy_v*mRNGy$`Tg~1|ZdjMDrXlEUp=~pSGuGdieAs4hmom%tAgIw4B=Vnz50{FT9$O z7&*zo&P56w-7>JX{?SlF#-$a-0SN1vMCQ53-(tQaZ$2$(R{{?JfH=m0f^S5U>wJT_ zT&^nOaiU3L;R&AtFi8eTbQH|ZuX>1hnZxj2BM#`MbBBYm{xZZ2tsC~xDH#BMOta6R z5r4@!G>?Iblpm1_g}Ro;IwTo|X(Q~1-L)+GZcCySp@LB(Ea2H`l%c`x07px!(nr|F zgv!qtYHK<{lT*}Ns(8xC4ipN$_#DC_n%AeoE$O`_i8&l+0*slDoRM9D->r0w@ z!yrKk^~bY->S{zOu)V%P_(Hq+4V?;uAN%1=Sb90=g_No%aI(de0GhMsRhn@p2%Xdz z9T5@)7ko9()|nL&JlnEsOT%sdgHBpHy-h=z`6^P)> zIzAbxJ;Jv#<8+HH62Y>1iFt))eK3@C>DIXMhhffA-RIrQmu$F_7ujv=ihjJlPiRS^ zn{M2)W=JmOS1%3@{y@XJh6YGkvV)d3L`RG*7sGSd_gq{=H~SHpXb;Fh9t`^`IS<)>I=pdga{t&hvNY|>Wkl!ew#pzxFv8dy3zFt zU7wjO5m+5K)UlUkcSzY9Q2R72nj@0}@%iMZ4qk+|@_9;Z?1-s??|hZjR!;o{dBU*+ zm8DR;wDw6!qcl1(c+O?(yM(~#B5B~0lfp#e+}OxXp-ucG)G`#A;jgNz@_ZF9`)}m+z2x zIw4fp#~FNM*LE*p`gkFq4u|vXmi&~Nw?QnRG=Q=ToJl&BFmNPNdR9()kA?Bq?NLQF z#s_*}R=?v%k+w(>DtARJnU*D?F9nsMzT#b=8$gb~!4%-zcrxiGD7AzM)|m3SFhU6$ zkWvd^c6>uH)<&G;4{?C!%f7Zk?hWEDXE(4tg#K8IBpLj~3=!a<^;CI}IkqPNPfVlqe;VTh@K588Dlvt|1 z34uVpCsx5fyhG^cJ~A%F|7ZGZVrlX{q@a5n@*}3KOh6DKtFN8j34ufp+&zx{&v-r( z{(YY_(D-e;adoSaaI0}GkKNH4f#20SM8rPS5WlWg{2ddMb^S4OWyol|)~eP;x7KC3 zdy4$%+50Wsmd6NO3m(lsV_*92I0)HfHIjAOTg8YzYe_$A@p9F#U0$LryXiJAx^wxr zHfB#h=C$zb;<_Nv=*#qVPxj5ws0RKmQ1?gi4&?pqU6dys;R&*^cM5q+<~DdTo!P#G z;CR2w6KtDT+isoJCz;u|(Yt5?iM&_jJqQFS-uf+%YmuRtbHk&^wxTO|YM7T(kHH(M z4DYJ`*x{e|#A4q`MWhgz2Jgtm?#P~FwEN-A6och~Wt z6%`VVCbiW`GNU?TNvEJ(1D#(aY=|5#Rf_lzwNXk6<^pSUbABO+0(tL9tQV~CVi0Ck zKAIdFbqY+Di0>UiV=r7U@<~1RJ|It=5Fa(t7fRVFo3yaD;5=xG|3x+e8|uaXa|sW+ zYCTk?oNky1w0v)}T%Zro0VrD-hBdlXEHN<>cU8m)@ee`7 zgf0U4Z6sJx^=3jLaMC~W4~SFZ0$FAwhpI}y4~PcmZ{1tZyln$z2jh{2yD+J6%ZLha z0&fvtIN}2(#2@5cW@1Uii$YLK0zt@D8d%T`*y4})ve*5|J^8l$M#;0fA?S+Qd`jQ{fD6kB9A(tn)1M403RxAXAYsx6}oF5DTxAc?KPn%~666fWQaLdbWc~REyCwp$hnjY}0sY{K~qacKAG% zp}1uI)b(QfFeV?Z5wfa!{UkjDd{B1)yM_|Ovhy^BC^26r3qS*JkFWI%%9?-~?hA0S zbzD)_N0CB-ffuX63j;U^=3~-Jt6~IHpr>hY$-rg9h$YEGh(nMfHP&nZ1@V7fa`KUi zofJt3k=c@3lSL!0$I)G9c1RvmC`Cej5k&oiBM`v?fFw+8?#Fv1E}~;yOGJ{F!K|U! z;>{5K-}4teGe->GG}Ppz>^kVWDq$jhmd-Q|)Ws2P)_y%amR;iL+Icsn$?(DR;jRoI z$t@_m0{G2KnJ}(_^n_BIx6F#d0<_Ilef%QdB>U!Jr>scPVg@q zKdx-vpB+Fo*$8w0SBCn))>_||uY>O=yCq;|V5X7}*wn}Y7_mHFAV$MgmY;nKH6rJeg7_+8GT1yU4w6~tmZ>jT|f>K?P%`$~$wMybe0QoKGwMD=WEp-+X36(#{& zfC*K_94x41es+QhEesz?0__)ySg^TW=qFaTo>WdoWM!IUPb_1WRYDkw%f^`p+s+gm z#1(#)6dWtAE%6+pMPo&e+RY&~0?UY=RY&Q$vm6tr#;Jz%7*3G96Z(KgDan>?bUXhE z2Z>yW;x*moAtEHM((Ac6Lu_aPb$p592%u{$g-aE)8qE3`MUZMim;s1_YYG$7e;q3< z=ZUTsj!4*s9rA{6esR$Zvk)9Z?Ezd3PGoM-D^MvT_Gh;Tw40`cQ=}BIqNjxZkh)n2 zzAbBqEr7Es+)yVZTQ9C%zVar}B`r9yN1C z%tIZc!b^|nRO_j_a))_ihm~IH4u6-8(*j*Wx_|zo9tAIO!>>ZBD%?)GMN>d?;I|ty zt!8qT2ivY+_x^rpLlS+NyK>3lZz>luR3gW4J3s!4F&@*O<6nw`6<=PkBkv%FUfsiS z7H>an8*V;Mrrq}NaU#9UHQTA4z-z=Cd&xOnV&P$S*?pHq=7Ta*Wyu=IHopXQ4OrXS z`$g4@N7?J*8G>V%bpyWb=%(HV?BIw-l{rHJfV4!S8aH5`stYi^n-Qd2{o%`msSsqb z?fK(3^|tXu&b{~X`S%1N=0Ryc(VbU2gXD`qTH4;p-{pwzU&(7O=pSAo({$)(ke{c4 z351hRbyfUw@5O^%5J>W^?i9pa<)NEWf{^d+d-TmSr2iG%()$YbV*OZ~OU$Prv!{V? z3n$YD50Fmm@#o9WyYD6>)Oir1^LujpQ=^wU(bT{n5Yq&L(0BR4F5oK)&e0u&ej1Et z?PdK@mwMrSRHf529~%?|F}QFaiLyMn9zk4t?{>IImgY%8d&=T+Jdw!I_4imYchs+N z`;pX-AuDPh{|ZIdLz{(BV~E8HgJ4aQCOFo}a1X?LV-Hw+H88?xf#Npp&h^ETsPIH$ zpSYNfh>Q3T?cn?EdvVdlDx|PiWrLyp!jr_|RC9I35bfA;QIL~+3LU|y;!7J#lv?Z? zxngjmA|STgEyIy2$#Dv<$U$CHf))GJ+^Xn&&;F>SV==?NrtUP7CeZloti= z8Y}cJ4mJ>2DAku->0@MO21~L`gBd0eh`VSpU`;J1u32y$(!mUf3zC3(3V}^RmCh>m z;)nx=*-(|pPpTIDkaNrsn|H2km57>ADlUj4!@wP*Ir#STLyO5^5{9`U za_>p%d{eH;mw=OJ7{CUS!qG(Tb%`OfoEw5&&`v9Z;l<_jh$q&_T`NFBniaely*@w?ML;$64o5-tMQ=r)7)n8H2vpcE3=CFan;JtBS{J?u zMLL;uDjfWVuZa{V;cd}9Mi^BezP%?f54s4bi5GH=MCFtZ3I(nJJry|p}M3t&L=T99TaaqY*V^ajx~RVVWq*O40R-%2LQ$?Uu&y2_n>Yk0s6*rk=@s|*g* z02-3JnVl6A_S8qHgm>BlUe$)#ShA|wL~v;+Y_F*h?oHO#-_gP}|Fpk5nFN#NCcURB zSaCza50m*NL`0GKC)`^Wzz)|(!pOpGRz=$p6oKc(M>wyzY7rO&JV_rl;ADLamFVDi z84Ji*LZQ}^?NQNB@w_Lxe}IoI$11_k>T}Sa=1?%IMgW)z#=K^c(r2)Tti|B{6>G;8 zLUKL7M6&jU0`O1rztAnT9QRs2BJdnKWmCr)Ji<(b!QP5I3xwy%A;J-)X)5nunrckC`U0A*s#|!LG2v z9PGGe4Np5ys$nX-fkGYUvlYRCiWsm!biZ)+T74gA=txB5!ib(sN%oCT>|r4oa9Bw# zq{vfH1d%sEyNH}X#Jz$SL^5P{WpPY9?fZczPU)yJ*^i9mmXL{i+^lQO=s1@vfe?1L zwXW)u^ghnhuwFQL39ZyTVL2qE5+bo7mRq&yw=9YnHH6-Goyd3h=z$!lC20eB*PP+%(`b@hEx4o{OE-MY=&`U#QG*3L6h zEEZxsZK!kq1hG(L(g92_k-QbTBUoOM-2&JbU-OQMkBzQMCiPkTamW9SLP-sVxi@MM zB$SP$x&ZMtheCMYCSQW+22%mh=OFWHhyNh&QWghqsG}jo36P-^-bpiT%1Em&N-sDF z)e#y$$&nML-c%glpZd6;;$m4(&T_oJ*9#_cSDoePTGtDxiVxZ;@$@aZ8yGDUViw_Z zT$osl#57lsuDQk{~h}#ELkw799;gpDwLC8@&2O$9ycc z_Q(%^HF%ARPWW4D?dO~SZkRAqIp-U3e`af1k2q{`v-uYHi;%E>=`a)IAo>%nv3@aP zzw&LXjHAu67<3w#@`?BKFmV8uxTx20gN=7-SuKEVN{J1|Slnm$m3Y+jk zdc-+rZ8bikCp2*!l-cpQJ0i)gM1m#R|dE0{_*1!Y;dS=n-8=|;)oMn`}cgatuZhvnes-=aGVDQYlYJBKh zeg~5-;wP07YE<9%jk#LMRGUG~vi%^)cHO{ZIJ!2MQP~ zL2&Fe+)N+69jN+63aEZ)=pN48Z++9fKmcOIjk@y6O|Od0E^?~iIN>nGE*(&+`47DRO1oO{A ze)BVCP;vD0KB%Dohp?{-ih~Kd4G9t~5S;Mg?(UZ0PH=a3cMAk3xVy8sJM0qN-F0za zV1dQu=04o2`}F@aUDf?I^LV;X*EzyNeYprnCeLDLQc|=T$ER6hZXbRj+7^ghC9u<= z;7$^#*gZw|wS*vHBibN+@Kl%&hal%&UfrIO`lOwHxSgR3f|E1CU7oF#zTj?oC zj2pv(ki)=wfe9)0oABNEcZ`06=?n}xf{~hLN-m;YV>C=nvo*n`FoiQaL|X>S(AiHP zn7AaRxZ@E!5x*F4dVey3^nBp@0AIjwFI&Bu!P^sTPvD&%LDF#tes|vWet#~0GvXWn zM+tTOLzIc_*WmZ+7q5CwlG>Ma#`lA>i+;aX9uHbP|N>2L(T zYyDq<7ytaOu5maQ6hwtC{vNyZ&b$0h`~SsJ&kv-qv-jlxE!-jpw%=#p8K2Z5L&33T z@NYfg(KLQ9|5{0l+V^SEBr;;b47-R0lU93EeQI39+N237O}cV*(LKyyb_iF}(X`0htHpRQ(sCxo;)A)h;O? z9t1xI4~V%l5MYJkNRa98a0CvJC4}CDRb5J#CeaDV$$`8pjUoX>`0O%KaNkE`WDrx! z%`w$c`gV~2oG1|%7P^u334JcnnC65xwVZ3gN+jR^tFkkoMn_JDQ0a_=7KWA-i6fLM z6G!k10RqKjGZIRjol^yBI3!7Z3%~awg}~ds>shV{ToJqqA(j&m{=T{Xq*E|E{C2d%j>7Q?wOyiwv2{`!_tR&`2|1cq{AD53 z+*I6DLYVQG98I{&_r{OE|CSzUYN_v~WdBZNPtnr|`$uWr4O|(rFN~4G2U}{Q?fg_x zQMc&(LFS7XA{WRPO)B$VTP|{yx%!vjsl2+J{fa%|nCNqONbs%cS~SCiFp;g0*wL<7 z`9Sai(kB%)buD$Psx17xeWWD%*JF*7kYCfeo$0Jh!opRi2r_zQm`ncsN%5Aom%u4P1~X% z_m6PW(svCHNrzTel#;)gMnXTxKPv~95TSe;W|~t>_38V|9DXEn%dGp!QR6)!;d_G6 zTn3sb>+_Fbcq5@;La`vDa%;v)Yf8-;F@obN4%`B#aDoSYLb~vq>yPx#;@fDiNAQHV zgKFhh9%b*Heck{WvL9>k5{-3OXhv7>92KxS1+*up$jOtoZGyBnwZ)kkb(8q_>cYyh z!P0-YCP@jaKv>~9AwIsEgTJaG{Y}Hr%IWBk2r4Z`sX%pTp-86%Yot0#Yj!$L!Ob)d z9I}UL7q43>KKov4nd+uz*Ldxn-8r+_dW5NtG+|Yw`^0fa42D&8Mc6e8{JDn# zKK)lB)K>0RJOh;2WZLLX8<~U!pI@6krfxK-ZQL=_sqBKAl#Z#ZX>}Hw&C@?AG_4z* zq1>ktP@-n<;@H*>|LkM~Ncth3PE{wACip0#UEVMbkJVAZT|sT2c`eQfIF4%HnrS5L zm9~M&&Rku7ND*Z4fL>^!%&6Qfw7TO~6o=tvvpd{6M%7el-G7%I%DlO^rWCmuiFX-i zpzu^5l@X*qHlP9mnlFu}128Hzg}aE~_K$-y53du=! z6>@aHsXLLbEBp84!wh;ulAX#Ox)-rbj-&C2(q`%jG7$s|&QdsV!(X&&dm8 zsoO|uY@!i!fHfJ^N9UR0%5YLzqS7f==mtHVq5**LbNi2uMaX6dDP9-QCTpJqi@nKlB>)X4M?`QoE6dX{*k0q?x^W{5qzBB?a#f9Rz3EbxiM9 zA{SMJOlHmLvu!B8_Nw*7>6ub21Wbw>FsR*3^z*ZmGEm_~fX*k@zg?1lt+=}GMT5dC zfEV(X1VUh68~DQ!LRUVGMj><^bs?vAXY)Qk{kd;r8ZgVBUZeUdT^X-VAMNW_Nzl`7 z8k|4NMZCA^S80IV&B7cRn3-;g#Rws(c>S5l>mBfYRO%}?IEifjw?nd6h{OZiT)u^! zIgiLRKG|sxj@l%gr&qjM0AWzO+2GFY(=US(XN~xWKE`9V{iJfPfs?~YNjKi4qL&tu z%w~)0b^EP+Lrg#Uo(X4rPghrWdF_%jLPNb`kxR2ZJCW5+`HS^7sj3=1|GT#kXc@-v zX$_X^S(d+K;kn=V(e~1Ecao|u_@QZ2R9bV|o3IYX}KEISkXDS=I@;cR!e_3_M)Epjg;F%7!81R0JSgmNes4 zrJXec2bHFzgFxYgO?5^0i4)HrIJOURI{~S+nBL|XSGcC1qTU$M0t4W%?HCg1ybLFwOQ()f>&{oZR#8%W-H-_eq;4^H!<>YNH zwGF;x==En49c8y6HWNvYhMky(;Rt1=$}zNS>{zD&yUu%SWsGOgv^%k8?ffEuET0#I z{u{XT^VW7sykhcE1f1;8@yYK{;$dTj_kGo#xJsU=Pvq9xzgDz_l;w41wfQW&tIIAm zd2b$6n|-Nx?KillvSgSnxleJ0|9j7A+D6|lvV2lwRsO;N@iQT{*!hl9bkYwM z)rkfuV#AclX})-DneJIzDiDn!24k)BF&KcQynk9gPF@DISn}QNtGg}|yyaPXf?96A zJ&HM#{RJ$3(B`+8P` zWF-E0C|5M;0ja<{8hBq2dG|db!t`0s*Cw8CDGnH3FKqa2jw|2zsRjF2@ZvW_fpwWq z{jKLO4zp5T*iFr@Zr980`K4n$(LUUVi|q)+tUd>5N@wZa0hvvV{wPzIn_nRDK8e?GwB87Dq%lF+C+KfyzatzCq4;Y_GV17PVLb`;v@3KGLEGDtXS-k|ky z%fW_-*E?2bFGWU-V&)GKNVHY=?9(}3_|R(7ve=m(w0Egjs;R9HKA;D3I^`l|ZvX<0 zzp~8`a1QC2Jhs|>`Pue|=YivQ46SpLjF4l~Ll5Rlx+Ah)Wz{T7+mR^le_bg&Y==wNh;Q#={33rZfJ z5Xj8}*v$$U0-C%h9OAR!&CVWHX)IHbr{KJXi`7u}Z#8IX5p$s#&)yfFm*QJM@;XYm z>*sxcdR54a6|)y)K=Go}wngjX4!+U#m>+ytXikuco3MqW^hOl#jnjQj1q!bo)aT#c zv=%dXGU2&MHx39G7@~_>FF=pUO82D;ocXYsH3zOPkDuoMlfnf8$mBsR_`phC0~-FML$4u~b~i8T7{s?*lqxs&l^rywfMDw{%mB?6HF+0w9Wy zYQa(T0oU^eLX2zYZuQTiDKr+u`x^A4r@mZ4;=2JRZsW?sc8Psu&sMSQICVjs;}-T$ zvxk(HzPW}JJ0_M#1H0qO>&&EXGvqC(xw>%HmtgM}b4?!u)A>>0)=}3NB3*sv7C`{0 zb?*}SDgd(0iQ!Vt|7A z@~gA?v6ICj$DuL1*@SUP>BWCKV{~0TmGQic0VWwa&5G5zbt_T4$i_|Sv@3V=$Q^m- z(b>X|C>VLkF2-U@ zt5xOHUQgby#Vy)h(b>YpTuymrNC-W>w?0rtjNlQ%k4CR$qhnlgb-R!X_0#l-1NLv# zVpWVh7ffZSF~$w?t4BPd5{u?Y0q;Ju;mP^*d@m{D-CSZbP(2M-gXC)zv}9h;=O}FTOy!uncw>VrEio*A&okTCF<03AcW;O;p@X zCt4Ei>1nVxC|7!`oZGq^b$C|)deKc^s(Lvdq@RpSCw-8&99tiKrq2t0)lHTz0=#Oq z9DjmFuzKq+N}h6=rwLzHC{A5tHFQ!R;;|Ar36s#j-#&+-e#I3!5ej7G$+~H7%M3=H z;+Yb-msv1`&YYYbxbB4TSPU=|hA%VQJrnY_l1Zd5k}m6%|Jmq~mFtHVL(d}TAvnoPT+>Z{niSzkz7 zJB37VuBxkK!Dd|yBeETBRb$nbcjXJr=jiWjP!*5Mu2t-Od0BG-WlhwN3DSlas#snA z@8a^~p#hl^)nyt3EUUS`(ztT>q@&c!7XCVoLL)OhD^PQ~#WKbVZLtqL1xYB69>p(U0mXy6c{o2(%a<0Gwq{x^0vkrtisxVmCDM zNUBykdB5lmFQwp-`U@YSvsP>>AGXd5f7+{7^Rl2GhIB4B%Z1c=pQM~dlnWx+$JgYz zrYz6qxymK-YQV-GRwbM61H1BIFtZ#ykXQ8v)PA((aFWJLE3nsa zs@)>tI`DO+xAr=cp)M$lts$_{*|kWuoi0?n`V5KgOf#(LdFSl@`xTa8#c# z(xr}jAWl$^UBvui=-8o^zv{SG=jQk}>Xl2y8U*MrC*O>&xgsnanX|^zU`my= zvI^Y-ED9~c7XuxL!y|-ZYO(kwTB)Rwt&O?Y!oq!86_v}&jC544HcCbVVdL^uPbr2$ zlclu4g0n>iZi$cjMaiw%&+|SGcF03wGt+}?_2eEtwg(tjk5)fprDa1& z?@c?0;P8t;5sk$Gt-7K0PJ6&4sWK|WuDfyDpwlnYs0)BDri}QqVMv1N1jBm4bm`(!^h+=zpuPWKwJjVW>gCVKfL1PXT9$4lq z-Zs%Ufe`5cyY+Zq+cs_RDX25;82LF)_w{koh^9;uP$7wgWmh!dWfd3A=#sA)dY&~3)` zmATUeWHLTsW=YBe@NcZFmHUa}`08nWd82(Wv&^kRnt&zwbQ#r~se|re5~<8^uUI<9 zS^bzSdF6@s^nkdF>-0%9nH1Y=aQujO@n%t1Vt7r+(fVS|d!%KMjV5(rI<-3AB;2+uqdd1hR4^1N`#Bw955qn^ zzgg_p16|XUK+`;hhj_3o(0RE3s*H;gtVW;A5gW&Qayr5%r$b$qibaK>zu2b4n15TL zW|mgjxw{C9U4~$E^OxKm-@PHrUpS8KJ^R-678%I8bvD`cC+RLQsC55qTht*9@$3-w znY8?UD3n<#3!P1U>ewFDR51L}ID=~1QY)j_uik&1i_W=*)>rW`-8dS3T(W@_td#Vy zF^4}aZ$~=?d|jIRozrD5zCD2~FsHgad7j4{$KZZi%c3x^YZPZq;Ac7)Up@8F2kYr@ zUn?rf1*0~loQ$+QlIwIR5sP)ptu@Q>7wX2nyNC2b2!^!&Uu+!!zHM3FqkZQOe#o`&}pk91JqIGa@OtyXW3MO49^20+U6!h&9HP5 z2E_z#aMQbz-oDB^QrTTrLm{HqHF#1f?n37QPNDE(^^V(4GaoLsz;E+lV{S{wh_RkE z%=C+qGI1NLxCp*Wga4@AHm8kFh-XcU_OyN1R{K;JHbBDZ!MK&ec~KrBLOM>OaP6?u zOTUvbs99oB1#!4^;%1EBk|+T^Q>Qa6o1z2YZ&xWaOTxFZt&=<$cL{%ba?yA zHgP)W9^EzFYD)5pa6XIkQIOHWbyu_@fqC=hP^mj6f_9UG!pz~s!78GE*~sIYjn>Mx_B8agOP#lNgb|+bg+Fk8(0g zD5b~0xGzt=4GMq4UA^?GHP3OukwPR2cgKN3)?S0X>Fo?Ib^0NW6*t0;27KQ(d288e zFLxsjrh_e)8`Bw9NvaNw?;IUu{$vZ}`n!(B8}~|OBJ;~7gOq=IAhW3O`u=TVdsuT= z&0#j;DTTp-b5w*Igo~aAI5uba6oM17@fa=wH#-gPm7U(RPIE|I0`heC9juPBexDio zO=HvM*^cRwM;X0v6$+f|>dQ7cU488m7PcW>6oyPe(i~Az>iCRzPn=~{`Y)l^p&b|t zU(~m&7}PvOtDmn=+jZ7^&vX~;M}O;0=}q$b!z?xn)srTAky+D`h$r=!*32cSASL## z_5E8hdJOHDZ4<%ny}Z>ge`$YVdNbEc4GUuClT?DFL#-#U&vr^)C`62H9qbewLM_fN z>JRG17UETVc}(`nHz*vSZM9T|XZZmX!^)Stl3UW}8*XNM%b9T~hVh&C207=pm&_bp zdf%BI&Wfx1gfaeGg2wRWGruo%PF5B#NAz8uY&C)-b(xamH@uXrs(p$x;}(FOp``L> zlaBxVd^#m#MfbOcZR-|%n~Q?CO4~f>L)*ri=d^^g4R&enKqzivZw?mFuZs~!{u)Ur zWvy}$Y=EP1SC^COO_tqg+jVVS`#XKO@qzH(;<=S++mv=p0H}le&Gl6pavfPeLok}N z*(N4$5m#r#={L);hwad2ad<-Nwc<}dOOfxNcF%RaS42IXxQeoD6(JW)`Mo*9`pmV5GW>F<0LG@faMAJ!?oMR5AHHPEwkl!V@SW zY7*&$hr*PTj7#+O4PoMLv); z8)U+*#~_y)uTx5EDsrH}HU#7Jf-3X30WaX`AXg>ZMYB}T?l31@O;;~P9}C}>xXkR>K^KQ5o zPG|5+9gR|v)vvW9|+Di z^qM(+Q!+zp!JxpeY;{MR5O@r=huQjeC*S|T1)$Wp049>)&{PPS_dJg*VA61jC6B5j zS-80L8NZ2Y7^Xg#JIN({sTZK$nK{?LeTb9ywpJ@SNgGp)0@n#j4sBM9PQ>{DmrAu? zqZ(VnQ~L){VAvg*s8J@}fDkqdgot=iBQGwA8t2aA!6@{$+;JmY-4pFR?;tp>*)Kd_ zEyG$%x}?;@^7(I6F5MC=Q%!dtxm)3kvz2y1ptcP5Hhfqv3W|VFwVto$_mTM97GJw{ z^wiQ@cpvy(+@m|G9)*TM8e9)~Q8t%m@4JBE`+fs}$7QT=dV!O>&GmCJZt9JeGWKIy zP@vn(6Hi_wfz1u)$332CN)Ja63e%CY*w;Jnsn66> zyrc5?-CWzSrg11neU}#;=Ct7UEGQt5JU;mDax>Y5)7pi3Le2v$zYRlb&)#i2Hnaho^a2EsG z9>Jtw{{X-pei>c%Qop9j?J9N%>Lyh>NH zGtgnS+g_t3NSJ!J$tz}^?`*+FF1vo&K*E4Z$vmcu|3pHndWaT3blj*(#^+g1(?MWJ z4BbWMcT&gI$)wHEx_oo@?YX5_eo0bJTG9Q4>w38=8!Q*Ff$w8dN*S6Cn*|c$Vi|!6y-72PKR@d-M+2} z(m+7Alg~<3wWfih3)AD@!0SV`l>#JQJtE$icA_%gai6=2=D@3pXk;NDhLOL8rA|6e z$t*U7pL;7s2q!xA_<=5C*n&Me`sH>?qk^~FI~x-3qbuviUwDS>>?8jiHihScju*js z7yp_|gZay`VPrwMpVVVspsDtqMHH?(enNX|U!87F3j5l_mj{G$*-lqaD|cohh3>5B zF{kOS2lJ=0HVipWukw5qnd_E>6)G83A^nA&XYbej%;-!zQ<*^D_-4OvLy9bCo1I?% zvCCX)D*3F83L5j&{djd4sVi+S(nMenW|(hmj38xL$<;@bRe{n{iYbJ)$0ZBHB%$!t zW_!2cMTC5C4OxrCOR*hXbTFED{ z#;-SYl?WV^|4_CndOLz&@veLg+5P7(9>m7ZrE;0QJMhhGvsrJ-oE z&1a%Ed<{8v{4U@F<8aKF2xGSNXTjdHK&roQ0jVeU8;gBJhtFUV!`5d%lJe!vL@zcI zQ-YmnUJfr8dBa}M6XveE)m4;8M2Bx?Rft)&EdC)(r~Y1?)1)^n3T2AG-ZQF|`!{dr z!J|P3x0^MrBZ3$kP`i)jwC0)l%d{YG8A1J$;tN zShdTN>2uPgx;2OUMe2t%r5O6RTba`e7l0CyGKv<4>Vx;J-|M14Q&-uTrV|u9q_uUx zgcZkM=>0b4&v*?zg02BJDG(VCC7s17N-Lnh*wJ%$E749*ad(bKw!o2Os=V`@En`$g1DE^D@8*j`*;sA&`8S{h>j!#6u;uuF&^{kELm-MukzU1@~7ai{-d6R3=bXE$zYvfh*vtwfRYROpLKG z1AcQk5;^t5!P@=gwGM7uPE5;#(~@C=zW``ea&9k6%_79)ST}R6d`)b&ny_Hf-F(`c zeB3YnVY<%1*wfyBysN5izflp(J3Up^*^8*gq8^j^fS42rNpvx3hIOx)+J@~sVi=`c zvlNIdtaS!VA?CT!KVq*F3#V$Ia_Jek1QS_@`8IDBx@259 z?u@Q$l37hv=2LjSW#o7+c8oY|t^5~upLU#`wYRjUFw;GL0AABrO2A@3^E2XUb{J~hsZRJm7lH4OC74EO-oLk5_V>rQ?>*HKo&r~Eu(X3? zuQUC&-3HmcGAT#>$j56r&)S<#9UX*BDF?ifl|UEHGdO< z1Ik0UwZ5(7!+hKu$#MVnqsvU=1anqjw|5b;(x1GJpyp`#v3Z~5ewO`BeR(#)^@beR z51GQkr{`bCkt_mIWkPN$u^qDItCR`atjfY5e0WQ;%(X7$UQG6}IbI7^eC0kOJXYpj($|gNj_RxcTSQOB^{(1FN{PFSyah=_>61FnQC5!u)&yOx=;duvL-HSuYznVHU2s67Gbazzk z0Q5Tk8!aXi=P{B8Myye?!}z?Z0{TRwPQwsn$BfU*rG!l`dFR3}p#&;^M`2zT%@q%?>Lm?^hhqfPQ|o z3e{ld-DlSHMU7au|JFt69Pazu_gBzP$dC=k*`wYxDZdB4Xrcb4KY|_0B~JmJrtx+d+aN0vg-K&*Ft9A--i9yNNB>UH&`_=a>2G_s zV89<&21T4<2XE7omx_M2IS91ydn4Hoadf^k8^Dt3*Ocbr-Tx|Y+dW(F`yxgYQ5%zK zS8Mgj19!_TlUJI`4Dq9G_+#omck&sQ818U>e{Rpn6Ubbb3+R^hY}#6iid8E^sC)1o zIX+kSscV~rSIs@^tU-Qjs{;u%drEnET~T547u0+eh}XhoYMXwS6Y$ScZ(1QC z5nk#WMi~DQ*{MH5-A8bEry;h3h*tNtf^m5XH`=g)w47n1Hj1@Z&$I!Sj=4ZtiE!1` zFTSRpEOUW=`?H^tiB=-kCjHZ?W4?3%6YbZ-aZ@lHz4%kCD$_fM-;bYRH~Bag%-l1$ z)1F$9H{Bt^V3i0o@UOeai6(Vvlbtr=Wi8rm#pJnC+`KwrlGwi~M{PH;&%4;TY%z7( z)!{vL#oKeh(fNj5ierHmuUG{&OAI^ zS3<(J`hbx8sS{kgBD% z%org?WU33r=rasdy~E0U8K;N#<0d_MbL{mF)mgDt8XbPn42rBRJpR7wUgq#8`eI4Y z`um~}_Au-eD3rI5=V#Ccshmox{?<~%dN{JhDATQ<^i-gNN|X}yWLxjqV3+*Y4Ipo^ z6h3t(Zp1p(9bAE4wYlI!MlEaW_u5XQrdQF?DE213sO3Zs@pnUP-!Q$U`lQy}NRavA z%A{>zF(dtyb$@d%kE?WE-9gzAx^f}}94-R^HG&X#7Ip}@mXw;iTYPM@qT{RhT7NQ z?~zsotwS}aQqSo|JaU^)fp2r}AmV(YI$~|m_aD}~=zYO887E4qEG+bzM?4V~Bw$RH zPAca$k#=>RqN8{79<9x4$XW!2zC} znnV_AR$jwJ7+|Df2O_~1MWMpJTc-FitkmjEOZ?^{&Ex3q6$@vrl9K(-tdCDqZAlCJ zUEoHRG%FQRVXwEDH$@#fk9S7p>b$9Nj@>QVD1e2&<^G!Rw;yVn zJK~-@Y7DMU@cb(CzHK$v>d(gQwFvd#zn5NxM@!Fb>Bs^6{PIpP{`9F1Y9>Ef=Xm4h z$7F`Og&Mi|Q>@Q^>=G8&Ip+@wqH1&TIky^CA{3^zISCwB$}*b18oUv%wvXu?r*KaE zaHv6nJ9}oy8yq56Iqt+~m+b=2(Ay#*V1LTmWC^>Q6%jboS<13!>nuiak;7L{>9t)M z7j5Am_4~MZwCK7}5fygLC}?=kyg$zl@$jmx)Vb@dJ9sI(Q3762e~|<>G5jLpw3WOQ z6?i&@LjQ^kugY>7DL@u#QH+t!w~q6# zWv2*wtER;%{K$iD??->0ol#M9R+5dTEec6sN6RIntfs z2i9<;XB}~C)VMj17tbEZo)xTVzPdh0$0y2^-=iJ4^z*9ix2XEWu+VKqPjj@oi}9@p z+(H8;a}QYK?FHik_E~}+v&|K^h>8IyTIX6Lfq837%{V@ML)V*K(~mSNs}Mm$#A;RK zpl8cehhN{VT4!KY2!X??cX;+P0!37y>(fOWQ1<=?6)_d`OrRCVt8?u6`ZIlENwe}g zF1J+9tA2)iOtIo3#2u2saP{}xZLu32`Zl5&*O0}9)fHhycDegZL&E(fgGcI_I|AwLoQ1m zZ4^QXw5XENQm^vMk{YyBc40gl!&)w^aUT;VMcXZ; zrg-A+bzl4CGVNhVwV^NcREbq)&pA_h?C?+fCNclLBfaKFzhBbjPtFy4?DL_a1RhCO z`ae9)z#%yj0^msTW!^h=+tcT_m=mIK^OT>cT}?-Mnz8icSCo(&tPB$Of*&66*9C6i z(rEIp&VJNWVicw`?GgWHrY=cjUUVR7yn?KTYI%EjK1}DaO5YUUe=fap7bosL_}`86 z{q~otzaC14T7QqfM}QXkq+@$-!mrZm+1~k0wv6W~Jny$nF-rO(%vU~}YV&#>7u|Wm zt90L>W2d}hagkbD-%tod+y3V5#XX%IR0w1U{Y39(*OAF~Ez2{Z3Jbpl_;-i+^FAZ@ zC*+0Eew_PcCUUgy)0vALH4Py_eF-V<{a*U9c_j3Hu^x1TyZ9x*aOqVaU%6+wW+;2& z))kOlax=6%_4sUo-5B6fbX0pzu#^z<*I?V-a;a|PlwB*-CA38Hc$F#<%b$6XD#!PaYvXiT6tAdTOd?aEgP}^lB@2QlRsA)Y4KFqnb9&YZ&8^i-2N6#%tVXk@}KrQ66Lb#8~l)WT?zuo0#8vPFurYaxNT)SK+dm76ZMF zL#-8xQfb!wEK!PLJ8XQ>pZgm22kb|m2YLaI`Bi0#D3x}h0jGcaFHzVuJX6Cxf*cm( z<>7gILJ@LjO80-Fi}=Zn<9`trhmiI?ae^pGp6<9LWhTzQL5af^n8dG^>|7;M`-kq4Y5{ILGf+M0Rg!@2Fb zmeI@C?wojEj3pc~`Ow0xFRZI%K-&(_8Wo~@MOplklHYcm>DBZk66Q7h4J~+39H?3P zTSD8pd_dSR_b0^WG6CcVQOobYAWpe-1r?I?7=L{+BVVr56VX($-RTqXEKhDpFeMN4 zwsF(-IhNzx65BkR;* z^0W@@v#O!ub-gMQ;-Yj!ZNXXow-`$XCog(@DsP$nTgR_hBCC~mU+ul|%kpL|qXNTt zFf^AaJ$7R^9((e0sQb!{lVa>tCZmnD`@W&O)HCi$lqRjGb?c${M~0COd`UY+ljz9V zzoMIP-|uLUilzO0z_P54R2Ka5_FvJ61-gkf$s&5~m^67UOy)UCRWzO3hxO;Yv04(z zUzg=cpsrkHm+ow>oFDs&C z4>vp2pXS>0HM?T9b9}@R|HjvLJ5V@h7U;aa7ixP)YTYwE~{g-k4$BCLW>QEAv8`N}O_lqyFAl<8+QgBN`4aR<}g5lyGhO zD9F-T8u_6&l{Ld7k|gktV0qc<9elKtNtjz7;D=^&Je?Ju&@6O4`BDw}13A024DsI= z31mDT1k&OSz{9fxAUvoyBPsTn^e`TK*r+DiOy&0t_eui!;!Y!l;<=Jhy?Dn4_`*Qv z;>%Xf<5fw(wRO1CfZ&}v4_S2>fV8ikhn!&b7NCTIs(p?~daeC;)XFT$SrDaEbC^tQ z#}84DvR?j-oLD5DqvJQpfUfvn=QPEhZBw>jBY&bC_=?C_Sf;)rBr*Ua&>OxZFmWO{ zUh*fQS%vqby)U!=!Y=tAHFN7pvwR&x8XBtl0?lZe9|7TK8Fk|SxpAM+jSkfDw&Js# zml-C}&f?xhYK07?!)c^(ZuyziE^S2P?od)8fW>llfgo_4`7|NHy%CvfY1KWFvwy@LFL2#IL?o=nKVJ|{y$ zpwtar5%T7Sei!cca<=622&NmAnL#eIT^SvUt~xxCKJjhWgG9ljn21p8)mJHH|ytHla(fPK_)(Rh@p zBpEc~nWK6+F3`ZE7-hO87C}P+%>Rhb(M(m%DqP6peo?~1HaC3s&<KD$N)47H4@LzuX2I{K zzoOBjz^#AC$$m9^*sx->9h1Njqx_bIy)&{o_z@o2yA8B8%=>XYL4rb`W2{5;Ygxg; z!PoF?pf}b&kqE=>1RIb3M2^2L*C-#;Y=1Djju+bDNt*70304W{?=96W*_GBpGpMLy zx3GYy&g(yc>);=IreygtV5!h`wf5HePC{w>zV^HXR;s5*#>gW<+#ex2Plfg`-*mGV zG0x}X_|bj9oEBnG%vpGD<-}U9Or8+Vby|9#FVr%#nvMOWK?M_GAWxL6;v|71p>o$& z>lwpS6^Wi+*=(HNfmzHX_|?y5J6Ph)?zKLJ-gFf#BNKauQ3>~iP@%BzSkokyxYt{< zCjQ@#Run;zp-uP?V;Ey5eQGrA!K)QJI$!q38Ne}lo<=i8wulbGfa3h)o}5ke?8Qg2 z5NrbzR2^N`koaK(m`76h>ay8@ihwxE`(V#7GK{|aqxgTI*wA#Y|IWRIjzKagMYM^9sv8S)`%bgpg z_e)pehnJrGcYoWMU$T)lS&8<3`$CwWNW22UqU$fO)e1S`!%Ky+Pg4tlb6q9fO0o*j zh8obWd3EaFKYAMz+(XN!TkpM}@B8jEzTq~7XOzj;H|=x^Z^$2QQ0)e)4!qOFalXlh zq`=8ru!drM{p@P;jXvKj92+k-1;=j$NOB3sP*9ku89WdBKLA%isJ}m0z;BW&SnH}HhAMLM zuMz?;$#^*YM$LS=GF$3 z8>W!U8Er{6X;0E0?R!BmbGQ#vZ^~#H)O)fL-gUe;a^DE>zMyp&Fbwc|&?6W8Ml5``EhjPhIXA5cn)!;(Ug<> zUYeSoT1&B7-BZLE$tL?h2pYu?865HBz#3S&GbK`yis~h-k<@Am8g0sZXgs4MGg6?w zjQhi}Gt>h)$Hd{{5nMgTgB$DdE|qdMrK;Xrg0fF@0qg+a&Q@<2sJrujsZ^6Nu3}jh|!i;29UF=H@Sg8nULe?~btrS*>pJjFuj z2Z+!K^25q-w!cNQ*L=%Aqhy!~Iy_phcW=UB{b0Z|FT!cPv^btN`vugY# z*Y+5`#+Uw|AJfF0d*JJ^4^{pyA>F7*3J9wULrh%!kFkbtAJhMcErox?SPJBK+Ol6@Mg(`dg#IYumK-$& z2k~v!FdoTqmO&m|Kn9?(87Bbb#r#SE917$2I01t`lP*BvJb$i4SULyeT*RBil?3?) z)f_s1B=h~2ala@Qe+8UCAOlE$U#B>`FaMh_K?OOoB%k(3T_jZ|)El5hW zCI8M$5cjgTv0_3Wz~8jl!~^YWGXl&f4OhUzvjHn9$#fzm98MdsPMdw`-{%h^r~+6sSk1DR za7H`drk_>AR24~Cgoh+0u}}+^|N585E>ZkTRvIdBnP|Pd?`^EPCaXsxlwNQBujpvS zTpX;B^Y_@^_(s`9oSB!dPQD6`;^IM8EWNW#V<$c=E+WvU!QYRZ=ZI68*|}!NRUAXC zMhMrHlPlo5kjf1ApbZa3SX;r1_0j*wcu?;1uvqXP=@PM~#vmJ%0e;8RKo*?sU{Ah& z-a(R_W~{>3Nh`J(fn-D-WF(1y4#dF~8VQaZ4Sy-*Kdu$Tsz2M7VUXpfcP zKbf7-Of6-Is3i4TU~}-$BD+obMC=L1TwwP~q`dOpLsNPBqKe~Z>sj}VW|SaM9RVEWG_iEN5&RQP=P|9e)_5+T`h zz84(%h z8IKbO%4bg*t!e49&aBY=tw&9a82{ zD<4Wcm~Tqn2vfEzB+o)ewXczgLsP4Yd5^#kE8b9HMWGW=!jo7Y9>h;{J+=#-8#4ej z4eOo5WTcJ1HICv>eva4B8bs{`p&q5;MigTQACXq`?G+zw&kX9yPnhGk)Y1frVQ17% zf|==0a`;&>(wpG}JZbLrX@0BOCVxVsC%Y_c(&4_2Jq*YXIbsMYli8BA0?H9L9dAr$ z8rJ=4_%kYifr!E@EK2n`CleIpFzR+(J#MFu2yQADkOS!9$uB71_-q_|771)uMTz_x z%uFV|=)ewq4+NG8{k<9TNVGz@LARLA529+4NI*TO;nJJ)Pe)o+rr3Us@Kd-ATliO1 z->{_J5SJqL`a=`PAR8xJpaq(IL2NL>En9R~vxuK^;tu^ed(w~fty1T58G;0l&|+vA zl8y0Exd=n%B>7=fJHFr4!I$fi;x1!RpszTO{4y#NA<5L&tuKRMU42q zC8I zf6TINst0PZJl*I|B9h+SIIGeIdUz@U?~Y#aa~R>GHF{YHmk z{a1xvsu7#DS>yVgk1yIi`g1uv?!6o4L5X|;Z9^e%7Xqw0o-tiWP*;GhWcvhaJ9?95 z!Ri#)woIpxs4;q`>>kY@f>?wUI{kMtWF&O|tKkU#T*Kd_X4uJ1Yo*U69tvkku0d9N z5`$?wUvO0h9e#xl3R-R(-pl7yYGMLpIukWI25!6)Xx&4g3hPH|tkLu+R64ER{NzIH zOAR<-JlO3x6%x$Su`&v@C^~65qJ0ju8N7f6iUKCpjWwh#dK3dAvX~O#yN&!jFleYW z*vx*cWvQgLU=}ixY@DPp>~M}IMtY1D&Q({h`7Nk?2$gA(Lbwz=6Ew3yUr$}e}~xQ2a@ z$CcEabydIgtleFjFHa&W)dQ9%+bk!LGTj7kEfixeW75^|+R{{Y*;Xghk5?cFR4_%2 zJ6W?OlCS633614(j^I@G9zplxv=W>*#O^-5318cB2S>CUnD8jK^1cC^EK8uHsD3{1iaZoMP1l6-<)Z!P{L=Y14UXCB}{TYNg<*$~j`ujAB z*&d&h{fl#bnZ{e~h23)p8Q&`jjg6e*>J8FN{vr_U-iB;|)P<~h8fhRx!6m}G0TOv~gR` z#{+CU*DvZ4E8AQSE0lk-x0>P z%1%f(2Nk!A3Olu2=WfJfwi)Ks2+nw!z@xcJx~bvEaIFmc<4-QU?qbJ1Cbo1;{1nea zoOV{Os>Il8o&!im1af4Rvs%Bojf=*CeG&0{iDru z-9@DKoh6S$zAqHtXERG7V-ws84WIiF5%E5>#qF+K2aY~C0fI+l8Y{Bk+z&lWq@m!q z9ffGC!dMOaLcx$bl<{Yqqfgw1G3ACoOP}+KWE6aIx;?^CDXcp+HMQI=Mw7Gp&UpFN zLiz=#xeg`^-p`7>sw`SAMQjAi{Z}!$4hdEUMJEa6Ia$EqHaLpGYZ2QY^lp*p9(nK= z1+L$%G%ZbkNg}s%QbFE(#_9H{3qfH&$($cf!4YawL8@CpS-@S_*uD(*dJUUYBG;+ctn{_`=2uz|Hy#(16>d#1bwfGJD$M6M6z)1V7(ln@e^@U*8B_Gf|3n1> zu`G(GOBd$X%0>LsW7^_hH_+J{2=F`6(xX$z=jZU;V~g(8Qn(*?@Nq7$a+sSYr?xz- z)Igf~B^9=*?4%$1##X9KlNwVUME+{^7tR`D2`xX6rVualVM*H|i*+A^t5U{(D*dWY z#nB7gVkttQg3W}^LAt1NbWEZLJAt?1&Qnk(|4bAQVb$$SFnhjUh}>Y80|&7Ps*`WT z&-Q^>%=q_&1;qNVjASlO6Ns_^{}hrvUJ#&jse=caeKC&#I^r#kKW_2eS3jWN{7RH& z?t4UJvscf1z#UneI<}go*!j0?2(h=Wojx)8cZmQyw|2>WnMmHvBxm;S>tzbsB#D-} zz`V`HT;!cDznA88ukNc9W4gPBGD@MpVZV!tgS#bM%wN6H!Nx@KGrMD-)LGU@lT{be z^zs`!w${!OF`bRetSsD&up4J@`f8`6PkMG(svPkcx^iu`GJ4bn)tHD(80X@vUOiItl$_jkUej}Fy>ipM{ZM8n~7 z*5O`$Ja)o=p!9H+>k2zn>lqHcYKaX_UickUk6f8_7_{OlT@X~+8%}JwA9>sDcxz|Q zUs>O*%%-01eW&ohccx=!o^@#UQpt=bs-jUpGAz$;P3|j&+7{KTEi`RWWv1Oz8X4<5 z(R=5b$mcohwT?Z|Fi%NONC%nMIS13!p*D}#W&dX3Pqk<)hxeV}Bz^-Ac-(6}iI zzUSFF(uQgD9sd=yCRe(im~VDRU8j4{v|*Ko{$A-!r)6E8FY- zpREeLd2&}%BppL|7W})h?>Hkns3T`Qianxm=Lmf@eKk+lP!OxugvSN*nUvZ>Z8%h5 z*Q=|3*OM#i!`APR{VD`E_b@L)EtT&%6Q1T;Kj zOQ6B*JtnK{!D(rS{k9>6_N&T?koi~J`^@13TnqZ^y{&Q1{O|1V>TCX80xc&w2lO7K z2Czma&obntk=@HEnONkLHe1E4bZ6YPPrLc>N)w!&GUoaeU*PgQ@QH@d}fopEa2#%=jNa$6r@2(YPezAtNwJYk&JJ30O z&+E|WZEAb;j7Xyyyr7twcpGGKJ(!qkhVarol`xC1azak~Ti(eVnFi+aFXYm9=;piL z7GW1yRC9wiq`>%pU@;kV{`|iua>H<_|q`Ciqw6gn45^Znx(ve+^M@%G-_^dxo1T#Rp-;sP;f!B?V zjgeA3;J3@O)-})_PR$JBFAb(6@B0b6e{&;rb$u&(*7cvLgC8I`e$+qz6?IU<4m+!j z95TZc((TXN`L-Sgqg_f48|Rq1E#yj-qmD8_^bX@cBK}nHm~|Jp5-iL+r*&EUT@V{S zmLGK_r}?bUDuy35mS1%L4ZCXJAZG<1cDLM(e=Z^f-u=A^W!JL1U_l%*~EZxj1F2>Oof~S0Y0&c_#$$M zivIl2czRW{=b&PA^imVbEc{aVk@FU9BRJC?V_XaST?1N14yp1bvTs@m%kiB+OSK}r z9ZZATP>+=eX(?@EJg;wINRw>;PVCiO##c%ysVzwJW4$T5{I|$MjOc|G8Iziot5A9< z6xo2T8%fs%j=9u6`-daYuv+hR+kN@NbCfFiBG*G{c&kEY=DVneBQ zNqm=|d5X|_u*?mHGYAEBCB{NLiHPUAu)~UeuZalcJoLXAzdz2S{qq$4(ERm*2sN2R zie$it%{!R(gm}C#RXyHzK(Xisp!3ZRaYcP(^%b_A9FnV#&raDEi>0;8esCn5KkXa# zK zwv##0c*MmgD?2hU3QBWRxh7(DrlgYFYEw$EmTmaElYCOwz69$sV!vv^(aXzE?)UKB z-TIhGTBN1~*sM@L0Tg5kWe%^{XP>sVy*i zgMIy=;`9K1Q1x4$PF4%Z15F_Y&^q*AdCTJjS^UrF=cn3&2Q-ZU)%U_SpR@mkpN$@s z|8wpCUu4d*NBr{f{hW1*0p2|oN`Y64Awbul>Z=#~{^-y@_W!b50o3vT?c(0b(Nci0 zw+{Y`T>*&dv3|>Q`z(VJe7d+%3Cv2r|M*9AlI6p;;%}Ce6AW~Aw|>cW16l@pb*lhb zZr4ZN{fCT$zo-5Y;UyFto$>LO+k@sQNlEIFmgGQcuDN{Ne4K=#3HVw;*xSAw$Wfljd!vx%GT$+e=N1nIkf)nL;`Ol2jgOzU5?>=6)B@qBC@VGle|11e>t5bTcu3 zQV&xwcliEf%C=yeO+UaqW77#Is+D~MpC!_6kn&@qc!(|`c@dpHNO{JHj{q@fUJM!$ z${dU@x$*B0Vhn|hCWnI&8xB2haFFxwDb-liW+z z{VA04y0l~3+Rl_xHsVwU`r>q+3_DM%0cIn$oD76d{Q=I!3ll{~46-`v;!KO#f6RXf zdm?JCtK{=lzj>AAvdh ztM-&qx=EBvgC30CI!^mY4s1%)ZKU>cSpXhAcn)!LY3v&aM-lV*tUFPN9j+M>zUlmk zog2AHewY0xYY{IdE7%T{usN|WeSEL^(dK&JR1^Cl8=^t!rKC~Ia>M%Qp!rCN2HcFU z_DsDErMcDgJKv09WT85mXT6n{($II&Hc z&BP?xm7Dw!rkKeaNxcY$%zrXBvShif+fh+a zh;Xs*acVG;X|pCwOoXe6a3x_ZlN)noF*|)IT!l7NF)Id_9?CAYlb7DK zsV_<{VET=HEYQxrKs94+dYwW8zO&=%>V%XVpSn^T+}(W3++=4qAFmF!g}15rAn_eL zdfhhnY8Gi-H(e4_+!iZ+q|0+Ugq$Jen(1*|hewK#}-^1s^}T0ArBS2s=j zJZU>P?26gK2%8KZM73Z8UL%4lV0#QVO>DkRqF1d+t$}OTQZwt{+DRx*8;?>$4{ft& zBo}i0xiBprcewDwU8EEEdnA!DgDP2i~U5tT;~P40t^s$;MdA6zGuD zqPH04QV83$P7=7(5@yKEU@g?@lp#jf^Vi2jomV)ep`5Zy=I7kVoiof0MU;3{rX`or zD~G;gRzR)R26I^iFkluLCG{W>Vd8HLpq6m) zF+l7`ZzP7o$zb7Lnn3Z)RA+yG7_|MYUQM@C>a(nN=CG)7{o|Y6(>FJIZs0<~7e#sE zUWu-F+QIkg^Us^e^pq`Bvmj?t6vTo|R>A?bvo2~L2d`6!OYFpUHg%Ay`Yey@Jo>C?2h_07;TKk1%TNdVlYAe4)n%R1q*o`5vhiNCx!tHFScrrfWc<<7l3`wKid;tSPyr=G`g?q76MW1ERe*yLmW)qnnTE_@CaDQT zpHZ3p-1}wwYZzFk+ND1?nt3$n0mZx#^ATbWfGIVmjDX*d>iT|u^AqyBCNN1#TeF$( z4d-|To6EovcsHQ%i`{-Rhl=1-fMo7KD9S2Z93mc8sQ0B$E7#O%Fy4Uh9LJbG8Iu-) z385NuSJk{6Ts+nZr{3`tjn4CH>hjOB(lWEfSi^uh-^ION`2zDa&Q#=DVM7Sbgoyul z1ekljtNU1SaZO)P6@)hD9VuLe8`|`e_^<&X40}m=MdN+&enE`u<*_+L(5{x4;$9XK z65s)rJkLZ$eAT9Z!n^2m#Z8aN^`(dtpKK!C3!2A8lMY~pchYklWC9HC5{h}dQKXNE zI?i!GVpnj5q|2VoZ%%=V-`-Fj=Kv)hV<*`7B>{9jgsif>LLWql9&Cy+eKr>*T9Y~k z>cgos%+N&Kw7IP{tdWmM{H*EL4wDn8;+FLzYD}LssjfI&y1$;rgtB!l1r1^67TMc3 zGnGgIAg21t>|oU7y3`+bN%_syC5UP z+@T13#BIyBZ=Wy%9Y*3p;64LoE1h6oxaP+aC4ea|*puDphH&@(nL2m|sR;%(y$ao7 zn<2@broDJq5psH{lD3ZIQ}n^a^m(|E28$_6UA7?~-uZh3J*o@hgz4!&Z&rtvvl{NJ zO8gsvmGu?%3-a{E0%LMRB9Xx1NpVBj+npMII(|RPh3vV`t|yotx@aSQV}~^qs>LJj z%e1zXn1%|Bi5}xyi;?s(9R$0Nd5#9RDBlU}{wvv~3xec%y~k+!oH?J4TT0VMdfvxJ zx#S9Yv%;(CiL)R9rH#E4t|5UCnS0@xBIRj50A(|{7#_)R=iT2%Ee>c^I|P`HXuVGK zC-_{Luel-#+S7(0xz(AjxMZ^1rO#X@6FQ9qcH_^(>~nh@Z83RGJ9E^@qx)>L%%&OWX_!&+~_(ZZ&GVc(DKXbD2@lOYeDN$i9e=l zM{S^IBX-jYVlhMKpCy&tb5*@Y$u4n-{){8E0{g5RB6iVMs$h7M+&^gn3+>39ffn?s zNtrZ!TZEsQ;>^o?gBXMS*97*r>ig`TMSb7|8o5poX}yz@j*U0b19~g<>GJ#54zf9Y z_-aJG_*xdXxU<_{VfL31{Fm+9G+rw+YiqW?gJR|4Gn*H-=GiTigv~Cp(|l&!GDHnc z>Ro33oY{@C$R9uZ&FRR~eEMb>IZs`;Y5baJ2)H-*@%%aSYFiW>=<&9AQC3eHlL-f% zn^$^|C$%%^l~4QDa~M6F?JpJ8T|m3utsMnBK%K+Ve}1{eZva+8RXgy2gTACadmcqnWSWs8$_KCle_mA3aN#xG zx!1RG9|9?i68OK~dq!9q*TG3>%u7Uzi$s+zKZZP9niUc;!jv(7A~xyH4)qOq>pJ)d zxasH)WI#Y+!&*>tBuONUb`N5l|F9TND5PCvaVkeBMSt2Ga2?Xr?Ht2ee#Rz1SmD5D zc48v3nGc+)3M$ZC3=|nAmbLSWlI097_l*ip${Km9;S{=pi$nQ9aLsK>I0}3TaV(-4 zE}k_0hJQd-$;x`)_h=xHO@^QA@8%``BU zP94+kBR$zf^B}Rb#(Z2TQV@MWBeEM~fa>Sk=0%06h_U#=$btV|h?91I?;SrTdQ%m( z7^(RpRiWRad$Dda`O@1@m;vIqatB^^@qew#u{h0Hx2)%HnYg)_m??EA8>*Ci6&XtK zA!TB3;y-t$2?DIK)znxUst7SdcPng^cAc;ExJS)K?>*R83cqC4ASJw72TWT~mr-B1 znkmzje_|Ci_&1=Wx)r341b@q%!N_jQQf6Vs>8b1bHukrhTAh`O=?M0qtwHeFUhw z2dqDP@qoJjC;Vu=1MIs#YQuu3@&k_4LV+=Uz@gf~21Hyn+j{v3kmbr8`p<=2lai6~ zzOb!9K4{Po>TC9`2nG!7{%7Dgq9JVd;Xm?tnDYaVN-ZSKx+o}xd(qn5&hh$vXituq@Gmm_cNTUq6Z{na z3nb>3?_R9LYe2Mbv@vO^o}@)M9G-XaH<2tTxGzf1l5MbdF+5A|mo8G|Z^uQ~iJuW_ zv>vOVfRIJ_``F+^uXom(Gzx3(A;%7iC+2q<{2=sdjs?Y0+@;27YkOt>A8hh|j<`YH zQwclY6L$jy>$^4zn|34s&b0C3)_H%OO~$QmtX1-6M?%K>hMg@adU`N+Ih1M#yI=(3 z*Y+5CO)YSFoGov(*sa*G(*q$9w2PmpC|e2mfeHS>2M`}LpZq-Da7^&oXs|65~)J%9?Zp0~CV!fGUR2REJU33JU8e=%C$HLV9aCgBMw zP9j(n?^nCIWpVUtqvJ=pddxSW7~B*QFvok1sY$X8T^DhWQ3Zbwi>Y!nYPL70Z9IW( zP2&+EcDqL69CC~AN8mi)8|m003hk9e8VT_aciXflei3@nc0w6w4~q%&u9+qZ$$CRE z*9d!jgeJNC71tb)7A8zym4NZU%~g_mIpof&*CIuV!p*TiojNHZF5vR*QFBq)wx?Zi z+N#ZMF__vja*CZ|jpP9r&x)NQ6TEOx-QL~);r<~sKZSQ@Z|rWoSoo2*oY*Jzr@ zf%=59^DY%uD|ktK0m5D7&kMx&-pV^>uLocwlL>HGddZ9OVtchxDNc0|>5$5ppTm#d z8q4xH{H)~swf<8nNElmr&le+peRnBo6sStC7We?BYt)uO9yPH*cY0X>sEp^+!wCsB zgqic!p_W;yyTibqHEl3>}bleAWV;+q-q4#53 z_2;~cV;}eD`TafRR1i46><#(ku08J624!i0KA=fldMc>n0Dq?Ux)(ruN<|7p4-cqJ z`WtK~HGry|ff*g(QfK4euC~s0<$twaszz;HCNb|e9Um=QS}@rnu;ZwH$~O3(@64N8 zXk}^5Fu*a-Y&g$Mrq_)(H|~iSEC)G5SB=*JBoFPWacYWrmM5BfV~?Ta=BBqmR0FWN zvGgh2+GYd9g$h6&0fl!cX2t%0zAE-gN(}SHNaCsYMl>~$Lo|>%(U0KTpX8CDmmC$r zEt4?>Qdvo70^*Rr<6*w2*^Y{@qfZzrix$aG;G^XV^dJ?uFC8{172F%9G$ojEYvLoU zj0Ckb7}_!XH^x0lJqQmhiY^-DTypz09s|=Bm-sf3pS>M_JCr7YC{I)1R)-tn^fWjD zf3&WF;#Tmrd;V_dCl2&vdJ^c6IK8NKdOUMa7}&pb=Y4?A^}iHnoO15Qy2o({#23bC z?W5$M{75sz?S}C2@iDaOTKYEbhPe~i*9i%GUWzSl)Z8U@31PzAfgF~8{|Bx;>T{?Y z$4gd~l??lYsQ}C0BSWQ_AIH;)Kw@u391(%p!|{}Qb)N&ADZHEFo~!|yqADbtaop6f0tTp;^75&J>#KO^F2xF=eBm-f4Q5RC- z@X#TYp9S+rf$tTPOmUd;d9ozzKBoEA_R$E(KpVm%94)^272BSz{ZDF~hCYNO`4ils z?0G0Mi{oIGqhrzUsIG+5krRji4)^bNzm+WD_a%~`)_2BqQ`C{~IvTRw)7oldy9LW%L&Q*Eg|!oMQ#-=N#!} zHd$b9*4)p=gVj}`J>KzyF|E;NK+iB$L=(P=jC%0 z?Ysz=eBn6>9Jy|*zGxPe8Sj~q?bUFD&v9Vc8_n_l!a0AcN$751JYK3*P${Oq$H6^{ zfIh=cUIIEct;CbM;65#_L3sC(8zvR$22C8(z0fY3o5cY7`~=arIaqR(IfGJm@<((5 zn5%~i%yC>(^-ml*6Nm6+NFGwYNlAV5_nn$iC%ObVRQceLS?0|6qm7@#dhc!eU}wnY zInH)|Dbfh{q+F#*Cop~oo#X(ydUr9gRSjQ8Mx-@42Y0K}2+U1Ls-QbHN!O63L!U|B zxyUvObkX@-)VM|n#w&O`t59=MLc&8nN_x`fg=CCN-JH6#w@77~Lw|TTGINR0x^!|X zePa2qKFiKQ(o*(7+i5ZwENwV(p@^dKMFbB=_8ftRQx$Ra0P(d*9qr?{v?Fx1ro_96&uML zs);1x&4;SVMnfbVEO7l$q$m!Su#WQ~u`cMk-tS1SB3kfj( z_`5OCh@jzjJD(6A9Mb7go2QAaZf=Z0abI$u_^Z+$eLXLkgd6u3rK@9ud&Bp-dyep{ zNal>c=Ea_YVOx+>pwOk&9Z%w$9MSzobR}>s+~8)0t1ww>*VnwLf-0NIgplVYD`6)tovuS*l9= zi0t3-XS;bFsVAqmz^A9<m zI^`~rBiZnzDY-CAD7l=;!IvMof=i$ICB1KU`)<>qF*C5 z_q;}p1Hxx-7I-&3e;tg(Mx3dr1rhw#is(L?)hkI?tx=%u66K=ZN|&)!CSOdI`~9mW z2E&8Lj7amRd5*4*htjx67cEki%5wFf0BSV${jx zqGfTeQLUiInK;c|uDgtu7P!8^L-5i0TwDU-++yEHya3^QAPfjwk7YP`ERRA#x-g}W z@S}fu5xkKA2{pnRN~oZ9MIk|YB#H2DC`V4hm9VZWZg4NcIXnTN7?I}R8^0_{QlUH- z=jqx-8;$h$Q*7R6+4kjgNc+s?xVA&+g}<~+xiqSjxOi|Ew0RFiBR1OWXD@i$R4R&Uj}wQdpNhzHsi z^a^*G7;&x*vdBH}(XU2#-2^&fi_Kxq>{I)mAtoOMYNjKT_AWV)eNZ=Xf8*5kn5b^w z$gj-&n-#Js&_=7hzzNjrEih5DMpnMG`OwzHNFV8JWMR%tdt<5k#&vu^aYDNmA+iMB{)Pt>&g|R2F4jowQ03J zRQ{4*>NL!G*um7cwE#PIG4JcLF~%pzpG#u4M8#rbpnbsNR&R5IClR#&>Km!`(gW&~ zKr=*iHyjtLN$7(oh<*s5x@U=(eYq=@Z)1v>sWs_n765`ftfaJpmL?yzIqwmPqUP|IMJ&SLql(KoO&x)_|6A%~S4;wrRGmTRGsi$6SvV>b3J8i3>1> zqjP=AnLt$>AHP{N@1!#L$IP9yf2QXgq$qe>lGv12&U}_|GLvx7 z?S&QDpZtCQSKbt1eMvOhMWc_mJKR<+<+GuSx!i$X6&y|HNj@RFnNl%Zng5>dzV{b` zt5BjIo#LKV-zn8s+~tpzfyAeu zaP2fBjMxLYlvV_S6b!L5)DB6?#1V4iJd#sP-$NhD)T+CEk+~x`FP_|MYR0=maVl{i8I;PD{u=n*QPNyrAO&S@c z{vlj3McOjucz2uqzur%41ReZJyfnLfwv$x@d7D2W7)$CbcmBScqhd z%9V_ripO0>qML}ykgJCzWY+&+e~B3O%i6~yJ>=)p(%P(h1oCcc`l;^m&m0~O`;~gf zL4W9|NC^4tGV(+H2G)7PFyevxdyfju0>g%XTN3Y{8@T2Eq>BTrf@NY^L=Yxs$qPI0ZyyC zQwfgib)haxr4k!uM}A#~U-N_By%kdVN3XJsxr0h;tAK!fLDf&?f5Xe@Jtr)`?$=`C zy?&7y&Ceji@~c3dw1fZh9U5Q`WU1L7|10o~49lJl9Us0M`&Ht|E86xV&NcC0qfebz5M4=Ow18dC2nD%j)) zSqlp}hWDI}igAOy=PDA~$^_OG6l1mIDY~1Ds(K02<-Xvezhw0GtQ`M+J>wA)k`2Qa zyX!Y>`Sy(I{mb`T76ElvYy&TBtN~sd+H)jE<85&!-VigH3tnvbkHCxN!k|kI)%D*7 zO%Zij{e`f?3d;|0iXz#A?9{8V{CeL(D2nnh)GfqbwD{JFzRj>UG_qupqW$N?Jf^x& z6D;xLX%?zp<1hq3eyNeQAE<>lGzmzJC*&WHbDjEg0MI-#p+9HUUImYdZH~07iYSCH zgqe5?`iXF~y7VzE#H>A{UB%$agJyE41nHkNnXGiimtX?@R?%NP@2d)6J(I@TkdG$# zx%=1}r08aymWw1rZ!A^TyD-L&+W0-ME{qAzyo3Z7)y{f?+{IJF)n!!BTJuP zWu1r%sU_{UT3o;dw;EeRoe;|3DWGwTkV!DatsIkmJppsw{t+$K#0jl(Q-l_z#mNqq zlH4#GH3Sap4&e3D;x`0gu#gsA$7_B|uzts`zzuP!qJ%JSBMQw7)XZ3sqGn3#&liy` zu{vQ(O?9|OE3bvM6V;mElBD7@u~k$+Zy#=#2QZavCQ_GFdI3@WqUEcGXf)>?mMSu| zpTzS@Eto9$hIfyy^b~AC(%)df&pCmI1tTtW1r`AcR|S++ngb!yJk{y>X}n?qw$69p z*ifzuQ;9*{6?ZL)oeM^D#m`zr`a5~nS67T>2m?mQV?V720uc5}2PGBpAA|ALmS# zVy*}R;5+4u-2sUx)SALoo1MRUq)jMv;%5zz>QEl_WcbzZ(1bP%QA;I2WzYxpp4P{D1q@O<~{ z8eM~*SuZJ|hI%UkRJEwxeN}n%up!TWiWR~Ltscp6b-ZeVPvMw~uULpTDOs^zC*HXX z-#-ldgq=5iSX$_&%tK|)-!{3yS~Iv5);&;;oOvFU>HoY9sQM%NzskNlO&JV+9>2f)XN1GDRvm1JfmP-9y3;BS_M${bbcRf3h|xL+bpj` z_Xs*PC;dG45OK*Bn)oP8iIm7L=FbdlG8p{zU-6=eTJc5NWZ1IcQF~?6CvH8F!{vHn zm*iuRf8<8?PN&hXsIV)Oy6eQ%Cgx8J_4bVLfER5O^a%oYQB+BMPpnqM z^AGvKT3JQzib0rp6JS%1*SE@EaYp@s zE7c0x(p!>ff(Yhwt6O(Cr;v0~uPoqvlGVI%)Oa!}J5xQ=m`f=8Bi#Sc8yC@M2WkIG zb}C4d+UfJc*7SM=rOBU}*8R-e#Ok`6goEmVpF+1k0KFJ3t)h0dO#W}KeTtRw;Zoj` z>&3a@H66`7IoEfJnl!C!7s`kTH-hc> zsd@r*ioh$Z;S!IFv?&3qCkHziwFkn!(^lj0y-vth&JD2da_K0%YN0kX6xzjFI5W16 z%2(lX#_PjXTdfm*9i>fQAgIg%at1$u0!5TZtu3GhK5ax0S5HzjuL8i&O}h9XXzX9Z z*9;U>xg7`wyW2ARwNd(6C>;fR>R$Uw$ZIW&_GhKasD8$f-t!AXTeFJ5Zl-25L)x2n zB@~>~gb-|8C2H=_YUl8XDBVi8MC{9Pc!<3i=c zwKF3*c($zA$Ijz`=8c+h`#V%or}N+FlERQeGEq_7X-m!LZ+avplpLvAkGGjC9?@DP z{@=OFfG}6;=g7o{A-aQ7b!nyQcOu|#3b)~RrAq9Zw`B* z>{rt96gH`ggR-i9L3qT_>@7r&k+|>?;U?A~JODyKy}vUm$smil%fu8Lu5-gj*K>2X zehVc9H*XWE>`0l17VyNA#4*bHs=_@331bU#+(qGcC2r>MV2$k&d_M#lg`ZBFMfq+` zbF9DO=RK%Y`?{ONcUtXsviD&AWfs)XrTTXID3O-DiJ;+6McspNAeg~ zNxs^a384(@81Onc5fKlk;@B^g9N3g<0EIkY64-Bg&6fTsN@vefwH1^9lbsmZ*xtEsf727c_s?52{T z9Ns=>qB?%xlN(qV6{BafNw^3 z)eB5-%BWiM$uhG6Tbp&#hIjSsO0Px6Om(<;j)za5Z-pU%ADy7_s9{RUB9Uh#+UND1 zQq)f6cF9p_gc2*vxfUUJ^iZ79+DXz%U!xYL;~rpj#puk!%i>SYj`euQWX{b_Ep8mx z;060<7M7Q3jSYpyW%l0Teo;^?E z0}j8O4w)^QDbII#Qjdo2Zy$sgC*Gtf65Q!jE|BlVskfHut4d3@a-3Gw(%tJJY1Dhl z{t_szBn+C;Ze0pX=S1aRNvGNL>b3IIHn;+Nvh(zVDGYR+(iBW3+Uc-4@uWS%h&$-2 zA-kuD$;+eq?Ro37j>3-o>;CNb zZ!Xy!fov9iF@i6YToiK3mvdyUcnnL@9CEi?LmI`Tw;R@q!CEul$Q%rAXMNEmWjL6k zSnaZ`?>gRACm#7vw%q@GPX+2dBV3Jxq&C1Gvc9TyASYToLvK3d7&~k7*-P%-95O~6 zHigG-k5O#*y5y2Zm**7h{k-xYKN6-ko&OZu0X}MTyHd?gK zPpyLL?XzV_HEHNca=Z|8KC&3bl|<6%T)pIB{MkZ)OaN$z`h zPo^W&wU}uz4OsZ~ZWHo?;y%AZ64|n!uKyq`Z}VZA$j+!*6Z=)tijXtS z;YUvPlt{0YuPW0MgyB8s*f5zIPPz}IpA>k$Xsn%38xdao*>Za4DHq5&2=p#t(md5^ zT(w&nvI?pC`5d?8Zr(UV@4)_7ZGzVEX{eK578fN4QQX%oZ5QVa8MIRLC{e4P%{WL= zcB~jO*(P`xD)KS6vTWsL#z=-jXMa+R+SrOWW8O`&&q3g`rB{0`O4Nl3Jk&yvE?DbR&Xt{2v?r{*a zo|UU)qbk&0p$5#|Z?G$yqv_8HykeiJ8(ew3DBRJiXz@Panpq!*0yuXa(ICXUlyvME ztb8uvygCviG)k#93$0OV+mc@Oss=F<%eKK(gQSFG8Ut?UNlr>#fKCFdxDw$7nQ4`- z-^bvSi}?(KMmnR~O4ejAySpwP>c*+g>TJ|0d=k1lm$U~xJS$Iak_&#!8=t!yGJvii zVA)9*=|kXcrc=r#JL*i8Ca-AN;xX~;%=M1T@&$qxx$9rT4HAFkI&$F-g52gQR|ydv zYms)p5XsM#0ev#%0?16hZDU8|7-V^#*%exx11uHiEy?mcZ#iLgVGsLbVR3zliQm&h z&0Y3U>JyRs$V3s8Ob7<2D)-ON_{qxa=YD=_gqGy}?+YSp#=`hV#+AiC4IQT@LOv@k z-7r5E6g4ad*wcJ-;?yIOOZr5~SMk4zX+Ia#7c|d3XaJm~5j5S6ssKZ?>7snM9%mFu zLAM`ah5?b(j10!a+7jLgrl4xdK{(4-Br}NbcaCWf7Eh*c9?f-5KsJw;r<|2W1~wN^ zfrur_Z-?L6bKu}Hg#YI&x7yK(C2O5K;nAR&0ou7TBCGsox@eQlb@!|;?7AF-> z-5@YtQn^zU<}~!Ls2c{#{T73leU%-aF_PSGC#^WCCK^P)PJ3HeeLxa?bAf3oWB38_ zb{%ryd8i+%LTkupNS3?pnh;lBChrCZ1G_cduQ^$TV}1y1HQ;k?h_gs*$evy$TYkeq z$G+7)tDveeyQ1>SnEy3-WX*;4GUJ&oH|(q96rXx?2ngXclCn*Izw)Gnj9|N8aLHE#mfwmzj~r z6&d&C^%=>anZmFd5ZfN*;=lik7g8(u$EIk*9G6Q7x(wle(j(;@DUVinq zx3ueK0Z;GWyWH{NL$7t+9J~sf+*fWyqBf(*2`SxytKB>14ql3b-=H&o?Oj}WEUcH& zp;3M*7{ADH34j`!T?b!0+FQEDS7gi!+{?bn>B<9YOyA0YYdtk7dFp815Pr){^kn1? zm5xDe?6?$b<$b^tRoXrnv+WY&DzdC*Evn4@l{`~9_VjoGikaZIJUX>W*hJPHuB4kG z%VsrJI)x>_L9U#!V;z^NxRM3i-mRX?29^08umRX!o4aCe)P;E*nJX)W|<4 zc;{CJDO1CGVsr%0dy1ToAzadRuyc4N>lmaO>iZrMP#iR~W1vY;oQXoR4f(H1PZ9G(zd9I0iwHbeTBi>w=0tI*8SU6Da)#3NSMrYaB3WBH=RlRkapcZPxGVywu zoFBzwsT;Lx3POenMS=5xoA$kA|ACy<$l2f8R%%Sv&ejUDu@3{*;sscIoGa{{gqOdi zA=YoZ%M*qUop*P+ZQsC)fNP-qO60d6L2mmduomXi&j3h&1q}J_tpj4d00(~b)_}18 zZ%MHZtUa*?<{p2N*8}Zr&`yCK-V0Yiwf}FUcRbb)*!#i2ed)LH19ZMT|46X`>TOcp z08by;eyewfz`UgN!<{-LNV4>k)`dm-*ns zOO;89li40qgQs5GR-$E9OMHH5)WPwCcXoF6A1oih2;TgXfIwJlq}(ARO5K~cah?jyV{KQR<%f0bzd}Fsvbgef*P{fc%0PDVfs&F*O7Q@i|>ldxHQFhwZ$u z@!_D%s2v5I7_DoJ1ksGdO_FsRI2vv@}ar~UpCbZd9z{>f{f{=T>hgb;ZSFoDAD^yn9G1?R^L57jiV7Gjw{m=Jo&FL8$6s>)G&vAyvus-x0i+i^ByBD^ zu8;fDCgy!%S_%0$!*LSSH3b{lfuuic9%6sA+%SgF@6DKrbhdAP{1^agfc;}CEYY4@ zvBJ!qy@${Hvh9FOU6s!+kK#*buhMMuRF z^R|kdMJXQlpYOzJT3;_*QMLG*8mIBHTaY*UE1>mdd025oKMR++-H4MPmfS9^Ktg99vV_zS6~}m z$02=N6!mp^ZPVYwVVm(|vC5Dw;k5Z)$-<1$cWVodGe5r#bKRIv>HFs^*(??ic8h+T z>I+`M@P+b;fTF@r<9lmG#ve7G?@xH0qer}T#?|*EGvsRZZ*-(Lstl}uMQZFdX*Vhm zwfKd6;$_ddj9*h?&a1p-=ncEsdzbo z3hh_knP#}WJ_V_sEE~i}7Y;IUE`rzJ{)Xy`B`z&k3!&4kqX_PTm zinQ^h9{N3;j%hsD$*1ax6Vj$0Y>xNpctz}m`^kP*qrfM*k({Jxw{EmsxqBv*vTl`K zam<=s%D^2@DkHVgyvJ3F6K|&sT)vxAv(Q3jH9FD1cFlIEXW8@_dEoheXBO?AkfPA` zMB$`{0e&8&sEU=S%hpNA`WKE;(8NFW*%crzih|5KVb8f@=g6%xd@((K9G;jTCPN_C`VDq9Y$f_R+~m|-S*$m922(l zupH$MmdMrotBmDhT$e!ydaE^$Ai-56^T^yp8|drzj2;0UsmYE6nS)#8LQe4bOQyq4 zSkj39W_WNh0-KZyv1i>~S%j#+-*z^3tD?<+gpE9vn$oK60=vw6j8t2?%wKBCPzwR` zs3BCr4ixNmt}KTFC(;oudoeukbL=;RJ1J@NYPLra5VdXHOI9SsnHOh&Ix>9e0TAy0 zpaKbSdM&jFR*!fLvDr3gFm?<_eYCmhjwk3FuMco{N^N`aj(k>4=yFD)fq;mq6j_T= z%mX|&+Z}MP){;LTR+}B7bNVIUpLXr&qhwUHEys4IS}int#%3vMqyRaJN7nIke2I%o zwxh=LSb6U2l80bMJNbs2<+hmy8C2=R%atL|# z7Sz3z-^mdxewdttE|Pe&fME(QTszqgw^wJ`Z1JYd(XA@e^{H`Lg7vY+oyG-TkBRDV zH<24rck~v9*v&E?GcjL&)+Bbfb-1RCzR8>8=u_L(*2`&&&H_fF5h=-y+@+W++hOCY z83b)>E5BVES#mfwF0yZ|r}7AHW~`9DdO>vaR;YD?c#ap{tJ8#uBl0wf<15S$=A_9k zYQ#UOkM2-1H~4W>{o}@@()OEnd-2kav4jE@A{0%Oq@Kj{hqSt(v0`_3OzW5435!?B zv*B>-=|dZFnI=dk75p@%2KyMj`Nmnz>{ETFS| z6&Vvs*Ru+-xkxkz2YZPfX%o1FDo)&;QIkpX(6g+>*gs$Cr3CJixt%XX3z(&x9R4`s zMn?kKx^uy0vjtOG+)~hPw+9vAYOZ4C`*Av@2Xa$C8^W1m9d4Dt(f&TkCj>P=DSRvO z-|OD>-nuc3|H@6KU%hx`-E;c%87uy2zRSDlAYW{%I;n#{j>lqaXrm{gP;92fgq)9* ztf^Wzs~;|2Hsfux8L(0MT~pHm8L{cRg!c;phKs0gfRyG}2mi(HfV80m_Gtn8O;g3Z zK-H-}I~M%NywN_bZ42J-3GVE8L;~j2_n9~WN;TB zzfr{ukXn%8Uo+MEYXSlrymz`ZHQ1~a109r;6rH0Sl-Gnbl1@@VAPUbHdKi&Wy$8}H z%1w55GS6ILU9ia>pyDiE)x<0}&VsnFSqB9Or3 zgH{UkksJ_H>$kt8;7CK5z0JZS8*2l!HiAK~IbqS+Mge@Z{^k|o>lINKsk}~+C}*qq zBcaKNDk1qFuuVZAeV@J83@-&R{^b}%cWx@?VEHb{$Za~HGAdu0zZ@(2_I&Q;{*)3h ztdx)O$E^J@yI(6N|Gq!?tk3+b-{nFHAoUtoZ*mH58-*&3u3avxhFbL;8ej&PPWJ8x zL+Kl6oPi3))3Rb)B|NZgFi{GaT(ifz@SI77^b}YBPxP)eJb9~J|MBfx4WScG z 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

1h^F5TVy+1MmNt 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%0DkuYzpR@E;XsF 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�K^DnDiBX#l8MT?3JpP=$QozKu*gnM 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 literal 0 HcmV?d00001 diff --git a/toprf-secure-backup.tgz b/toprf-secure-backup.tgz new file mode 100644 index 0000000000000000000000000000000000000000..98c6dfa6cfd1243ea6fd27276a4504ad404e57fd GIT binary patch literal 88975 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 Date: Wed, 16 Apr 2025 11:31:06 +0800 Subject: [PATCH 040/187] fix: update google-acm package --- package.json | 2 +- yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index cb5d3ba1b70c..89edaa03e1d0 100644 --- a/package.json +++ b/package.json @@ -338,7 +338,7 @@ "react-native-fs": "^2.20.0", "react-native-gesture-handler": "^1.10.3", "react-native-get-random-values": "^1.8.0", - "react-native-google-acm": "git+https://github.com/Web3Auth/react-native-google-acm.git#71367b39a22cb6684d79752cce0680b63c25c9ea", + "react-native-google-acm": "git+https://github.com/Web3Auth/react-native-google-acm.git#edf4e52397f766d56d1644d908246e358f3cf774", "react-native-gzip": "^1.1.0", "react-native-i18n": "2.0.15", "react-native-in-app-review": "^4.3.3", diff --git a/yarn.lock b/yarn.lock index 1176355362b7..2a6296f351ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24660,9 +24660,9 @@ react-native-get-random-values@^1.8.0: dependencies: fast-base64-decode "^1.0.0" -"react-native-google-acm@git+https://github.com/Web3Auth/react-native-google-acm.git#71367b39a22cb6684d79752cce0680b63c25c9ea": +"react-native-google-acm@git+https://github.com/Web3Auth/react-native-google-acm.git#edf4e52397f766d56d1644d908246e358f3cf774": version "0.1.0" - resolved "git+https://github.com/Web3Auth/react-native-google-acm.git#71367b39a22cb6684d79752cce0680b63c25c9ea" + resolved "git+https://github.com/Web3Auth/react-native-google-acm.git#edf4e52397f766d56d1644d908246e358f3cf774" react-native-gzip@^1.1.0: version "1.1.0" From 7455c06d6075818d575b3b58a3dca9d66296c000 Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 16 Apr 2025 11:59:41 +0800 Subject: [PATCH 041/187] feat: update to use web3auth google-acm --- package.json | 2 +- yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index c042fa79cebf..ec493cced29e 100644 --- a/package.json +++ b/package.json @@ -334,7 +334,7 @@ "react-native-fs": "^2.20.0", "react-native-gesture-handler": "^1.10.3", "react-native-get-random-values": "^1.8.0", - "react-native-google-acm": "git+https://github.com/ieow/react-native-google-acm.git#09de563582649c34069d06d087f22c998b37f436", + "react-native-google-acm": "git+https://github.com/Web3Auth/react-native-google-acm.git#edf4e52397f766d56d1644d908246e358f3cf774", "react-native-gzip": "^1.1.0", "react-native-i18n": "2.0.15", "react-native-in-app-review": "^4.3.3", diff --git a/yarn.lock b/yarn.lock index ae221f41fe30..abb97f2b67e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24520,9 +24520,9 @@ react-native-get-random-values@^1.8.0: dependencies: fast-base64-decode "^1.0.0" -"react-native-google-acm@git+https://github.com/ieow/react-native-google-acm.git#09de563582649c34069d06d087f22c998b37f436": +"react-native-google-acm@git+https://github.com/Web3Auth/react-native-google-acm.git#edf4e52397f766d56d1644d908246e358f3cf774": version "0.1.0" - resolved "git+https://github.com/ieow/react-native-google-acm.git#09de563582649c34069d06d087f22c998b37f436" + resolved "git+https://github.com/Web3Auth/react-native-google-acm.git#edf4e52397f766d56d1644d908246e358f3cf774" react-native-gzip@^1.1.0: version "1.1.0" From be8f8d62e5d26065f61370cc9a77b58c4fceb8df Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 16 Apr 2025 12:06:29 +0800 Subject: [PATCH 042/187] fix: update pod lock --- ios/Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f6e7c88dbbc9..0301c4b85a44 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1354,7 +1354,7 @@ SPEC CHECKSUMS: FlipperKit: 2efad7007d6745a3f95e4034d547be637f89d3f6 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b - GoogleAcm: 8bb9e2d7b8effe2f178d3212fbb90719e8d52ab8 + GoogleAcm: c34f3645441b02e92bd6a1ff3d85358169a3331d GoogleAppMeasurement: f9de05ee17401e3355f68e8fc8b5064d429f5918 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 From 32b82295501c8710830004393db3d528fef1dd32 Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 16 Apr 2025 12:12:13 +0800 Subject: [PATCH 043/187] fix: add dependency at root --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 89edaa03e1d0..c929da7a565b 100644 --- a/package.json +++ b/package.json @@ -223,6 +223,7 @@ "@metamask/swappable-obj-proxy": "^2.1.0", "@metamask/swaps-controller": "^12.1.0", "@metamask/token-search-discovery-controller": "^2.1.0", + "@metamask/toprf-secure-backup": "file:./toprf-secure-backup.tgz", "@metamask/transaction-controller": "45.0.0", "@metamask/utils": "^11.2.0", "@ngraveio/bc-ur": "^1.1.6", From 4097ebc3d9b56ab30e80eaab3db85a9341b2fa79 Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 16 Apr 2025 12:53:44 +0800 Subject: [PATCH 044/187] fix: use https://raw.githubusercontent.com/ for local packages --- package.json | 12 ++++++------ yarn.lock | 32 +++++++++----------------------- 2 files changed, 15 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index c929da7a565b..e0c2f096ce61 100644 --- a/package.json +++ b/package.json @@ -143,9 +143,9 @@ "undici": "5.28.5", "**/@ethersproject/signing-key/elliptic": "^6.6.1", "**/@walletconnect/utils/elliptic": "^6.6.1", - "@metamask/auth-network-utils": "file:./auth-network-utils.tgz", - "@metamask/toprf-secure-backup": "file:./toprf-secure-backup.tgz", - "@metamask/seedless-onboarding-controller": "file:./seedless-onboarding-controller.tgz" + "@metamask/auth-network-utils": "https://raw.githubusercontent.com/MetaMask/metamask-mobile/refs/heads/feat/seedless-ui-merge-toprf/auth-network-utils.tgz", + "@metamask/toprf-secure-backup": "https://raw.githubusercontent.com/MetaMask/metamask-mobile/refs/heads/feat/seedless-ui-merge-toprf/toprf-secure-backup.tgz", + "@metamask/seedless-onboarding-controller": "https://raw.githubusercontent.com/MetaMask/metamask-mobile/refs/heads/feat/seedless-ui-merge-toprf/seedless-onboarding-controller.tgz" }, "dependencies": { "@config-plugins/detox": "^8.0.0", @@ -158,7 +158,7 @@ "@metamask/address-book-controller": "^6.0.3", "@metamask/approval-controller": "^7.1.0", "@metamask/assets-controllers": "^51.0.2", - "@metamask/auth-network-utils": "./auth-network-utils.tgz", + "@metamask/auth-network-utils": "https://raw.githubusercontent.com/MetaMask/metamask-mobile/refs/heads/feat/seedless-ui-merge-toprf/auth-network-utils.tgz", "@metamask/base-controller": "^8.0.0", "@metamask/bitcoin-wallet-snap": "^0.9.0", "@metamask/bridge-controller": "^7.0.0", @@ -208,7 +208,7 @@ "@metamask/rpc-errors": "^7.0.2", "@metamask/scure-bip39": "^2.1.0", "@metamask/sdk-communication-layer": "0.29.0-wallet", - "@metamask/seedless-onboarding-controller": "file:./package-seedless.tgz", + "@metamask/seedless-onboarding-controller": "https://raw.githubusercontent.com/MetaMask/metamask-mobile/refs/heads/feat/seedless-ui-merge-toprf/package-seedless.tgz", "@metamask/selected-network-controller": "^21.0.0", "@metamask/signature-controller": "^23.1.0", "@metamask/slip44": "^4.1.0", @@ -223,7 +223,7 @@ "@metamask/swappable-obj-proxy": "^2.1.0", "@metamask/swaps-controller": "^12.1.0", "@metamask/token-search-discovery-controller": "^2.1.0", - "@metamask/toprf-secure-backup": "file:./toprf-secure-backup.tgz", + "@metamask/toprf-secure-backup": "https://raw.githubusercontent.com/MetaMask/metamask-mobile/refs/heads/feat/seedless-ui-merge-toprf/toprf-secure-backup.tgz", "@metamask/transaction-controller": "45.0.0", "@metamask/utils": "^11.2.0", "@ngraveio/bc-ur": "^1.1.6", diff --git a/yarn.lock b/yarn.lock index 2a6296f351ea..dde51b59a6b4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4458,24 +4458,10 @@ single-call-balance-checker-abi "^1.0.0" uuid "^8.3.2" -"@metamask/auth-network-utils@./auth-network-utils.tgz": +"@metamask/auth-network-utils@^0.0.0", "@metamask/auth-network-utils@https://raw.githubusercontent.com/MetaMask/metamask-mobile/refs/heads/feat/seedless-ui-merge-toprf/auth-network-utils.tgz": version "0.0.0" uid daf330dd5d37cd709ca4a63d4df48d50ee9609ce - resolved "./auth-network-utils.tgz#daf330dd5d37cd709ca4a63d4df48d50ee9609ce" - dependencies: - "@noble/curves" "^1.8.1" - "@noble/hashes" "^1.7.1" - "@toruslabs/bs58" "^1.0.0" - "@toruslabs/constants" "^15.0.0" - "@toruslabs/eccrypto" "^6.0.2" - bn.js "^5.2.1" - elliptic "^6.6.1" - json-stable-stringify "^1.2.1" - loglevel "^1.9.2" - -"@metamask/auth-network-utils@^0.0.0", "@metamask/auth-network-utils@file:./auth-network-utils.tgz": - version "0.0.0" - resolved "file:./auth-network-utils.tgz#daf330dd5d37cd709ca4a63d4df48d50ee9609ce" + resolved "https://raw.githubusercontent.com/MetaMask/metamask-mobile/refs/heads/feat/seedless-ui-merge-toprf/auth-network-utils.tgz#daf330dd5d37cd709ca4a63d4df48d50ee9609ce" dependencies: "@noble/curves" "^1.8.1" "@noble/hashes" "^1.7.1" @@ -5386,9 +5372,9 @@ utf-8-validate "^5.0.2" uuid "^8.3.2" -"@metamask/seedless-onboarding-controller@file:./package-seedless.tgz": +"@metamask/seedless-onboarding-controller@https://raw.githubusercontent.com/MetaMask/metamask-mobile/refs/heads/feat/seedless-ui-merge-toprf/package-seedless.tgz": version "0.0.1" - resolved "file:./package-seedless.tgz#94b5a6babb86d84e35a795902545668d05cdc630" + resolved "https://raw.githubusercontent.com/MetaMask/metamask-mobile/refs/heads/feat/seedless-ui-merge-toprf/package-seedless.tgz#94b5a6babb86d84e35a795902545668d05cdc630" dependencies: "@metamask/base-controller" "^8.0.0" "@metamask/browser-passworder" "^4.3.0" @@ -5399,14 +5385,14 @@ async-mutex "^0.5.0" loglevel "^1.8.1" -"@metamask/seedless-onboarding-controller@file:./seedless-onboarding-controller.tgz": +"@metamask/seedless-onboarding-controller@https://raw.githubusercontent.com/MetaMask/metamask-mobile/refs/heads/feat/seedless-ui-merge-toprf/seedless-onboarding-controller.tgz": version "0.0.1" - resolved "file:./seedless-onboarding-controller.tgz#1663636a0aee299b8532c9a50d442c96f502a26e" + resolved "https://raw.githubusercontent.com/MetaMask/metamask-mobile/refs/heads/feat/seedless-ui-merge-toprf/seedless-onboarding-controller.tgz#dfa6c8444ce21a7ed6e7c5c99ed4e890a49a6a8a" dependencies: "@metamask/base-controller" "^8.0.0" "@metamask/browser-passworder" "^4.3.0" "@metamask/keyring-controller" "^21.0.0" - "@metamask/toprf-secure-backup" "file:../../../Library/Caches/Yarn/v6/.tmp/bed4a21ccbd12946dee10cd7fb53148c/toprf-secure-backup.tgz" + "@metamask/toprf-secure-backup" "./toprf-secure-backup.tgz" "@metamask/utils" "^11.2.0" "@noble/hashes" "^1.4.0" async-mutex "^0.5.0" @@ -5664,9 +5650,9 @@ "@metamask/base-controller" "^8.0.0" "@metamask/utils" "^11.1.0" -"@metamask/toprf-secure-backup@file:../../../Library/Caches/Yarn/v6/.tmp/bed4a21ccbd12946dee10cd7fb53148c/toprf-secure-backup.tgz", "@metamask/toprf-secure-backup@file:../../../Library/Caches/Yarn/v6/.tmp/cf2b3ab9814dbdb69995d9396542a2f7/toprf-secure-backup.tgz", "@metamask/toprf-secure-backup@file:./toprf-secure-backup.tgz": +"@metamask/toprf-secure-backup@./toprf-secure-backup.tgz", "@metamask/toprf-secure-backup@file:../../../Library/Caches/Yarn/v6/.tmp/ea5e0e24f7777bc954b7e4f37c709ccd/toprf-secure-backup.tgz", "@metamask/toprf-secure-backup@https://raw.githubusercontent.com/MetaMask/metamask-mobile/refs/heads/feat/seedless-ui-merge-toprf/toprf-secure-backup.tgz": version "0.0.0" - resolved "file:./toprf-secure-backup.tgz#65be9bb22084abce031dabfa35195228ae7cc988" + resolved "https://raw.githubusercontent.com/MetaMask/metamask-mobile/refs/heads/feat/seedless-ui-merge-toprf/toprf-secure-backup.tgz#65be9bb22084abce031dabfa35195228ae7cc988" dependencies: "@metamask/auth-network-utils" "^0.0.0" "@noble/ciphers" "^1.2.1" From 399561e9d418a091ce914d0aff0cd2cdebad9df2 Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 17 Apr 2025 12:33:03 +0800 Subject: [PATCH 045/187] fix: ui fix for social --- .../Views/AccountStatus/index.styles.ts | 3 +- app/components/Views/AccountStatus/index.tsx | 31 ++++++++-- app/components/Views/ChoosePassword/index.js | 4 +- app/components/Views/Login/index.js | 57 ++++++++++++++----- app/components/Views/Onboarding/index.js | 2 +- e2e/selectors/wallet/LoginView.selectors.js | 1 + locales/languages/en.json | 3 +- 7 files changed, 76 insertions(+), 25 deletions(-) diff --git a/app/components/Views/AccountStatus/index.styles.ts b/app/components/Views/AccountStatus/index.styles.ts index 3d9e03c307a4..96c3c4a4bfc0 100644 --- a/app/components/Views/AccountStatus/index.styles.ts +++ b/app/components/Views/AccountStatus/index.styles.ts @@ -8,6 +8,7 @@ const styles = StyleSheet.create({ padding: 24, justifyContent: 'space-between', marginBottom: 16, + marginTop: 100, }, content: { flex: 1, @@ -28,7 +29,7 @@ const styles = StyleSheet.create({ fontWeight: '400', }, descriptionWrapper: { - width: '90%', + width: '100%', flexDirection: 'column', rowGap: 20, }, diff --git a/app/components/Views/AccountStatus/index.tsx b/app/components/Views/AccountStatus/index.tsx index 28e6e79b4ea9..1342e3cda0cd 100644 --- a/app/components/Views/AccountStatus/index.tsx +++ b/app/components/Views/AccountStatus/index.tsx @@ -1,5 +1,5 @@ import React, { useLayoutEffect } from 'react'; -import { View, Image } from 'react-native'; +import { View, Image, TouchableOpacity } from 'react-native'; import Text from '../../../component-library/components/Texts/Text'; import { TextColor, @@ -7,7 +7,7 @@ import { } from '../../../component-library/components/Texts/Text/Text.types'; import { useNavigation, useRoute } from '@react-navigation/native'; import { strings } from '../../../../locales/i18n'; -import { getTransparentOnboardingNavbarOptions } from '../../UI/Navbar'; +import { getOnboardingNavbarOptions } from '../../UI/Navbar'; import { useTheme } from '../../../util/theme'; import styles from './index.styles'; import Button, { @@ -15,6 +15,7 @@ import Button, { ButtonSize, ButtonWidthTypes, } from '../../../component-library/components/Buttons/Button'; +import Icon , { IconName, IconSize } from '../../../component-library/components/Icons/Icon'; const account_status_img = require('../../../images/account_status.png'); // eslint-disable-line @@ -37,10 +38,30 @@ const AccountStatus = ({ const accountName = (route.params as AccountRouteParams)?.accountName; const onContinue = (route.params as AccountRouteParams)?.onContinue; - useLayoutEffect(() => { - navigation.setOptions(getTransparentOnboardingNavbarOptions(colors)); - }, [navigation, colors]); + const marginLeft = 16; + const headerLeft = () => ( + navigation.goBack()}> + + + ); + + const headerRight = () => ( + + ); + + navigation.setOptions(getOnboardingNavbarOptions(route, { + headerLeft, + headerRight, + }, + colors, + false,)); + }, [navigation, colors, route]); return ( diff --git a/app/components/Views/ChoosePassword/index.js b/app/components/Views/ChoosePassword/index.js index fdaad2576c69..b81b188afba9 100644 --- a/app/components/Views/ChoosePassword/index.js +++ b/app/components/Views/ChoosePassword/index.js @@ -363,8 +363,8 @@ class ChoosePassword extends PureComponent { if (authType.oauth2Login) { this.props.navigation.reset({ - index: 1, - routes: [{ name: Routes.ONBOARDING.SUCCESS }], + index: 0, + routes: [{ name: Routes.ONBOARDING.SUCCESS, params: { showPasswordHint: true } }], }); } else { this.props.navigation.replace('AccountBackupStep1'); diff --git a/app/components/Views/Login/index.js b/app/components/Views/Login/index.js index 92594ec35c6d..9c0f13710c0f 100644 --- a/app/components/Views/Login/index.js +++ b/app/components/Views/Login/index.js @@ -74,6 +74,7 @@ import HelpText, { import { getTraceTags } from '../../../util/sentry/tags'; import { store } from '../../../store'; import Fox from '../../../images/fantom.png'; +import { UserActionType } from '../../../actions/user'; const deviceHeight = Device.getDeviceHeight(); const breakPoint = deviceHeight < 700; @@ -224,6 +225,10 @@ class Login extends PureComponent { * Action to set if the user is using remember me */ setAllowLoginWithRememberMe: PropTypes.func, + /** + * Action to reset the oauth2 login + */ + dispatchOauth2LoginReset: PropTypes.func, /** * Metrics injected by withMetricsAwareness HOC */ @@ -318,6 +323,11 @@ class Login extends PureComponent { BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress); } + handleUseOtherMethod = () => { + this.props.dispatchOauth2LoginReset(); + this.props.navigation.navigate('Onboarding'); + }; + handleBackPress = async () => { if (!this.props.oauth2LoginSuccess) { await Authentication.lockApp(); @@ -659,21 +669,35 @@ class Login extends PureComponent { /> - - - {strings('login.go_back')} - -