Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: reCaptcha #37

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions mobile/app/(app)/add-key/captcha copy.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<html>

<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>reCAPTCHA demo: Simple page</title>
<script src="https://www.google.com/recaptcha/enterprise.js" async defer></script>
<style>
html,
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
width: 100vh;
margin: 0;
background-color: #f9f9f9;
}

.g-recaptcha {
/* width: 100vh; */
/* transform: scale(0.85); */
/* Adjust the scale to fit */
/* transform-origin: 0 0; */
}
</style>
</head>

<script>
function onSubmit(token) {
// Handle the token submission
window.ReactNativeWebView.postMessage(token);
}
</script>

<body>
<div class="captcha-container">
<form action="" method="POST">
<div class="g-recaptcha" data-sitekey="6LcHZ54qAAAAAEXtX8jbfsPvFvUPuIlwuytxq2mk" data-action="LOGIN" data-callback="onSubmit"></div>
<br />
<input type="submit" value="Submit">
</form>
</div>
</body>

</html>
42 changes: 42 additions & 0 deletions mobile/app/(app)/add-key/captcha.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<html>

<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>reCAPTCHA demo: Simple page</title>
<script src="https://www.google.com/recaptcha/enterprise.js" async defer></script>
<style>
html,
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f9f9f9;
}

.g-recaptcha {
/* width: 100vh; */
/* transform: scale(0.85); */
/* Adjust the scale to fit */
/* transform-origin: 0 0; */
}
</style>
</head>

<script>
function onSubmit(token) {
// Handle the token submission
window.ReactNativeWebView.postMessage(token);
}
</script>

<body>
<div class="captcha-container">
<form action="" method="POST">
<div class="g-recaptcha" data-sitekey="6LcHZ54qAAAAAEXtX8jbfsPvFvUPuIlwuytxq2mk" data-action="LOGIN" data-callback="onSubmit"></div>
</form>
</div>
</body>

</html>
54 changes: 47 additions & 7 deletions mobile/app/(app)/add-key/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StyleSheet, Text, View, ScrollView, TextInput as RNTextInput, Alert as RNAlert } from "react-native";
import { StyleSheet, Text, View, ScrollView, TextInput as RNTextInput, Alert as RNAlert, Modal } from "react-native";
import React, { useEffect, useRef, useState } from "react";
import { router, useNavigation } from "expo-router";
import { useGnoNativeContext } from "@gnolang/gnonative";
Expand All @@ -14,16 +14,19 @@ import {
selectKeyName,
setKeyName,
selectPhrase,
selectSelectedChain,
} from "@/redux";
import { ProgressViewModal, ChainSelectView } from "@/views";
import { TextCopy, Layout, Alert, Spacer, Button, TextInput } from "@/components";
import { TextCopy, Layout, Alert, Spacer, Button, TextInput, ModalHeader, ModalContent } from "@/components";
import { Octicons } from "@expo/vector-icons";
import { colors } from "@/assets";
import { WebView, WebViewMessageEvent } from 'react-native-webview';

export default function Page() {

const [error, setError] = useState<string | undefined>(undefined);
const [loading, setLoading] = useState(false);
const [modalVisible, setModalVisible] = useState(false);

const inputRef = useRef<RNTextInput>(null);

Expand All @@ -38,6 +41,8 @@ export default function Page() {
const existingAccount = useAppSelector(existingAccountSelector);
const keyName = useAppSelector(selectKeyName);
const phrase = useAppSelector(selectPhrase);
const currentNetwork = useAppSelector(selectSelectedChain)


useEffect(() => {
const unsubscribe = navigation.addListener("focus", async () => {
Expand Down Expand Up @@ -91,7 +96,7 @@ export default function Page() {
})();
}, [signUpState, newAccount]);

const onCreate = async () => {
const onCreate = async (catpchaToken: string | undefined = undefined) => {
setError(undefined);
if (!keyName) {
setError("Please fill out all fields");
Expand All @@ -114,16 +119,21 @@ export default function Page() {
return;
}

if (currentNetwork?.hasCaptcha && !catpchaToken) {
setModalVisible(true);
return
}

if (signUpState === SignUpState.user_exists_only_on_local_storage && existingAccount) {
await gnonative.activateAccount(keyName);
await gnonative.setPassword(masterPassword, existingAccount.address);
await dispatch(onboarding({ account: existingAccount })).unwrap();
await dispatch(onboarding({ account: existingAccount, captcha: catpchaToken })).unwrap();
return;
}

try {
setLoading(true);
await dispatch(signUp({ name: keyName, password: masterPassword, phrase })).unwrap();
await dispatch(signUp({ name: keyName, password: masterPassword, phrase, captcha: catpchaToken })).unwrap();
} catch (error) {
RNAlert.alert("Error", "" + error);
setError("" + error);
Expand All @@ -139,6 +149,14 @@ export default function Page() {
router.back()
}

const handleMessage = (event: WebViewMessageEvent) => {
// Capture the token from the WebView
const { data } = event.nativeEvent;
console.log('Recaptcha token received:', data);
setModalVisible(false); // Update the token in state
onCreate(data); // Call the onCreate function with the token
};

return (
<Layout.Container>
<Layout.Body>
Expand Down Expand Up @@ -169,19 +187,41 @@ export default function Page() {
<ChainSelectView />
<Alert severity="error" message={error} />
<Spacer />
<Button.TouchableOpacity title="Create" onPress={onCreate} variant="primary" loading={loading} />
<Button.TouchableOpacity title="Create" onPress={() => onCreate()} variant="primary" loading={loading} />
<Spacer space={8} />
<Button.TouchableOpacity title="Back" onPress={onBack} variant="secondary" disabled={loading} />
<Modal visible={modalVisible} style={{
width: '100%',
height: '100%',
backgroundColor: 'black'
}}>
<View style={{ flex: 1, height: '100%', paddingTop: 100 }}>
<ModalHeader title="Recaptcha" onClose={() => setModalVisible(false)} />
<WebView source={{ uri: 'https://fantastic-barnacle-jvxv645773j7vj-5173.app.github.dev/?chain_id=dev' }} style={{ minHeight: 500 }}
javaScriptEnabled
domStorageEnabled
automaticallyAdjustContentInsets
onMessage={handleMessage} // Handle messages from WebView
/>
</View>
</Modal>
</View>
</View>
</ScrollView>
<ProgressViewModal />
</Layout.Body>
</Layout.Body >
</Layout.Container >
);
}

const styles = StyleSheet.create({
centeredView: {
flex: 1,
justifyContent: "center",
alignItems: "center",
marginTop: 22,
backgroundColor: "#0008",
},
container: {
flex: 1,
alignItems: "center",
Expand Down
13 changes: 13 additions & 0 deletions mobile/app/(app)/add-key/localHtmlFile.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<html>
<head>
<title>reCAPTCHA demo: Simple page</title>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
</head>
<body>
<form action="?" method="POST">
<div class="g-recaptcha" data-sitekey="6LfHmYIqAAAAAEktVwxuLoagtBfSsp8kA_zx0vA2"></div>
<br/>
<input type="submit" value="Submit">
</form>
</body>
</html>
2 changes: 1 addition & 1 deletion mobile/app/(app)/home/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default function Page() {

const response = await gnonative.listKeyInfo();
setAccounts(response);
dispatch(checkForKeyOnChains())
// dispatch(checkForKeyOnChains())
} catch (error: unknown | Error) {
console.error(error);
} finally {
Expand Down
20 changes: 6 additions & 14 deletions mobile/assets/chains.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,21 @@
"chainId": "portal-loop",
"chainName": "Portal loop",
"gnoAddress": "https://rpc.gno.land:443",
"faucetAddress": "https://faucet-api.gno.land"
"faucetAddress": "https://faucet-api.gno.land",
"hasCaptcha": true
},
{
"chainId": "test5",
"chainName": "Testnet 5",
"gnoAddress": "http://rpc.test5.gno.land",
"faucetAddress": "https://faucet-api.test5.gno.land"
"faucetAddress": "https://faucet-api.test5.gno.land",
"hasCaptcha": true
},
{
"chainId": "dev",
"chainName": "Berty-Dev",
"gnoAddress": "https://api.gno.berty.io:443",
"faucetAddress": "https://faucetpass.gno.berty.io"
},
{
"chainId": "teritori-1",
"chainName": "Teritori-1",
"gnoAddress": "testnet.gno.teritori.com:26657"
},
{
"chainId": "dev",
"chainName": "localhost",
"gnoAddress": "localhost:26657",
"faucetAddress": "http://localhost:8545"
"faucetAddress": "https://faucetpass.gno.berty.io",
"hasCaptcha": false
}
]
28 changes: 17 additions & 11 deletions mobile/redux/features/signupSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ interface SignUpParam {
name: string;
password: string;
phrase: string;
captcha?: string;
}

type SignUpResponse = { newAccount?: KeyInfo, existingAccount?: KeyInfo, state: SignUpState };
Expand All @@ -67,7 +68,7 @@ type SignUpResponse = { newAccount?: KeyInfo, existingAccount?: KeyInfo, state:
*/
export const signUp = createAsyncThunk<SignUpResponse, SignUpParam, ThunkExtra>("user/signUp", async (param, thunkAPI) => {

const { name, password, phrase } = param;
const { name, password, phrase, captcha } = param;
const { registerAccount, selectedChain } = (thunkAPI.getState() as RootState).signUp;

const gnonative = thunkAPI.extra.gnonative as GnoNativeApi;
Expand Down Expand Up @@ -161,15 +162,15 @@ export const signUp = createAsyncThunk<SignUpResponse, SignUpParam, ThunkExtra>(
thunkAPI.dispatch(addProgress(`no faucetAddress set for chain "${selectedChain.chainName}"`))
} else {
thunkAPI.dispatch(addProgress(`onboarding "${name}"`))
await onboard(gnonative, newAccount, selectedChain.faucetAddress);
await onboard(gnonative, newAccount, selectedChain.faucetAddress, captcha);
}

thunkAPI.dispatch(addProgress(`SignUpState.account_created`))
return { newAccount, state: SignUpState.account_created };
}
})

export const onboarding = createAsyncThunk<SignUpResponse, { account: KeyInfo }, ThunkExtra>("user/onboarding", async (param, thunkAPI) => {
export const onboarding = createAsyncThunk<SignUpResponse, { account: KeyInfo, captcha?: string }, ThunkExtra>("user/onboarding", async (param, thunkAPI) => {
thunkAPI.dispatch(addProgress(`onboarding "${param.account.name}"`))

const { selectedChain } = (thunkAPI.getState() as RootState).signUp;
Expand All @@ -178,9 +179,9 @@ export const onboarding = createAsyncThunk<SignUpResponse, { account: KeyInfo },
throw new Error("No chain selected");
}

const { account } = param;
const { account, captcha } = param;
const gnonative = thunkAPI.extra.gnonative as GnoNativeApi;
await onboard(gnonative, account, selectedChain.faucetAddress);
await onboard(gnonative, account, selectedChain.faucetAddress, captcha);

thunkAPI.dispatch(addProgress(`SignUpState.account_created`))
return { newAccount: account, state: SignUpState.account_created };
Expand Down Expand Up @@ -264,10 +265,10 @@ function convertToJson(result: string | undefined) {
return json;
}

const onboard = async (gnonative: GnoNativeApi, account: KeyInfo, faucetRemote?: string) => {
const onboard = async (gnonative: GnoNativeApi, account: KeyInfo, faucetRemote?: string, captcha?: string) => {
const { name, address } = account
const address_bech32 = await gnonative.addressToBech32(address);
console.log("onboarding %s, with address: %s", name, address_bech32);
console.log("onboard %s, with address: %s", name, address_bech32);

try {
const hasBalance = await hasCoins(gnonative, address);
Expand All @@ -279,7 +280,7 @@ const onboard = async (gnonative: GnoNativeApi, account: KeyInfo, faucetRemote?:
}

if (faucetRemote) {
const response = await sendCoins(address_bech32, faucetRemote);
const response = await sendCoins(address_bech32, faucetRemote, captcha);
console.log("coins sent, response: %s", response);
await registerAccount(gnonative, account);
} else {
Expand Down Expand Up @@ -326,14 +327,19 @@ const hasCoins = async (gnonative: GnoNativeApi, address: Uint8Array) => {
}
};

const sendCoins = async (address: string, faucetRemote: string) => {
const sendCoins = async (address: string, faucetRemote: string, captcha?: string) => {
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");

const raw = JSON.stringify({
To: address,
captcha,
amount: 1 * 1000000 + 'ugnot'
});

console.log("sending coins to %s", address);
console.log("raw", raw);

const requestOptions = {
method: "POST",
headers: myHeaders,
Expand Down Expand Up @@ -393,7 +399,7 @@ export const signUpSlice = createSlice({
state.existingAccount = action.payload?.existingAccount;
state.signUpState = action.payload?.state;
}).addCase(initSignUpState.fulfilled, (state, action) => {
console.log("initSignUpState.fulfilled nnnnnn", action.payload);
console.log("initSignUpState.fulfilled", action.payload);
state.phrase = action.payload.phrase;
state.loading = false;
state.newAccount = undefined;
Expand All @@ -420,7 +426,7 @@ export const signUpSlice = createSlice({

export const selectChainsAvailable = createSelector(
(state: RootState) => state.signUp.customChains,
(customChains) => customChains ? chains.concat(customChains) : chains
(customChains) => customChains ? (chains as NetworkMetainfo[]).concat(customChains) : chains
);

export const { addProgress, signUpState, clearProgress, addCustomChain, setRegisterAccount, setKeyName,
Expand Down
1 change: 1 addition & 0 deletions mobile/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,6 @@ export type NetworkMetainfo = {
chainName: string;
gnoAddress: string;
faucetAddress?: string;
hasCaptcha?: boolean;
};