From f4c04e06281dc7cc3d4568f001476fae04a600c0 Mon Sep 17 00:00:00 2001 From: Hailey Date: Wed, 30 Jul 2025 16:54:03 -0700 Subject: [PATCH] implement the attestation client, update captcha loop --- package.json | 2 +- src/screens/Signup/StepCaptcha/index.tsx | 89 ++++++++++++++++++++++-- yarn.lock | 8 +-- 3 files changed, 88 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 9dcfae4d87..cbd8d5437a 100644 --- a/package.json +++ b/package.json @@ -186,7 +186,7 @@ "react-native": "^0.79.3", "react-native-compressor": "^1.11.0", "react-native-date-picker": "^5.0.12", - "react-native-device-attest": "^0.1.1", + "react-native-device-attest": "^0.1.4", "react-native-drawer-layout": "^4.1.8", "react-native-edge-to-edge": "^1.6.0", "react-native-gesture-handler": "2.25.0", diff --git a/src/screens/Signup/StepCaptcha/index.tsx b/src/screens/Signup/StepCaptcha/index.tsx index c5d1a60e26..6a38fd9135 100644 --- a/src/screens/Signup/StepCaptcha/index.tsx +++ b/src/screens/Signup/StepCaptcha/index.tsx @@ -1,12 +1,13 @@ -import React from 'react' -import {ActivityIndicator, View} from 'react-native' +import React, {useEffect, useState} from 'react' +import {ActivityIndicator, Platform, View} from 'react-native' +import ReactNativeDeviceAttest from 'react-native-device-attest' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {nanoid} from 'nanoid/non-secure' import {createFullHandle} from '#/lib/strings/handles' import {logger} from '#/logger' -import {isWeb} from '#/platform/detection' +import {isAndroid, isIOS, isNative, isWeb} from '#/platform/detection' import {ScreenTransition} from '#/screens/Login/ScreenTransition' import {useSignupContext} from '#/screens/Signup/state' import {CaptchaWebView} from '#/screens/Signup/StepCaptcha/CaptchaWebView' @@ -16,9 +17,56 @@ import {BackNextButtons} from '../BackNextButtons' const CAPTCHA_PATH = isWeb ? '/gate/signup' : '/gate/signup/attempt-attest' -type - export function StepCaptcha() { + if (isWeb) { + return + } else { + return + } +} + +export function StepCaptchaNative() { + const [token, setToken] = useState() + const [payload, setPayload] = useState() + const [ready, setReady] = useState(false) + + useEffect(() => { + ;(async () => { + logger.debug('trying to generate attestation token...') + try { + if (isIOS) { + logger.debug('starting to generate devicecheck token...') + const token = await ReactNativeDeviceAttest.getDeviceCheckToken() + setToken(encodeURIComponent(token)) + logger.debug(`generated devicecheck token: ${token}`) + } else { + const {token, payload} = + await ReactNativeDeviceAttest.getIntegrityToken('signup') + setToken(encodeURIComponent(token)) + setPayload(encodeURIComponent(base64UrlEncode(payload))) + } + } catch (e: any) { + logger.error(e) + } finally { + setReady(true) + } + })() + }, []) + + if (!ready) { + return + } + + return +} + +function StepCaptchaInner({ + token, + payload, +}: { + token?: string + payload?: string +}) { const {_} = useLingui() const theme = useTheme() const {state, dispatch} = useSignupContext() @@ -27,6 +75,7 @@ export function StepCaptcha() { const stateParam = React.useMemo(() => nanoid(15), []) const url = React.useMemo(() => { + console.log(`service url: ${state.serviceUrl}`) const newUrl = new URL(state.serviceUrl) newUrl.pathname = CAPTCHA_PATH newUrl.searchParams.set( @@ -36,8 +85,26 @@ export function StepCaptcha() { newUrl.searchParams.set('state', stateParam) newUrl.searchParams.set('colorScheme', theme.name) + if (isNative && token) { + newUrl.searchParams.set('platform', Platform.OS) + newUrl.searchParams.set('token', token) + if (isAndroid && payload) { + newUrl.searchParams.set('payload', payload) + } + } + + console.log(newUrl.href) + return newUrl.href - }, [state.serviceUrl, state.handle, state.userDomain, stateParam, theme.name]) + }, [ + state.serviceUrl, + state.handle, + state.userDomain, + stateParam, + theme.name, + token, + payload, + ]) const onSuccess = React.useCallback( (code: string) => { @@ -108,3 +175,13 @@ export function StepCaptcha() { ) } + +function base64UrlEncode(data: string): string { + const encoder = new TextEncoder() + const bytes = encoder.encode(data) + + const binaryString = String.fromCharCode(...bytes) + const base64 = btoa(binaryString) + + return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/[=]/g, '') +} diff --git a/yarn.lock b/yarn.lock index 3fff9ffbde..cfb327c48a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16787,10 +16787,10 @@ react-native-date-picker@^5.0.12: resolved "https://registry.yarnpkg.com/react-native-date-picker/-/react-native-date-picker-5.0.12.tgz#12540b6a58500811ee7e4fc0244e3accc7cca9c1" integrity sha512-R/mUnCKhcuxbhKPFwYdBQCxQt9HHLqpM4ruRUqlcBjiUZ3N2wdnwOMyc888Ps8qp8e7v29PrDHtUlG8LPuFn9w== -react-native-device-attest@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/react-native-device-attest/-/react-native-device-attest-0.1.1.tgz#5aa55bad2fcc964228656d1b764fb87625afe888" - integrity sha512-dOOaCWPzEISdMubtCbwExhjOe7izZmAUbdk790FCBVqq09Vqepxt/vn2zRKyy4EoUBG8LKLMg22kCko6TJNoPA== +react-native-device-attest@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/react-native-device-attest/-/react-native-device-attest-0.1.4.tgz#221860443765422f6dfebf4d868f367cda2a61b1" + integrity sha512-jFogWXXl3NBEAA3U+f1NQwdeIysCu6bGEmMGzz6gw4uipe6ELxZmovx64cJJHessZs1gjYDaUcbXGncirwykYg== react-native-dotenv@^3.4.11: version "3.4.11"