Compare commits

..

No commits in common. "63edff2aedce3054a06af63a045fabc0b3d56461" and "9caba52f7d0157d435c360e3204b602612714564" have entirely different histories.

3 changed files with 75 additions and 357 deletions

View File

@ -34,7 +34,6 @@ import {
CreateSignatureEventContent, CreateSignatureEventContent,
Meta, Meta,
ProfileMetadata, ProfileMetadata,
SignedEventContent,
User, User,
UserRole UserRole
} from '../../types' } from '../../types'
@ -53,12 +52,7 @@ import {
uploadToFileStorage uploadToFileStorage
} from '../../utils' } from '../../utils'
import styles from './style.module.scss' import styles from './style.module.scss'
import { import { Download } from '@mui/icons-material'
Cancel,
CheckCircle,
Download,
HourglassTop
} from '@mui/icons-material'
enum SignedStatus { enum SignedStatus {
Fully_Signed, Fully_Signed,
@ -356,13 +350,9 @@ export const SignPage = () => {
setLoadingSpinnerDesc('Generating hashes for files') setLoadingSpinnerDesc('Generating hashes for files')
setLoadingSpinnerDesc('Signing nostr event') setLoadingSpinnerDesc('Signing nostr event')
const prevSig = getPrevSignersSig(hexToNpub(usersPubkey!))
if (!prevSig) return
const signedEvent = await signEventForMetaFile( const signedEvent = await signEventForMetaFile(
JSON.stringify({ JSON.stringify({
prevSig fileHashes: currentFileHashes
}), }),
nostrController, nostrController,
setIsLoading setIsLoading
@ -471,7 +461,7 @@ export const SignPage = () => {
key, key,
npubToHex(nextSigner)!, npubToHex(nextSigner)!,
nostrController, nostrController,
true, false,
setAuthUrl setAuthUrl
) )
} }
@ -498,13 +488,9 @@ export const SignPage = () => {
setIsLoading(true) setIsLoading(true)
setLoadingSpinnerDesc('Signing nostr event') setLoadingSpinnerDesc('Signing nostr event')
const prevSig = await getLastSignersSig()
if (!prevSig) return
const signedEvent = await signEventForMetaFile( const signedEvent = await signEventForMetaFile(
JSON.stringify({ JSON.stringify({
prevSig fileHashes: currentFileHashes
}), }),
nostrController, nostrController,
setIsLoading setIsLoading
@ -549,70 +535,6 @@ export const SignPage = () => {
navigate(appPrivateRoutes.verify) navigate(appPrivateRoutes.verify)
} }
/**
* This function accepts an npub of a signer and return the signature of its previous signer.
* This prevSig will be used in the content of the provided signer's signedEvent
*/
const getPrevSignersSig = (npub: string) => {
if (!meta) return null
// if user is first signer then use creator's signature
if (signers[0] === npub) {
try {
const createSignatureEvent: Event = JSON.parse(meta.createSignature)
return createSignatureEvent.sig
} catch (error) {
return null
}
}
// find the index of signer
const currentSignerIndex = signers.findIndex((signer) => signer === npub)
// return null if could not found user in signer's list
if (currentSignerIndex === -1) return null
// find prev signer
const prevSigner = signers[currentSignerIndex - 1]
// get the signature of prev signer
try {
const prevSignersEvent: Event = JSON.parse(meta.docSignatures[prevSigner])
return prevSignersEvent.sig
} catch (error) {
return null
}
}
/**
* This function returns the signature of last signer
* It will be used in the content of export signature's signedEvent
*/
const getLastSignersSig = () => {
if (!meta) return null
// if there're no signers then use creator's signature
if (signers.length === 0) {
try {
const createSignatureEvent: Event = JSON.parse(meta.createSignature)
return createSignatureEvent.sig
} catch (error) {
return null
}
}
// get last signer
const lastSigner = signers[signers.length - 1]
// get the signature of last signer
try {
const lastSignatureEvent: Event = JSON.parse(
meta.docSignatures[lastSigner]
)
return lastSignatureEvent.sig
} catch (error) {
return null
}
}
if (authUrl) { if (authUrl) {
return ( return (
<iframe <iframe
@ -661,10 +583,9 @@ export const SignPage = () => {
</> </>
)} )}
{submittedBy && zip && meta && ( {submittedBy && zip && (
<> <>
<DisplayMeta <DisplayMeta
meta={meta}
zip={zip} zip={zip}
submittedBy={submittedBy} submittedBy={submittedBy}
signers={signers} signers={signers}
@ -673,7 +594,6 @@ export const SignPage = () => {
currentFileHashes={currentFileHashes} currentFileHashes={currentFileHashes}
signedBy={signedBy} signedBy={signedBy}
nextSigner={nextSinger} nextSigner={nextSinger}
getPrevSignersSig={getPrevSignersSig}
/> />
{signedStatus === SignedStatus.Fully_Signed && ( {signedStatus === SignedStatus.Fully_Signed && (
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}> <Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
@ -698,7 +618,6 @@ export const SignPage = () => {
} }
type DisplayMetaProps = { type DisplayMetaProps = {
meta: Meta
zip: JSZip zip: JSZip
submittedBy: string submittedBy: string
signers: `npub1${string}`[] signers: `npub1${string}`[]
@ -707,11 +626,9 @@ type DisplayMetaProps = {
currentFileHashes: { [key: string]: string | null } currentFileHashes: { [key: string]: string | null }
signedBy: `npub1${string}`[] signedBy: `npub1${string}`[]
nextSigner?: string nextSigner?: string
getPrevSignersSig: (usersNpub: string) => string | null
} }
const DisplayMeta = ({ const DisplayMeta = ({
meta,
zip, zip,
submittedBy, submittedBy,
signers, signers,
@ -719,8 +636,7 @@ const DisplayMeta = ({
creatorFileHashes, creatorFileHashes,
currentFileHashes, currentFileHashes,
signedBy, signedBy,
nextSigner, nextSigner
getPrevSignersSig
}: DisplayMetaProps) => { }: DisplayMetaProps) => {
const theme = useTheme() const theme = useTheme()
@ -868,16 +784,16 @@ const DisplayMeta = ({
> >
{filename} {filename}
</Typography> </Typography>
{isValidHash && ( <Typography
<Tooltip title="File integrity check passed" arrow> component="label"
<CheckCircle sx={{ color: theme.palette.success.light }} /> sx={{
</Tooltip> color: isValidHash
)} ? theme.palette.success.light
{!isValidHash && ( : theme.palette.error.main
<Tooltip title="File integrity check failed" arrow> }}
<Cancel sx={{ color: theme.palette.error.main }} /> >
</Tooltip> {isValidHash ? 'Valid' : 'Invalid'} hash
)} </Typography>
</Box> </Box>
) )
})} })}
@ -893,177 +809,46 @@ const DisplayMeta = ({
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{users.map((user) => ( {users.map((user, index) => {
<DisplayUser const userMeta = metadata[user.pubkey]
key={user.pubkey}
meta={meta} let signedStatus = '-'
user={user}
metadata={metadata} if (user.role === UserRole.signer) {
signedBy={signedBy} // check if user has signed the document
nextSigner={nextSigner} const usersNpub = hexToNpub(user.pubkey)
getPrevSignersSig={getPrevSignersSig} if (signedBy.includes(usersNpub)) {
/> signedStatus = 'Signed'
))} }
// check if user is the next signer
else if (user.pubkey === nextSigner) {
signedStatus = 'Awaiting Signature'
}
}
return (
<TableRow key={index}>
<TableCell className={styles.tableCell}>
<UserComponent
pubkey={user.pubkey}
name={
userMeta?.display_name ||
userMeta?.name ||
shorten(hexToNpub(user.pubkey))
}
image={userMeta?.picture}
/>
</TableCell>
<TableCell className={styles.tableCell}>
{user.role}
</TableCell>
<TableCell>{signedStatus}</TableCell>
</TableRow>
)
})}
</TableBody> </TableBody>
</Table> </Table>
</ListItem> </ListItem>
</List> </List>
) )
} }
enum PrevSignatureValidationEnum {
Pending,
Valid,
Invalid
}
enum UserStatus {
Viewer = 'Viewer',
Awaiting = 'Awaiting Signature',
Signed = 'Signed',
Pending = 'Pending'
}
type DisplayUserProps = {
meta: Meta
user: User
metadata: { [key: string]: ProfileMetadata }
signedBy: `npub1${string}`[]
nextSigner?: string
getPrevSignersSig: (usersNpub: string) => string | null
}
const DisplayUser = ({
meta,
user,
metadata,
signedBy,
nextSigner,
getPrevSignersSig
}: DisplayUserProps) => {
const theme = useTheme()
const userMeta = metadata[user.pubkey]
const [userStatus, setUserStatus] = useState<UserStatus>(UserStatus.Pending)
const [prevSignatureStatus, setPreviousSignatureStatus] =
useState<PrevSignatureValidationEnum>(PrevSignatureValidationEnum.Pending)
useEffect(() => {
if (user.role === UserRole.viewer) {
setUserStatus(UserStatus.Viewer)
return
}
// check if user has signed the document
const usersNpub = hexToNpub(user.pubkey)
if (signedBy.includes(usersNpub)) {
setUserStatus(UserStatus.Signed)
return
}
// check if user is the next signer
if (user.pubkey === nextSigner) {
setUserStatus(UserStatus.Awaiting)
return
}
}, [user, nextSigner, signedBy])
useEffect(() => {
const validatePrevSignature = async () => {
const handleNullCase = () => {
setPreviousSignatureStatus(PrevSignatureValidationEnum.Invalid)
return
}
// get previous signers sig from the content of current signers signed event
const npub = hexToNpub(user.pubkey)
const signedEvent = await parseJson<Event>(
meta.docSignatures[npub]
).catch((err) => {
console.log(`err in parsing the singed event for ${npub}:>> `, err)
toast.error(
err.message ||
'error occurred in parsing the signed event signature event'
)
return null
})
if (!signedEvent) return handleNullCase()
// now that we have signed event of current signer, we'll extract prevSig from its content
const parsedContent = await parseJson<SignedEventContent>(
signedEvent.content
).catch((err) => {
console.log(
`an error occurred in parsing the content of signedEvent of ${npub}`,
err
)
toast.error(
err.message ||
`an error occurred in parsing the content of signedEvent of ${npub}`
)
return null
})
if (!parsedContent) return handleNullCase()
const prevSignersSignature = getPrevSignersSig(npub)
if (!prevSignersSignature) return handleNullCase()
setPreviousSignatureStatus(
parsedContent.prevSig === prevSignersSignature
? PrevSignatureValidationEnum.Valid
: PrevSignatureValidationEnum.Invalid
)
}
if (userStatus === UserStatus.Signed) {
validatePrevSignature()
}
}, [userStatus, meta.docSignatures, user.pubkey, getPrevSignersSig])
return (
<TableRow>
<TableCell className={styles.tableCell}>
<UserComponent
pubkey={user.pubkey}
name={
userMeta?.display_name ||
userMeta?.name ||
shorten(hexToNpub(user.pubkey))
}
image={userMeta?.picture}
/>
</TableCell>
<TableCell className={styles.tableCell}>{user.role}</TableCell>
<TableCell>
<Box sx={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<Typography component="label">{userStatus}</Typography>
{userStatus === UserStatus.Signed && (
<>
{prevSignatureStatus === PrevSignatureValidationEnum.Valid && (
<Tooltip title="Contains valid signature of prev signer" arrow>
<CheckCircle sx={{ color: theme.palette.success.light }} />
</Tooltip>
)}
{prevSignatureStatus === PrevSignatureValidationEnum.Invalid && (
<Tooltip
title="Contains invalid signature of prev signer"
arrow
>
<Cancel sx={{ color: theme.palette.error.main }} />
</Tooltip>
)}
</>
)}
{userStatus === UserStatus.Awaiting && (
<Tooltip title="Waiting for user's sign" arrow>
<HourglassTop />
</Tooltip>
)}
</Box>
</TableCell>
</TableRow>
)
}

View File

@ -4,7 +4,6 @@ import {
List, List,
ListItem, ListItem,
ListSubheader, ListSubheader,
Tooltip,
Typography, Typography,
useTheme useTheme
} from '@mui/material' } from '@mui/material'
@ -16,12 +15,7 @@ import { toast } from 'react-toastify'
import { LoadingSpinner } from '../../components/LoadingSpinner' import { LoadingSpinner } from '../../components/LoadingSpinner'
import { UserComponent } from '../../components/username' import { UserComponent } from '../../components/username'
import { MetadataController } from '../../controllers' import { MetadataController } from '../../controllers'
import { import { CreateSignatureEventContent, Meta, ProfileMetadata } from '../../types'
CreateSignatureEventContent,
Meta,
ProfileMetadata,
SignedEventContent
} from '../../types'
import { import {
getHash, getHash,
hexToNpub, hexToNpub,
@ -31,7 +25,6 @@ import {
shorten shorten
} from '../../utils' } from '../../utils'
import styles from './style.module.scss' import styles from './style.module.scss'
import { Cancel, CheckCircle } from '@mui/icons-material'
export const VerifyPage = () => { export const VerifyPage = () => {
const theme = useTheme() const theme = useTheme()
@ -215,35 +208,6 @@ export const VerifyPage = () => {
setIsLoading(false) setIsLoading(false)
} }
const getPrevSignersSig = (npub: string) => {
if (!meta) return null
// if user is first signer then use creator's signature
if (signers[0] === npub) {
try {
const createSignatureEvent: Event = JSON.parse(meta.createSignature)
return createSignatureEvent.sig
} catch (error) {
return null
}
}
// find the index of signer
const currentSignerIndex = signers.findIndex((signer) => signer === npub)
// return null if could not found user in signer's list
if (currentSignerIndex === -1) return null
// find prev signer
const prevSigner = signers[currentSignerIndex - 1]
// get the signature of prev signer
try {
const prevSignersEvent: Event = JSON.parse(meta.docSignatures[prevSigner])
return prevSignersEvent.sig
} catch (error) {
return null
}
}
const displayUser = (pubkey: string, verifySignature = false) => { const displayUser = (pubkey: string, verifySignature = false) => {
const profile = metadata[pubkey] const profile = metadata[pubkey]
@ -255,27 +219,7 @@ export const VerifyPage = () => {
if (signedEventString) { if (signedEventString) {
try { try {
const signedEvent = JSON.parse(signedEventString) const signedEvent = JSON.parse(signedEventString)
const isVerifiedEvent = verifyEvent(signedEvent) isValidSignature = verifyEvent(signedEvent)
if (isVerifiedEvent) {
// get the actual signature of prev signer
const prevSignersSig = getPrevSignersSig(npub)
// get the signature of prev signer from the content of current signers signedEvent
try {
const obj: SignedEventContent = JSON.parse(signedEvent.content)
if (
obj.prevSig &&
prevSignersSig &&
obj.prevSig === prevSignersSig
) {
isValidSignature = true
}
} catch (error) {
isValidSignature = false
}
}
} catch (error) { } catch (error) {
console.error( console.error(
`An error occurred in parsing and verifying the signature event for ${pubkey}`, `An error occurred in parsing and verifying the signature event for ${pubkey}`,
@ -296,19 +240,16 @@ export const VerifyPage = () => {
/> />
{verifySignature && ( {verifySignature && (
<> <Typography
{isValidSignature && ( component="label"
<Tooltip title="Valid signature"> sx={{
<CheckCircle sx={{ color: theme.palette.success.light }} /> color: isValidSignature
</Tooltip> ? theme.palette.success.light
)} : theme.palette.error.main
}}
{!isValidSignature && ( >
<Tooltip title="Invalid signature"> {isValidSignature ? 'Valid' : 'Invalid'} Signature
<Cancel sx={{ color: theme.palette.error.main }} /> </Typography>
</Tooltip>
)}
</>
)} )}
</> </>
) )
@ -484,20 +425,16 @@ export const VerifyPage = () => {
> >
{filename} {filename}
</Typography> </Typography>
{isValidHash && ( <Typography
<Tooltip title="File integrity check passed" arrow> component="label"
<CheckCircle sx={{
sx={{ color: theme.palette.success.light }} color: isValidHash
/> ? theme.palette.success.light
</Tooltip> : theme.palette.error.main
)} }}
{!isValidHash && ( >
<Tooltip title="File integrity check failed" arrow> {isValidHash ? 'Valid' : 'Invalid'} hash
<Cancel </Typography>
sx={{ color: theme.palette.error.main }}
/>
</Tooltip>
)}
</Box> </Box>
) )
} }

View File

@ -19,7 +19,3 @@ export interface CreateSignatureEventContent {
viewers: `npub1${string}`[] viewers: `npub1${string}`[]
fileHashes: { [key: string]: string } fileHashes: { [key: string]: string }
} }
export interface SignedEventContent {
prevSig: string
}