Persist subscriber ID in local storage (#26)

Part of [laconicd testnet validator enrollment](https://www.notion.so/laconicd-testnet-validator-enrollment-6fc1d3cafcc64fef8c5ed3affa27c675)
- Disable nitro key sign button if subscribe ID does not exist

Co-authored-by: Shreerang Kale <shreerangkale@gmail.com>
Reviewed-on: cerc-io/testnet-onboarding-app#26
This commit is contained in:
nabarun 2024-08-09 06:24:29 +00:00
parent aa9aed89f2
commit fc1c8df06b
13 changed files with 55 additions and 50 deletions

View File

@ -12,6 +12,7 @@
} }
}, },
"rules": { "rules": {
"indent": ["error", 2, { "SwitchCase": 1 }] "indent": ["error", 2, { "SwitchCase": 1 }],
"semi": ["error", "always"]
} }
} }

View File

@ -4,7 +4,7 @@ import { Link, useLocation } from 'react-router-dom';
import { AppBar, Toolbar, Avatar, Box, IconButton } from '@mui/material'; import { AppBar, Toolbar, Avatar, Box, IconButton } from '@mui/material';
const Header: React.FC = () => { const Header: React.FC = () => {
const location = useLocation() const location = useLocation();
return ( return (
<AppBar position="static" color="inherit"> <AppBar position="static" color="inherit">

View File

@ -14,16 +14,16 @@ const SelectRoleCard = ({ handleAccept, handleRoleChange }: { handleAccept: () =
const [checked, setChecked] = useState(false); const [checked, setChecked] = useState(false);
const [isHidden, setIsHidden] = useState(false); const [isHidden, setIsHidden] = useState(false);
const [isDialogOpen, setisDialogOpen] = useState(false) const [isDialogOpen, setisDialogOpen] = useState(false);
const handleCheckboxChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleCheckboxChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setChecked(event.target.checked); setChecked(event.target.checked);
}; };
const handleContinue = () => { const handleContinue = () => {
handleAccept() handleAccept();
setIsHidden(true) setIsHidden(true);
} };
const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSelectedRole(event.target.value as Role); setSelectedRole(event.target.value as Role);

View File

@ -4,7 +4,7 @@ import { Document, Page, pdfjs } from 'react-pdf';
import { Typography } from '@mui/material'; import { Typography } from '@mui/material';
// https://github.com/wojtekmaj/react-pdf?tab=readme-ov-file#copy-worker-to-public-directory // https://github.com/wojtekmaj/react-pdf?tab=readme-ov-file#copy-worker-to-public-directory
pdfjs.GlobalWorkerOptions.workerSrc = process.env.PUBLIC_URL + '/pdf.worker.min.mjs' pdfjs.GlobalWorkerOptions.workerSrc = process.env.PUBLIC_URL + '/pdf.worker.min.mjs';
const TermsAndConditionsBox = ({height}: {height: string}) => { const TermsAndConditionsBox = ({height}: {height: string}) => {
const [numPages, setNumPages] = useState<number>(); const [numPages, setNumPages] = useState<number>();

View File

@ -1,5 +1,7 @@
export const WALLET_DISCLAIMER_MSG = 'You are connecting to an experimental wallet! It is not secure. Do not use it elsewhere and/or for managing real assets.' export const WALLET_DISCLAIMER_MSG = 'You are connecting to an experimental wallet! It is not secure. Do not use it elsewhere and/or for managing real assets.';
export const REDIRECT_EMAIL_MSG = 'Close this tab and the confirmation link in your email will bring you back to the onboarding app.' export const REDIRECT_EMAIL_MSG = 'Close this tab and the confirmation link in your email will bring you back to the onboarding app.';
export const ENABLE_KYC = false; export const ENABLE_KYC = false;
export const SUBSCRIBER_ID_HASH_KEY = 'subscriberIdHash';

View File

@ -1,8 +1,8 @@
import React from 'react' import React from 'react';
import { Box, Typography } from '@mui/material' import { Box, Typography } from '@mui/material';
import { REDIRECT_EMAIL_MSG } from '../constants' import { REDIRECT_EMAIL_MSG } from '../constants';
const Email = () => { const Email = () => {
return ( return (
@ -27,7 +27,7 @@ const Email = () => {
{REDIRECT_EMAIL_MSG} {REDIRECT_EMAIL_MSG}
</Typography> </Typography>
</Box> </Box>
) );
} };
export default Email export default Email;

View File

@ -8,7 +8,7 @@ import SumsubWebSdk from "@sumsub/websdk-react";
import { MessageHandler } from "@sumsub/websdk"; import { MessageHandler } from "@sumsub/websdk";
import { config, fetchAccessToken, getAccessTokenExpirationHandler, options } from "../utils/sumsub"; import { config, fetchAccessToken, getAccessTokenExpirationHandler, options } from "../utils/sumsub";
import { ENABLE_KYC } from "../constants"; import { ENABLE_KYC, SUBSCRIBER_ID_HASH_KEY } from "../constants";
interface Participant { interface Participant {
cosmosAddress: string; cosmosAddress: string;
@ -25,7 +25,7 @@ const OnboardingSuccess = () => {
const location = useLocation(); const location = useLocation();
const { cosmosAddress } = location.state as { const { cosmosAddress } = location.state as {
cosmosAddress?: string cosmosAddress?: string
} };
const [participant, setParticipant] = useState<Participant>(); const [participant, setParticipant] = useState<Participant>();
const [token, setToken] = useState<string>(''); const [token, setToken] = useState<string>('');
@ -48,6 +48,8 @@ const OnboardingSuccess = () => {
return; return;
} }
localStorage.removeItem(SUBSCRIBER_ID_HASH_KEY);
setParticipant(participant); setParticipant(participant);
} catch (error) { } catch (error) {
console.error("Error fetching participants", error); console.error("Error fetching participants", error);

View File

@ -12,6 +12,7 @@ import { StargateClient } from "@cosmjs/stargate";
import { useWalletConnectContext } from "../context/WalletConnectContext"; import { useWalletConnectContext } from "../context/WalletConnectContext";
import SelectRoleCard, {Role} from "../components/SelectRoleCard"; import SelectRoleCard, {Role} from "../components/SelectRoleCard";
import { SUBSCRIBER_ID_HASH_KEY } from "../constants";
const SignWithCosmos = () => { const SignWithCosmos = () => {
const { session, signClient } = useWalletConnectContext(); const { session, signClient } = useWalletConnectContext();
@ -25,17 +26,17 @@ const SignWithCosmos = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const {message: innerMessage, cosmosAddress, receivedEthSig: ethSignature, subscriberIdHash} = location.state as { const {message: innerMessage, cosmosAddress, receivedEthSig: ethSignature} = location.state as {
message?: { message?: {
msg: string; msg: string;
address: string; address: string;
}; };
cosmosAddress?: string; cosmosAddress?: string;
receivedEthSig?: string; receivedEthSig?: string;
subscriberIdHash?: string;
}; };
const ethAddress = innerMessage!.address; const ethAddress = innerMessage!.address;
const subscriberIdHash = localStorage.getItem(SUBSCRIBER_ID_HASH_KEY);
const createCosmosClient = useCallback(async (endpoint: string) => { const createCosmosClient = useCallback(async (endpoint: string) => {
return await StargateClient.connect(endpoint); return await StargateClient.connect(endpoint);

View File

@ -1,5 +1,5 @@
import React, { useState, useMemo, useEffect } from "react"; import React, { useState, useMemo, useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { enqueueSnackbar } from "notistack"; import { enqueueSnackbar } from "notistack";
import canonicalStringify from "canonical-json"; import canonicalStringify from "canonical-json";
@ -13,7 +13,7 @@ import LoadingButton from '@mui/lab/LoadingButton';
import { utf8ToHex } from "@walletconnect/encoding"; import { utf8ToHex } from "@walletconnect/encoding";
import { useWalletConnectContext } from "../context/WalletConnectContext"; import { useWalletConnectContext } from "../context/WalletConnectContext";
import { ENABLE_KYC } from "../constants"; import { ENABLE_KYC, SUBSCRIBER_ID_HASH_KEY } from "../constants";
const SignWithNitroKey = () => { const SignWithNitroKey = () => {
@ -27,13 +27,20 @@ const SignWithNitroKey = () => {
}, [session, signClient, checkPersistedState]); }, [session, signClient, checkPersistedState]);
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation();
const [ethAddress, setEthAddress] = useState(""); const [ethAddress, setEthAddress] = useState("");
const [ethSignature, setEthSignature] = useState(""); const [ethSignature, setEthSignature] = useState("");
const [cosmosAddress, setCosmosAddress] = useState(""); const [cosmosAddress, setCosmosAddress] = useState("");
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const subscriberIdHash = localStorage.getItem(SUBSCRIBER_ID_HASH_KEY);
useEffect(() => {
if (!subscriberIdHash) {
setIsLoading(false);
enqueueSnackbar("Subscriber ID not found. Please verify your email and try again", { variant: "error" });
}
}, [subscriberIdHash]);
const message = useMemo(() => { const message = useMemo(() => {
return { return {
@ -45,7 +52,7 @@ const SignWithNitroKey = () => {
const signEth = async () => { const signEth = async () => {
if (session && signClient) { if (session && signClient) {
try { try {
setIsLoading(true) setIsLoading(true);
const jsonMessage = canonicalStringify(message); const jsonMessage = canonicalStringify(message);
const hexMsg = utf8ToHex(jsonMessage, true); const hexMsg = utf8ToHex(jsonMessage, true);
const receivedEthSig: string = await signClient!.request({ const receivedEthSig: string = await signClient!.request({
@ -56,7 +63,7 @@ const SignWithNitroKey = () => {
params: [hexMsg, ethAddress], params: [hexMsg, ethAddress],
}, },
}); });
setIsLoading(false) setIsLoading(false);
setEthSignature(ethSignature); setEthSignature(ethSignature);
if (ENABLE_KYC) { if (ENABLE_KYC) {
@ -68,26 +75,17 @@ const SignWithNitroKey = () => {
}, },
}); });
} else { } else {
const state = location.state as {
subscriberIdHash?: string
}
if (!state.subscriberIdHash) {
throw new Error("Subscriber ID not found. Please verify your email and try again")
}
navigate("/sign-with-cosmos", { navigate("/sign-with-cosmos", {
state: { state: {
message, message,
cosmosAddress, cosmosAddress,
receivedEthSig, receivedEthSig,
subscriberIdHash: state.subscriberIdHash,
}, },
}); });
} }
} catch (error) { } catch (error) {
console.log("err in signing ", error); console.log("err in signing ", error);
setIsLoading(false) setIsLoading(false);
enqueueSnackbar("Error signing message", { variant: "error" }); enqueueSnackbar("Error signing message", { variant: "error" });
} }
} }
@ -151,7 +149,7 @@ const SignWithNitroKey = () => {
<LoadingButton <LoadingButton
variant="contained" variant="contained"
onClick={signEth} onClick={signEth}
disabled={!Boolean(ethAddress)} disabled={!Boolean(ethAddress) || !subscriberIdHash}
sx={{ marginTop: 2 }} sx={{ marginTop: 2 }}
loading={isLoading} loading={isLoading}
> >

View File

@ -5,6 +5,8 @@ import { ethers } from 'ethers';
import { Box, colors, Typography } from '@mui/material'; import { Box, colors, Typography } from '@mui/material';
import { SUBSCRIBER_ID_HASH_KEY } from '../constants';
interface JwtPayload { interface JwtPayload {
subscriber_id: string; subscriber_id: string;
exp: number; exp: number;
@ -24,28 +26,26 @@ const Thanks: React.FC = () => {
try { try {
if(!token){ if(!token){
throw new Error("Invalid JWT Token") throw new Error("Invalid JWT Token");
} }
const decoded = jwtDecode(token) as JwtPayload; const decoded = jwtDecode(token) as JwtPayload;
const currentTime = Math.floor(Date.now() / 1000); const currentTime = Math.floor(Date.now() / 1000);
if (!decoded.subscriber_id) { if (!decoded.subscriber_id) {
throw new Error("Subscriber ID not found") throw new Error("Subscriber ID not found");
} }
if (decoded.exp < currentTime) { if (decoded.exp < currentTime) {
throw new Error("Token has expired"); throw new Error("Token has expired");
} }
const subscriberIdBytes = ethers.utils.toUtf8Bytes(decoded.subscriber_id) const subscriberIdBytes = ethers.utils.toUtf8Bytes(decoded.subscriber_id);
const subscriberIdHash = ethers.utils.sha256(subscriberIdBytes); const subscriberIdHash = ethers.utils.sha256(subscriberIdBytes);
navigate('/connect-wallet', { localStorage.setItem(SUBSCRIBER_ID_HASH_KEY, subscriberIdHash);
state:{
subscriberIdHash navigate('/connect-wallet');
}
});
} catch (error) { } catch (error) {
setErr(String(error)); setErr(String(error));
} }

View File

@ -45,13 +45,14 @@ const UserVerification = () => {
useEffect(() => { useEffect(() => {
if (applicationSubmitted && kycId !== '') { if (applicationSubmitted && kycId !== '') {
const kycIdHash = ethers.utils.sha256(ethers.utils.toUtf8Bytes(kycId)); const kycIdHash = ethers.utils.sha256(ethers.utils.toUtf8Bytes(kycId));
navigate("/sign-with-cosmos", { navigate("/sign-with-cosmos", {
state: { state: {
message, message,
cosmosAddress, cosmosAddress,
receivedEthSig, receivedEthSig,
subscriberIdHash: kycIdHash, kycIdHash
}}) }});
} }
}, [applicationSubmitted, kycId, navigate, cosmosAddress, message, receivedEthSig]); }, [applicationSubmitted, kycId, navigate, cosmosAddress, message, receivedEthSig]);

View File

@ -1,4 +1,4 @@
import React from 'react' import React from 'react';
const VerifyEmail = () => { const VerifyEmail = () => {
return ( return (
@ -18,7 +18,7 @@ const VerifyEmail = () => {
}} }}
></iframe> ></iframe>
</div> </div>
) );
} };
export default VerifyEmail export default VerifyEmail;

View File

@ -29,5 +29,5 @@ export const getAccessTokenExpirationHandler = (userId: string) => {
return async () => { return async () => {
const newToken = await fetchAccessToken(userId); const newToken = await fetchAccessToken(userId);
return newToken; return newToken;
} };
}; };