Skip to content

Commit

Permalink
integrate decrypt attestation secrets
Browse files Browse the repository at this point in the history
  • Loading branch information
mehdi-torabiv committed Aug 27, 2024
1 parent e8138f4 commit 1adc5da
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 43 deletions.
19 changes: 17 additions & 2 deletions src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ interface Domain {
version: string;
}

interface AttestMessage {
attester: string;
export interface AttestMessage {
attester?: string;
data: string;
expirationTime: bigint;
nonce: bigint;
Expand All @@ -32,6 +32,13 @@ interface AttestMessage {
schema: string;
}

export interface RevokeMessage {
nonce: string;
revoker: string;
schema: string;
uid: string;
}

interface Signature {
r: string;
s: string;
Expand All @@ -54,3 +61,11 @@ export interface AttestPayload {
signature: Signature;
types: Types;
}

export interface RevokePayload {
domain: Domain;
message: RevokeMessage;
primaryType: string;
signature: Signature;
types: Types;
}
2 changes: 1 addition & 1 deletion src/pages/Identifiers/Attestation/Attestation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from '@ethereum-attestation-service/eas-sdk';
import StepperComponent from '../../../components/shared/CustomStepper';
import { platformAuthentication } from '../../../services/api/auth';
import { useLinkIdentifierMutation } from '../../../services/api/linking/query';
import { useLinkIdentifierMutation } from '../../../services/api/eas/query';
import sepoliaChain from '../../../utils/contracts/eas/sepoliaChain.json';
import { useSigner } from '../../../utils/eas-wagmi-utils';
import { AttestPayload } from '../../../interfaces';
Expand Down
208 changes: 168 additions & 40 deletions src/pages/Identifiers/Identifiers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ import {
Box,
Avatar,
CircularProgress,
IconButton,
} from '@mui/material';
import VerifiedIcon from '@mui/icons-material/Verified';
import VisibilityIcon from '@mui/icons-material/Visibility';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import { FaDiscord, FaGoogle } from 'react-icons/fa';
import { useNavigate } from 'react-router-dom';
import clsx from 'clsx';
Expand All @@ -26,8 +29,11 @@ import { useGetAttestations } from '../../services/eas/query';
import { decodeAttestationData, IAttestation } from '../../libs/oci';
import sepoliaChain from '../../utils/contracts/eas/sepoliaChain.json';
import { useSigner } from '../../utils/eas-wagmi-utils';
import { useRevokeIdentifierMutation } from '../../services/api/linking/query';
import { AttestPayload } from '../../interfaces';
import {
useDecryptAttestationsSecretMutation,
useRevokeIdentifierMutation,
} from '../../services/api/eas/query';
import { RevokePayload } from '../../interfaces';
import { convertStringsToBigInts } from '../../utils/helper';

interface IdentifierItemProps {
Expand All @@ -40,13 +46,19 @@ interface IdentifierItemProps {
onRevoke: (uid: string) => void;
onConnect: (name: string) => void;
isLoading: boolean;
isRevealedPending: boolean;
isRevealed: string;
onReveal: () => void;
}

const IdentifierItem: React.FC<IdentifierItemProps> = ({
identifier,
onRevoke,
onConnect,
isLoading,
isRevealedPending,
isRevealed,
onReveal,
}) => (
<Box mb={2}>
<Paper elevation={1} className="rounded-xl py-2">
Expand All @@ -64,9 +76,25 @@ const IdentifierItem: React.FC<IdentifierItemProps> = ({
primary={
<div className="flex items-center">
{identifier.verified && (
<VerifiedIcon sx={{ color: 'blue', mr: 2 }} />
<VerifiedIcon sx={{ color: 'blue', mr: 1 }} />
)}
{identifier.name}
<Typography>{identifier.name}</Typography>
<div className="ml-3">
{isRevealedPending ? (
<CircularProgress size={24} />
) : (
<>
{isRevealed !== '*********' ? isRevealed : '*********'}
<IconButton onClick={onReveal} sx={{ ml: 1 }}>
{isRevealed !== '*********' ? (
<VisibilityOffIcon />
) : (
<VisibilityIcon />
)}
</IconButton>
</>
)}
</div>
</div>
}
sx={{ ml: 2 }}
Expand Down Expand Up @@ -119,27 +147,46 @@ export function Identifiers() {
const { mutate: mutateRevokeIdentifier, data: revokeIdentifierResponse } =
useRevokeIdentifierMutation();

const [loadingIdentifier, setLoadingIdentifier] = useState<boolean>(false);
const { mutate: mutateDecryptAttestationsSecret } =
useDecryptAttestationsSecretMutation();

useEffect(() => {
if (!attestationsResponse) throw new Error('No attestations found');
const [loadingIdentifiers, setLoadingIdentifiers] = useState<{
[uid: string]: boolean;
}>({});

const attestationsData = attestationsResponse.map((attestation) => {
const decodedData = decodeAttestationData(attestation.data);
const [revealedIdentifiers, setRevealedIdentifiers] = useState<{
[uid: string]: string;
}>({});

const providerData = decodedData.find((data) => data.name === 'provider');
const [revealing, setRevealing] = useState<{ [uid: string]: boolean }>({});

return {
...attestation,
provider:
typeof providerData?.value.value === 'string'
? providerData.value.value
: undefined,
decodedData,
};
});
useEffect(() => {
const processAttestations = () => {
if (!attestationsResponse) {
return;
}

setAttestations(attestationsData);
const attestationsData = attestationsResponse.map((attestation) => {
const decodedData = decodeAttestationData(attestation.data);

const providerData = decodedData.find(
(data) => data.name === 'provider'
);

return {
...attestation,
provider:
typeof providerData?.value.value === 'string'
? providerData.value.value
: undefined,
decodedData,
};
});

setAttestations(attestationsData);
};

processAttestations();
}, [attestationsResponse]);

useEffect(() => {
Expand All @@ -158,6 +205,16 @@ export function Identifiers() {
});

setIdentifiers(updatedIdentifiers);

const initialRevealedState = updatedIdentifiers.reduce(
(acc, identifier) => {
acc[identifier.uid] = '*********';
return acc;
},
{} as { [uid: string]: string }
);

setRevealedIdentifiers(initialRevealedState);
}, [attestations]);

const navigate = useNavigate();
Expand All @@ -168,7 +225,7 @@ export function Identifiers() {

if (!siweJwt) throw new Error('OCI SIWE token not found');

setLoadingIdentifier(true);
setLoadingIdentifiers((prev) => ({ ...prev, [uid]: true }));

mutateRevokeIdentifier({
uid,
Expand All @@ -181,20 +238,71 @@ export function Identifiers() {

const handleConnect = useCallback(
(identifier: string) => {
console.log(`Connect identifier for ${identifier}`);
navigate(`/identifiers/${identifier.toLowerCase()}/attestation`);
},
[navigate]
);

const handleReveal = useCallback(
(uid: string) => {
// Toggle between showing and hiding the identifier
if (revealedIdentifiers[uid] !== '*********') {
setRevealedIdentifiers((prev) => ({
...prev,
[uid]: '*********',
}));
return;
}

setRevealing((prev) => ({
...prev,
[uid]: true,
}));

const siweJwt = localStorage.getItem('OCI_TOKEN');

if (!siweJwt) throw new Error('OCI SIWE token not found');

mutateDecryptAttestationsSecret(
{
uid,
siweJwt,
chainId,
},
{
onSuccess: (response) => {
console.log('Decrypted secret:', response);

setRevealedIdentifiers((prev) => ({
...prev,
[uid]: response.data.id,
}));
setRevealing((prev) => ({
...prev,
[uid]: false,
}));
},
onError: (error) => {
console.error('Error decrypting secret:', error);
setRevealing((prev) => ({
...prev,
[uid]: false,
}));
},
}
);
},
[chainId, mutateDecryptAttestationsSecret, revealedIdentifiers]
);

useEffect(() => {
const revokeIdentifier = async () => {
if (revokeIdentifierResponse) {
console.log('Revoke identifier response', revokeIdentifierResponse);

const payload: AttestPayload = convertStringsToBigInts(
const payload: RevokePayload = convertStringsToBigInts(
revokeIdentifierResponse.data
) as AttestPayload;
) as RevokePayload;

console.log('Payload:', payload);

Expand All @@ -208,28 +316,45 @@ export function Identifiers() {

eas.connect(signer);

const transformedPayload: DelegatedRevocationRequest = {
schema: payload?.message?.schema,
data: {
uid: payload?.message?.uid,
},
signature: payload.signature,
revoker: payload.message.revoker,
deadline: 0n,
};

const tx = await eas.revokeByDelegation(transformedPayload);

await tx.wait();
if ('revoker' in payload.message) {
const transformedPayload: DelegatedRevocationRequest = {
schema: payload.message.schema,
data: {
uid: payload.message.uid,
},
signature: payload.signature,
revoker: payload.message.revoker,
deadline: 0n,
};

const tx = await eas.revokeByDelegation(transformedPayload);

await tx.wait();

setLoadingIdentifiers((prev) => ({
...prev,
[payload.message.uid]: false,
}));
} else {
throw new Error('Invalid message type for revocation');
}
} catch (error) {
console.error('Error during revocation:', error);

if ('uid' in payload.message) {
setLoadingIdentifiers((prev) => ({
...prev,
[payload.message.uid]: false,
}));
}
} finally {
setLoadingIdentifier(false);
refetch();
}
}
};

revokeIdentifier();
}, [revokeIdentifierResponse, refetch, signer]);
}, [revokeIdentifierResponse]);

return (
<div>
Expand All @@ -252,7 +377,10 @@ export function Identifiers() {
identifier={identifier}
onRevoke={handleRevoke}
onConnect={handleConnect}
isLoading={loadingIdentifier}
isLoading={loadingIdentifiers[identifier.uid] || false}
isRevealedPending={revealing[identifier.uid] || false}
isRevealed={revealedIdentifiers[identifier.uid] || '*********'}
onReveal={() => handleReveal(identifier.uid)}
/>
))}
</List>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ export interface RevokeIdentifierParams {
chainId?: number;
}

export interface DecryptAttestationsSecretParams {
uid: string;
siweJwt: string;
chainId?: number;
}

export const linkIdentifier = async ({
siweJwt,
anyJwt,
Expand All @@ -34,3 +40,14 @@ export const revokeIdentifier = async ({
chainId,
});
};

export const decryptAttestationsSecret = async ({
uid,
siweJwt,
chainId,
}: DecryptAttestationsSecretParams) => {
return api.post(`/eas/${uid}/decrypt-attestation-secret`, {
siweJwt,
chainId,
});
};
Loading

0 comments on commit 1adc5da

Please sign in to comment.