Skip to content

Commit

Permalink
feat(multi part upload): added more descriptive error messages and fi…
Browse files Browse the repository at this point in the history
…xed test cases
  • Loading branch information
ctrlc03 authored and 0xjei committed Mar 21, 2023
1 parent 5e12eb1 commit b59ad73
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 46 deletions.
58 changes: 39 additions & 19 deletions packages/actions/test/unit/security.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ import {
cleanUpMockParticipant,
getStorageConfiguration,
sleep,
deleteBucket
deleteBucket,
deleteObjectFromS3
} from "../utils"
import {
commonTerms,
formatZkeyIndex,
generateGetObjectPreSignedUrl,
genesisZkeyIndex,
getBucketName,
getCurrentFirebaseAuthUser,
getZkeyStorageFilePath,
Expand Down Expand Up @@ -272,6 +272,7 @@ describe("Security", () => {
const ceremonyContributor = fakeCeremoniesData.fakeCeremonyOpenedDynamic
const circuitsNotCurrentContributor = fakeCircuitsData.fakeCircuitSmallContributors
const bucketName = getBucketName(ceremonyContributor.data.prefix!, ceremonyBucketPostfix)
let storagePath: string = ""
beforeAll(async () => {
// we need the pre conditions to meet
await createMockCeremony(adminFirestore, ceremonyNotContributor, circuitsNotCurrentContributor)
Expand All @@ -281,6 +282,10 @@ describe("Security", () => {
await signInWithEmailAndPassword(userAuth, users[2].data.email, passwords[2])
await createS3Bucket(userFunctions, bucketName)
await sleep(2000)
storagePath = getZkeyStorageFilePath(
circuitsCurrentContributor.data.prefix!,
`${circuitsCurrentContributor.data.prefix}_${formatZkeyIndex(1)}.zkey`
)
})

afterAll(async () => {
Expand All @@ -289,6 +294,7 @@ describe("Security", () => {
await cleanUpMockCeremony(adminFirestore, ceremonyContributor.uid, circuitsCurrentContributor.uid)
await cleanUpMockParticipant(adminFirestore, ceremonyNotContributor.uid, users[0].uid)
await cleanUpMockParticipant(adminFirestore, ceremonyContributor.uid, users[0].uid)
await deleteObjectFromS3(bucketName, storagePath)
await deleteBucket(bucketName)
})

Expand All @@ -309,40 +315,54 @@ describe("Security", () => {
}
})

await expect(
openMultiPartUpload(
userFunctions,
getBucketName(ceremonyContributor.data.prefix!, ceremonyBucketPostfix),
getZkeyStorageFilePath(
circuitsCurrentContributor.data.prefix!,
`${circuitsCurrentContributor.data.prefix}_${formatZkeyIndex(1)}.zkey`
),
ceremonyContributor.uid
)
).to.be.fulfilled
await expect(openMultiPartUpload(userFunctions, bucketName, storagePath, ceremonyContributor.uid)).to.be
.fulfilled
})
it("should revert when the user is not a contributor for this ceremony circuit", async () => {
await signInWithEmailAndPassword(userAuth, users[0].data.email, passwords[0])
await expect(
openMultiPartUpload(
userFunctions,
getBucketName(ceremonyNotContributor.data.prefix!, ceremonyBucketPostfix),
`${circuitsNotCurrentContributor.data.prefix}_${genesisZkeyIndex}.zkey`,
storagePath,
ceremonyNotContributor.uid
)
).to.be.rejectedWith(
"Unable to interact with a multi-part upload (start, create pre-signed urls or complete)."
)
})
it("should fail when the user is trying to upload a file to a bucket not part of a ceremony", async () => {
await signInWithEmailAndPassword(userAuth, users[0].data.email, passwords[0])
it("should revert when the user is trying to upload a file with the wrong storage path", async () => {
await adminFirestore
.collection(getCircuitsCollectionPath(ceremonyContributor.uid))
.doc(circuitsCurrentContributor.uid)
.set({
prefix: circuitsCurrentContributor.data.prefix,
waitingQueue: {
completedContributions: 0,
contributors: [users[0].uid, users[1].uid],
currentContributor: users[0].uid, // fake user 1
failedContributions: 0
}
})

await expect(
openMultiPartUpload(
userFunctions,
"not-a-ceremony-bucket",
`${circuitsNotCurrentContributor.data.prefix}_${formatZkeyIndex(1)}.zkey`,
ceremonyNotContributor.uid
getBucketName(ceremonyContributor.data.prefix!, ceremonyBucketPostfix),
getZkeyStorageFilePath(
circuitsCurrentContributor.data.prefix!,
`${circuitsCurrentContributor.data.prefix}_${formatZkeyIndex(2)}.zkey`
),
ceremonyContributor.uid
)
).to.be.rejectedWith(
"Unable to interact with a multi-part upload (start, create pre-signed urls or complete)."
)
})
it("should fail when the user is trying to upload a file to a bucket not part of a ceremony", async () => {
await signInWithEmailAndPassword(userAuth, users[0].data.email, passwords[0])
await expect(
openMultiPartUpload(userFunctions, "not-a-ceremony-bucket", storagePath, ceremonyNotContributor.uid)
// @todo discuss whether this error name should be changed to be more general?
).to.be.rejectedWith("Unable to generate a pre-signed url for the given object in the provided bucket.")
})
Expand Down
57 changes: 30 additions & 27 deletions packages/backend/src/functions/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,35 +63,38 @@ const checkUploadingFileValidity = async (contributorId: string, ceremonyId: str
// Get the circuits for the ceremony
const circuits = await getCeremonyCircuits(ceremonyId)

// We need to have at least 1 circuit
if (circuits.length === 0) logAndThrowError(SPECIFIC_ERRORS.SE_CONTRIBUTE_NO_CEREMONY_CIRCUITS)

// Loop through the circuits until we find the one we are contributing to
for (const circuit of circuits) {
// Extract the data we need
const { prefix, waitingQueue } = circuit.data()!
const { completedContributions, currentContributor } = waitingQueue

// If we are not a contributor to this circuit, continue looping
if (currentContributor === contributorId) {
// Get the index of the zKey
const contributorZKeyIndex = formatZkeyIndex(completedContributions + 1)
// The uploaded file must be the expected one
const zkeyNameContributor = `${prefix}_${contributorZKeyIndex}.zkey`
const contributorZKeyStoragePath = getZkeyStorageFilePath(prefix, zkeyNameContributor)

// If the object key is not one of the two zkeys, throw an error
if (objectKey !== contributorZKeyStoragePath) {
logAndThrowError(SPECIFIC_ERRORS.SE_STORAGE_CANNOT_INTERACT_WITH_MULTI_PART_UPLOAD)
}
// Get the participant document
const participantDoc = await getDocumentById(getParticipantsCollectionPath(ceremonyId), contributorId!)
const participantData = participantDoc.data()

// void return if we found a match and the contributor can upload the zkey
return
if (!participantData) logAndThrowError(COMMON_ERRORS.CM_INEXISTENT_DOCUMENT_DATA)

// The index of the circuit will be the contribution progress - 1
const index = participantData?.contributionProgress
// If the index is zero the user is not the current contributor
if (index === 0) logAndThrowError(SPECIFIC_ERRORS.SE_STORAGE_CANNOT_INTERACT_WITH_MULTI_PART_UPLOAD)
// We can safely use index - 1
const circuit = circuits.at(index - 1)

// If the circuit is undefined, throw an error
if (circuit === undefined) logAndThrowError(SPECIFIC_ERRORS.SE_STORAGE_CANNOT_INTERACT_WITH_MULTI_PART_UPLOAD)
// Extract the data we need
const { prefix, waitingQueue } = circuit!.data()
const { completedContributions, currentContributor } = waitingQueue

// If we are not a contributor to this circuit then we cannot upload files
if (currentContributor === contributorId) {
// Get the index of the zKey
const contributorZKeyIndex = formatZkeyIndex(completedContributions + 1)
// The uploaded file must be the expected one
const zkeyNameContributor = `${prefix}_${contributorZKeyIndex}.zkey`
const contributorZKeyStoragePath = getZkeyStorageFilePath(prefix, zkeyNameContributor)

// If the object key is not one of the two zkeys, throw an error
if (objectKey !== contributorZKeyStoragePath) {
logAndThrowError(SPECIFIC_ERRORS.SE_STORAGE_WRONG_OBJECT_KEY)
}
}

// if there was no match for the current contributor, then throw an error
logAndThrowError(SPECIFIC_ERRORS.SE_STORAGE_CANNOT_INTERACT_WITH_MULTI_PART_UPLOAD)
} else logAndThrowError(SPECIFIC_ERRORS.SE_STORAGE_CANNOT_INTERACT_WITH_MULTI_PART_UPLOAD)
}

/**
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/lib/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ export const SPECIFIC_ERRORS = {
"Unable to generate a pre-signed url for the given object in the provided bucket.",
"The bucket is not associated with any valid ceremony document on the Firestore database."
),
SE_STORAGE_WRONG_OBJECT_KEY: makeError(
"failed-precondition",
"Unable to interact with a multi-part upload (start, create pre-signed urls or complete).",
"The object key provided does not match the expected one."
),
SE_STORAGE_CANNOT_INTERACT_WITH_MULTI_PART_UPLOAD: makeError(
"failed-precondition",
"Unable to interact with a multi-part upload (start, create pre-signed urls or complete).",
Expand Down

0 comments on commit b59ad73

Please sign in to comment.