-
Notifications
You must be signed in to change notification settings - Fork 0
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
add discourse #75
Changes from 2 commits
a15df17
8a882da
ea531f3
4fef466
c8f2c30
14bd160
6a235c7
aaa0b37
79e98ff
0c6c223
bd51d19
9b1f2ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
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 { | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Suggested change
|
||||||||||||||||||||||||||||
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; |
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); | ||||||||||
|
||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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:
|
||||||||||
handleNextStep(); | ||||||||||
}, | ||||||||||
onError: (error) => { | ||||||||||
console.error('Failed to generate token:', error); | ||||||||||
showSnackbar( | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Suggested change
|
||||||||||
'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’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; |
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 | ||||||||||||||||
); | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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
Suggested change
|
||||||||||||||||
|
||||||||||||||||
const handleGenerateSignedDelegation = async () => { | ||||||||||||||||
const siweJwt = localStorage.getItem('OCI_TOKEN'); | ||||||||||||||||
if (!siweJwt || !provider) return; | ||||||||||||||||
|
||||||||||||||||
const anyJwt = localStorage.getItem('DISCOURSE_JWT') as string; | ||||||||||||||||
|
||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
🔗 Analysis chainValidate JWT before usage. The JWT from localStorage should be validated before use. 🏁 Scripts executedThe 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add contract address validation.
The contract address should be validated before use. Consider adding proper validation:
📝 Committable suggestion