diff --git a/.circleci/config.yml b/.circleci/config.yml index 56b17a43e6f..3d26c1b5449 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -440,7 +440,7 @@ jobs: command: | mkdir -p test-results/jest # Tests fail with https://stackoverflow.com/questions/38558989/node-js-heap-out-of-memory without this - NODE_OPTIONS="--max-old-space-size=4096" yarn --cwd packages/mobile test:ci + NODE_OPTIONS="--max-old-space-size=4096" yarn --cwd packages/mobile test:ci --runInBand environment: JEST_JUNIT_OUTPUT: test-results/jest/junit.xml diff --git a/packages/mobile/android/app/src/main/res/values/strings.xml b/packages/mobile/android/app/src/main/res/values/strings.xml new file mode 100644 index 00000000000..55344e51920 --- /dev/null +++ b/packages/mobile/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/mobile/android/settings.gradle b/packages/mobile/android/settings.gradle index c7cd38fed52..00f740b71d0 100644 --- a/packages/mobile/android/settings.gradle +++ b/packages/mobile/android/settings.gradle @@ -1,3 +1,5 @@ rootProject.name = 'celo' +include ':react-native-securerandom' +project(':react-native-securerandom').projectDir = new File(rootProject.projectDir, '../../../node_modules/react-native-securerandom/android') apply from: file("../../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) include ':app' diff --git a/packages/mobile/branding/celo/src/brandingConfig.ts b/packages/mobile/branding/celo/src/brandingConfig.ts index 75935836f45..78ac258cb05 100644 --- a/packages/mobile/branding/celo/src/brandingConfig.ts +++ b/packages/mobile/branding/celo/src/brandingConfig.ts @@ -1,4 +1,5 @@ export const APP_NAME = 'Celo Wallet' +export const WEB_LINK = 'https://celo.org/' export const CELO_FAUCET_LINK = 'https://celo.org/developers/wallet' export const CELO_TERMS_LINK = 'https://celo.org/terms' export const TOS_LINK = 'https://celo.org/user-agreement' diff --git a/packages/mobile/ios/Podfile b/packages/mobile/ios/Podfile index 43384bd11be..3f4a80f80b2 100644 --- a/packages/mobile/ios/Podfile +++ b/packages/mobile/ios/Podfile @@ -106,6 +106,8 @@ target "celo" do permissions_path = '../../../node_modules/react-native-permissions/ios' pod 'Permission-Camera', :path => "#{permissions_path}/Camera.podspec" + pod 'RNSecureRandom', :path => '../../../node_modules/react-native-securerandom' + target "celoTests" do inherit! :search_paths end diff --git a/packages/mobile/ios/Podfile.lock b/packages/mobile/ios/Podfile.lock index 8227d896683..c3e34ddfa4a 100644 --- a/packages/mobile/ios/Podfile.lock +++ b/packages/mobile/ios/Podfile.lock @@ -476,6 +476,8 @@ PODS: - React - RNScreens (2.7.0): - React + - RNSecureRandom (1.0.0): + - React - RNSentry (1.7.1): - React - Sentry (~> 5.2.0) @@ -568,6 +570,7 @@ DEPENDENCIES: - RNPermissions (from `../../../node_modules/react-native-permissions`) - RNReanimated (from `../../../node_modules/react-native-reanimated`) - RNScreens (from `../../../node_modules/react-native-screens`) + - RNSecureRandom (from `../../../node_modules/react-native-securerandom`) - "RNSentry (from `../../../node_modules/@sentry/react-native`)" - RNShare (from `../../../node_modules/react-native-share`) - RNSVG (from `../../../node_modules/react-native-svg`) @@ -738,6 +741,8 @@ EXTERNAL SOURCES: :path: "../../../node_modules/react-native-reanimated" RNScreens: :path: "../../../node_modules/react-native-screens" + RNSecureRandom: + :path: "../../../node_modules/react-native-securerandom" RNSentry: :path: "../../../node_modules/@sentry/react-native" RNShare: @@ -839,6 +844,7 @@ SPEC CHECKSUMS: RNPermissions: ad71dd4f767ec254f2cd57592fbee02afee75467 RNReanimated: dd8c286ab5dd4ba36d3a7fef8bff7e08711b5476 RNScreens: cf198f915f8a2bf163de94ca9f5bfc8d326c3706 + RNSecureRandom: 0dcee021fdb3d50cd5cee5db0ebf583c42f5af0e RNSentry: 2bae4ffee2e66376ef42cb845a494c3bde17bc56 RNShare: fea1801315aa8875d6db73a4010b14afcd568765 RNSVG: ce9d996113475209013317e48b05c21ee988d42e @@ -847,6 +853,6 @@ SPEC CHECKSUMS: Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 Yoga: 3ebccbdd559724312790e7742142d062476b698e -PODFILE CHECKSUM: 470c646f25b89e063843bdec1cd889a649b38781 +PODFILE CHECKSUM: e38c669823181b25b0d68e1d32554a4d8041b562 COCOAPODS: 1.9.1 diff --git a/packages/mobile/ios/celo.xcodeproj/project.pbxproj b/packages/mobile/ios/celo.xcodeproj/project.pbxproj index 051fd92da66..19120ed75c4 100644 --- a/packages/mobile/ios/celo.xcodeproj/project.pbxproj +++ b/packages/mobile/ios/celo.xcodeproj/project.pbxproj @@ -519,6 +519,7 @@ "${BUILT_PRODUCTS_DIR}/RNReanimated/RNReanimated.framework", "${BUILT_PRODUCTS_DIR}/RNSVG/RNSVG.framework", "${BUILT_PRODUCTS_DIR}/RNScreens/RNScreens.framework", + "${BUILT_PRODUCTS_DIR}/RNSecureRandom/RNSecureRandom.framework", "${BUILT_PRODUCTS_DIR}/RNSentry/RNSentry.framework", "${BUILT_PRODUCTS_DIR}/RNShare/RNShare.framework", "${BUILT_PRODUCTS_DIR}/React-Core/React.framework", @@ -586,6 +587,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNReanimated.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNSVG.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNScreens.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNSecureRandom.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNSentry.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNShare.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/React.framework", diff --git a/packages/mobile/locales/en-US/onboarding.json b/packages/mobile/locales/en-US/onboarding.json index 5c6af6dba0c..2c85a2d9462 100644 --- a/packages/mobile/locales/en-US/onboarding.json +++ b/packages/mobile/locales/en-US/onboarding.json @@ -43,9 +43,10 @@ "loadingBody": "This can take up to 90 seconds" }, "verificationEducation": { - "title": "Confirm", - "header": "Confirm phone number", + "title": "Phone Number", + "header": "Connect your phone number", "body": "To make sure your number is really yours, we’re going to send you three messages that will cost about 0.05 cUSD each.\n\nConfirming your number takes about three minutes.", + "feelessBody": "Connecting takes about three minutes. To confirm your number, you’ll receive three messages.", "start": "Start", "resume": "Resume", "doINeedToConfirm": "Do I need to confirm?", diff --git a/packages/mobile/locales/es-419/onboarding.json b/packages/mobile/locales/es-419/onboarding.json index ec862ffffd0..38823f1a841 100644 --- a/packages/mobile/locales/es-419/onboarding.json +++ b/packages/mobile/locales/es-419/onboarding.json @@ -43,9 +43,10 @@ "loadingBody": "Esto puede tardar hasta 90 segundos" }, "verificationEducation": { - "title": "Confirmar", - "header": "Confirmar número de teléfono", + "title": "Número de Teléfono", + "header": "Conectá tu número de teléfono", "body": "Para asegurarnos de que tu número sea realmente tuyo, te enviaremos tres mensajes de texto que costarán alrededor de 0.05 cUSD cada uno.\n\nConfirmar tu número tomará unos tres minutos.", + "feelessBody": "Confirmar tu número tomará unos tres minutos. Para confirmar tu número, recibirás tres mensajes.", "start": "Comenzar", "resume": "Reanudar", "doINeedToConfirm": "¿Necesito confirmar?", diff --git a/packages/mobile/package.json b/packages/mobile/package.json index 2b8c26ea0d4..d85cdf3bad6 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -22,7 +22,7 @@ "dev:emulator:run": "$ANDROID_HOME/emulator/emulator -avd", "test": "export TZ=UTC && ./scripts/sync_branding.sh -b celo && jest --silent", "test:all": "yarn lint && yarn build:ts && yarn test", - "test:ci": "yarn test --coverage --runInBand", + "test:ci": "yarn test --coverage", "test:watch": "yarn test --watch --maxWorkers=4", "test:verbose": "export TZ=UTC && jest --verbose", "test:e2e:android": "./scripts/run_e2e.sh -p android", @@ -107,6 +107,7 @@ "react-native-fs": "^2.16.6", "react-native-gesture-handler": "^1.6.1", "react-native-geth": "https://github.com/celo-org/react-native-geth#5864c09", + "react-native-google-safetynet": "https://github.com/celo-org/react-native-google-safetynet#8a0355f", "react-native-keep-awake": "^4.0.0", "react-native-keyboard-aware-scroll-view": "^0.9.1", "react-native-keychain": "6.0.0", @@ -120,6 +121,7 @@ "react-native-restart-android": "^0.0.7", "react-native-safe-area-context": "^3.0.6", "react-native-screens": "^2.7.0", + "react-native-securerandom": "^1.0.0", "react-native-secure-randombytes": "^3.0.2", "react-native-send-intent": "git+https://github.com/celo-org/react-native-send-intent#a0f4b00", "react-native-share": "^3.3.0", diff --git a/packages/mobile/scripts/sync_branding.sh b/packages/mobile/scripts/sync_branding.sh index 6f9db048f28..5a598ecb388 100755 --- a/packages/mobile/scripts/sync_branding.sh +++ b/packages/mobile/scripts/sync_branding.sh @@ -21,7 +21,7 @@ echo $mobile_root cd "$mobile_root" # Please update the sha when valora branding updates are needed -valora_branding_sha=c8f6b4b +valora_branding_sha=cd51906 if [[ "$branding" == "valora" ]]; then # prevents git from asking credentials diff --git a/packages/mobile/secrets.json.enc b/packages/mobile/secrets.json.enc index 97b1d0ade2a..cb9d577ee0f 100644 Binary files a/packages/mobile/secrets.json.enc and b/packages/mobile/secrets.json.enc differ diff --git a/packages/mobile/src/config.ts b/packages/mobile/src/config.ts index 62b262a188e..cdb1c0d3b54 100644 --- a/packages/mobile/src/config.ts +++ b/packages/mobile/src/config.ts @@ -96,6 +96,12 @@ export const MOONPAY_PUBLIC_KEY = keyOrUndefined( Config.SECRETS_KEY, 'MOONPAY_PUBLIC_KEY' ) +export const RECAPTCHA_SITE_KEY = keyOrUndefined( + secretsFile, + Config.SECRETS_KEY, + 'RECAPTCHA_SITE_KEY' +) +export const SAFETYNET_KEY = keyOrUndefined(secretsFile, Config.SECRETS_KEY, 'SAFETYNET_KEY') export const MOONPAY_RATE_API = `https://api.moonpay.io/v3/currencies/celo/price?apiKey=${MOONPAY_PUBLIC_KEY}` export const EXCHANGE_PROVIDER_LINKS: ExternalExchangeProvider[] = [ diff --git a/packages/mobile/src/flags.ts b/packages/mobile/src/flags.ts index 190dee8eb43..c4d96774221 100644 --- a/packages/mobile/src/flags.ts +++ b/packages/mobile/src/flags.ts @@ -10,6 +10,7 @@ export const features = { SHOW_CASH_OUT: false, PNP_USE_DEK_FOR_AUTH: true, USE_PHONE_NUMBER_PRIVACY: true, + KOMENCI: false, } export const pausedFeatures = { diff --git a/packages/mobile/src/index.d.ts b/packages/mobile/src/index.d.ts index 96f8fa42c7a..099191d0e05 100644 --- a/packages/mobile/src/index.d.ts +++ b/packages/mobile/src/index.d.ts @@ -15,6 +15,7 @@ declare module 'react-native-clock-sync' declare module 'react-native-crypto' declare module 'react-native-bip39' declare module 'react-native-flag-secure-android' +declare module 'react-native-google-safetynet' declare module 'svgs' declare module 'react-native-languages' declare module 'react-native-version-check' diff --git a/packages/mobile/src/onboarding/registration/__snapshots__/NameAndNumber.test.tsx.snap b/packages/mobile/src/onboarding/registration/__snapshots__/NameAndNumber.test.tsx.snap index 3d77fda32a1..e3c14bcfdb0 100644 --- a/packages/mobile/src/onboarding/registration/__snapshots__/NameAndNumber.test.tsx.snap +++ b/packages/mobile/src/onboarding/registration/__snapshots__/NameAndNumber.test.tsx.snap @@ -271,6 +271,7 @@ exports[`NameAndNumberScreen renders correctly 1`] = ` - + > + + - + > + + - + > + + - + > + + - + > + + { + const regionCode = getRegionCode(e164PhoneNumber || '') + const countries = new Countries(i18n.language) + return countries.getCountryByCodeAlpha2(regionCode || '') + }, [e164PhoneNumber, i18n.language]) + useEffect(() => { if (status.isVerified) { dispatch(setNumberVerified(true)) @@ -60,10 +79,17 @@ function VerificationEducationScreen({ route, navigation }: Props) { }, []) ) - const onPressStart = (withoutRevealing: boolean) => { - return () => { - dispatch(setHasSeenVerificationNux(true)) - dispatch(startVerification(withoutRevealing)) + const onStartVerification = () => { + const withoutRevealing = actionableAttestations.length >= numAttestationsRemaining + dispatch(setHasSeenVerificationNux(true)) + dispatch(startVerification(withoutRevealing)) + } + + const onPressStart = async () => { + if (features.KOMENCI) { + await showCaptcha() + } else { + onStartVerification() } } @@ -93,6 +119,26 @@ function VerificationEducationScreen({ route, navigation }: Props) { setShowLearnMoreDialog(false) } + const showCaptcha = async () => { + setIsCaptchaVisible(true) + const safetyNetAttestationResponse = await getSafetyNetAttestation() + Logger.info('SafetyNet attestation complete:', JSON.stringify(safetyNetAttestationResponse)) + setSafetyNetAttestation(safetyNetAttestationResponse) + } + const hideCaptcha = () => setIsCaptchaVisible(false) + + const handleCaptchaResolved = (res: any) => { + hideCaptcha() + const captchaCode = res?.nativeEvent?.data + if (captchaCode !== 'cancel' && captchaCode !== 'error') { + Logger.info('Captcha code received: ', captchaCode) + // TODO: Add some brief loading indicator and send captcha somewhere before continuing. + // Also, cache the session ID received. + // TODO: Before calling this, make sure |safetyNetAttestation| has finished loading on Android. + onStartVerification() + } + } + if (isLoading) { return ( @@ -123,8 +169,7 @@ function VerificationEducationScreen({ route, navigation }: Props) { ) } else if (isBalanceSufficient) { // Sufficient balance - const withoutRevealing = actionableAttestations.length >= numAttestationsRemaining - bodyText = t('verificationEducation.body') + bodyText = t(`verificationEducation.${features.KOMENCI ? 'feelessBody' : 'body'}`) firstButton = (