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

add discourse #75

Merged
merged 12 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from 6 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
104 changes: 104 additions & 0 deletions src/components/pages/attestations/Discourse/DiscourseStepFour.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { useState } from 'react';
import {
Box,
Button,
CircularProgress,
Stack,
Typography,
} from '@mui/material';
import { FaLink } from 'react-icons/fa';
import { useNavigate } from 'react-router-dom';
import { Address } from 'viem';
import { useAccount } from 'wagmi';

import { AttestPayload } from '../../../../interfaces';
import EASService from '../../../../services/eas.service';
import useSnackbarStore from '../../../../store/useSnackbarStore';
import { contracts } from '../../../../utils/contracts/eas/contracts';
import { useSigner } from '../../../../utils/eas-wagmi-utils';

interface DiscourseStepFourProps {
attestedSignutare: AttestPayload | null;
}

const DiscourseStepFour: React.FC<DiscourseStepFourProps> = ({
attestedSignutare,
}) => {
const { showSnackbar } = useSnackbarStore();
const navigate = useNavigate();
const signer = useSigner();
const { chainId } = useAccount();
const [isLoading, setIsLoading] = useState<boolean>(false);

const easContractAddress = contracts.find(
(contract) => contract.chainId === chainId
)?.easContractAddress;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add contract address validation.

The contract address should be validated before use. Consider adding proper validation:

   const easContractAddress = contracts.find(
     (contract) => contract.chainId === chainId
   )?.easContractAddress;
+  if (!easContractAddress) {
+    throw new Error(`No EAS contract address found for chain ID ${chainId}`);
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const easContractAddress = contracts.find(
(contract) => contract.chainId === chainId
)?.easContractAddress;
const easContractAddress = contracts.find(
(contract) => contract.chainId === chainId
)?.easContractAddress;
if (!easContractAddress) {
throw new Error(`No EAS contract address found for chain ID ${chainId}`);
}


const easService = signer
? new EASService(easContractAddress as Address, signer)
: null;

const handleAttestByDelegation = async () => {
if (!easService) {
throw new Error('EAS service not initialized');
}
if (!attestedSignutare) throw new Error('No attested signature provided');

setIsLoading(true);
try {
await easService.attestByDelegation(attestedSignutare);
showSnackbar('Attestation successfully completed.', {
severity: 'success',
});
navigate('/identifiers');
} catch (error) {
console.error('Error attesting identifier:', error);
showSnackbar('Failed to complete the attestation. Please try again.', {
severity: 'error',
});
} finally {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve error handling specificity.

The error handling should be more specific to help users understand and resolve issues:

     } catch (error) {
-      console.error('Error attesting identifier:', error);
-      showSnackbar('Failed to complete the attestation. Please try again.', {
+      const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
+      console.error('Error attesting identifier:', errorMessage);
+      showSnackbar(`Failed to complete the attestation: ${errorMessage}`, {
         severity: 'error',
       });
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (error) {
console.error('Error attesting identifier:', error);
showSnackbar('Failed to complete the attestation. Please try again.', {
severity: 'error',
});
} finally {
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
console.error('Error attesting identifier:', errorMessage);
showSnackbar(`Failed to complete the attestation: ${errorMessage}`, {
severity: 'error',
});
} finally {

setIsLoading(false);
}
};

return (
<Stack
spacing={3}
sx={{
textAlign: 'center',
py: 12,
px: 2,
}}
>
<Typography variant="h5" fontWeight="bold">
Finalize Delegated Attestation
</Typography>
<Typography>
To complete the process, you will be asked to sign a message with your
wallet, confirming ownership of the provided address.
</Typography>
<Box>
<Button
variant="contained"
startIcon={
isLoading ? (
<CircularProgress color="inherit" size={20} />
) : (
<FaLink />
)
}
sx={{ mt: 2, px: 4 }}
onClick={handleAttestByDelegation}
disabled={isLoading}
>
{isLoading ? 'Processing...' : 'Sign Delegated Attestation'}
</Button>
</Box>
<Typography variant="caption">
You need to pay some <b>gas</b> to complete the process.
</Typography>
</Stack>
);
};

export default DiscourseStepFour;
83 changes: 83 additions & 0 deletions src/components/pages/attestations/Discourse/DiscourseStepOne.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Box, Button, Stack, Typography } from '@mui/material';
import { jwtDecode } from 'jwt-decode';
import { FaDiscourse } from 'react-icons/fa';

import { useGenerateDiscourseVerificationTokenMutation } from '../../../../services/api/eas/query';
import useSnackbarStore from '../../../../store/useSnackbarStore';

interface DiscourseStepOneProps {
handleNextStep: () => void;
}

const DiscourseStepOne: React.FC<DiscourseStepOneProps> = ({
handleNextStep,
}) => {
const { showSnackbar } = useSnackbarStore();
const { mutate: mutateGenerateDiscourseVerificationToken, isPending } =
useGenerateDiscourseVerificationTokenMutation();

const handleGenerateDiscourseVerificationToken = async () => {
const siweJwt = localStorage.getItem('OCI_TOKEN') as string;

mutateGenerateDiscourseVerificationToken(
{
siweJwt,
},
{
onSuccess: (response) => {
const { data } = response;

localStorage.setItem(
'DISCOURSE_VERIFICATION_TOKEN',
data.verificationJwt
);

const { code } = jwtDecode(data.verificationJwt) as { code: string };
localStorage.setItem('DISCOURSE_VERIFICATION_CODE', code);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Consider secure token storage alternatives.

Storing sensitive tokens in localStorage poses security risks as it's vulnerable to XSS attacks. Consider using more secure alternatives:

  1. Use session storage for temporary tokens
  2. Implement secure cookie storage with httpOnly flag
  3. Use a secure token management service

handleNextStep();
},
onError: (error) => {
console.error('Failed to generate token:', error);
showSnackbar(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid logging sensitive error information.

Console logging error details could expose sensitive information in production.

-          console.error('Failed to generate token:', error);
+          console.error('Failed to generate token');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.error('Failed to generate token:', error);
showSnackbar(
console.error('Failed to generate token');
showSnackbar(

'Failed to generate verification token. Please try again.',
{
severity: 'error',
}
);
},
}
);
};

return (
<Stack
spacing={2}
sx={{
textAlign: 'center',
py: 12,
}}
>
<Typography variant="h5" fontWeight="bold">
Let&rsquo;s get started!
</Typography>
<Typography variant="body2">
To attest your Discourse account, you need to generate a token.
</Typography>
<Box sx={{ display: 'block' }}>
<Button
variant="contained"
startIcon={<FaDiscourse />}
disabled={isPending}
onClick={handleGenerateDiscourseVerificationToken}
aria-busy={isPending}
aria-live="polite"
>
{isPending ? 'Generating token...' : 'Generate token'}
</Button>
</Box>
</Stack>
);
};

export default DiscourseStepOne;
96 changes: 96 additions & 0 deletions src/components/pages/attestations/Discourse/DiscourseStepThree.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React from 'react';
import {
Box,
Button,
CircularProgress,
Stack,
Typography,
} from '@mui/material';
import { FaLink } from 'react-icons/fa6';
import { useAccount } from 'wagmi';

import { Provider } from '../../../../enums';
import { AttestPayload } from '../../../../interfaces';
import { useLinkIdentifierMutation } from '../../../../services/api/eas/query';
import { capitalize } from '../../../../utils/helper';

interface DiscourseStepThreeProps {
provider: Provider | undefined;
handlePrepareAttestation: (payload: AttestPayload) => void;
}

const DiscourseStepThree: React.FC<DiscourseStepThreeProps> = ({
provider,
handlePrepareAttestation,
}) => {
const { chainId } = useAccount();
const { mutate: mutateIdentifier, isPending } = useLinkIdentifierMutation(
chainId as number
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add chainId validation.

The type assertion on chainId could lead to runtime errors. Consider adding proper validation:

-  const { mutate: mutateIdentifier, isPending } = useLinkIdentifierMutation(
-    chainId as number
-  );
+  if (!chainId) {
+    throw new Error('Chain ID is required');
+  }
+  const { mutate: mutateIdentifier, isPending } = useLinkIdentifierMutation(chainId);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { mutate: mutateIdentifier, isPending } = useLinkIdentifierMutation(
chainId as number
);
if (!chainId) {
throw new Error('Chain ID is required');
}
const { mutate: mutateIdentifier, isPending } = useLinkIdentifierMutation(chainId);


const handleGenerateSignedDelegation = async () => {
const siweJwt = localStorage.getItem('OCI_TOKEN');
if (!siweJwt || !provider) return;

const anyJwt = localStorage.getItem('DISCOURSE_JWT') as string;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

JWT validation required before usage in critical operations

The JWT retrieved from localStorage is used in a security-critical attestation signing operation without any validation. Implement token validation before using it in the handleGenerateSignedDelegation function to ensure the token is valid, not expired, and hasn't been tampered with.

  • src/components/pages/attestations/Discourse/DiscourseStepThree.tsx: Add validation before using anyJwt in mutateIdentifier
🔗 Analysis chain

Validate JWT before usage.

The JWT from localStorage should be validated before use.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for JWT validation in the codebase
rg -l "jwt.*validate|validate.*jwt|verify.*jwt|jwt.*verify" --type ts

Length of output: 71


Script:

#!/bin/bash
# Search for JWT-related code and DISCOURSE_JWT usage
echo "=== JWT and Token Patterns ==="
rg "jwt|token" --type ts -i -l

echo -e "\n=== DISCOURSE_JWT Usage ==="
rg "DISCOURSE_JWT" --type ts -A 5

echo -e "\n=== Authentication/Verification Patterns ==="
rg "verify|validate|check.*token|isValid|authenticate" --type ts -l

Length of output: 2473


Script:

#!/bin/bash
# Check the implementation details of key components

echo "=== DiscourseStepThree Implementation ==="
rg -A 10 "mutateIdentifier" src/components/pages/attestations/Discourse/DiscourseStepThree.tsx

echo -e "\n=== Auth Service Implementation ==="
cat src/services/api/auth/index.ts

echo -e "\n=== API Service JWT Usage ==="
rg -A 5 "anyJwt|discourseJwt" src/services/api/

Length of output: 2601

mutateIdentifier(
{
siweJwt,
anyJwt,
},
{
onSuccess: (response) => {
const { data } = response;
handlePrepareAttestation(data);
},
onError: (error) => {
console.error(error);
},
}
);
};

if (!provider) {
return null;
}

return (
<Stack
spacing={3}
sx={{
textAlign: 'center',
py: 12,
px: 2,
}}
>
<Typography variant="h5" fontWeight="bold">
Connect Your {capitalize(provider)} Account to Your Wallet
</Typography>
<Typography>
To proceed, please verify your account by linking it to your wallet
address. This step ensures your {capitalize(provider)} account is
securely associated with your wallet.
</Typography>
<Box>
<Button
variant="contained"
startIcon={
isPending ? (
<CircularProgress color="inherit" size={20} />
) : (
<FaLink />
)
}
sx={{ mt: 2, px: 4 }}
onClick={handleGenerateSignedDelegation}
disabled={isPending}
>
{isPending ? 'Processing...' : 'Get Signed Delegated Attestation'}
</Button>
</Box>
</Stack>
);
};

export default DiscourseStepThree;
Loading
Loading