mirror of
https://github.com/privacy-scaling-explorations/p0tion.git
synced 2026-04-21 03:00:07 -04:00
Merge branch 'dev' into feat/githubsybil
This commit is contained in:
@@ -1,28 +1,32 @@
|
||||
import chai, { assert, expect } from "chai"
|
||||
import chaiAsPromised from "chai-as-promised"
|
||||
import { getAuth, signInWithEmailAndPassword, signOut } from "firebase/auth"
|
||||
import { getAuth, signOut, signInWithEmailAndPassword } from "firebase/auth"
|
||||
import { where } from "firebase/firestore"
|
||||
import { fakeCeremoniesData, fakeUsersData } from "../data/samples"
|
||||
import {
|
||||
createNewFirebaseUserWithEmailAndPw,
|
||||
deleteAdminApp,
|
||||
envType,
|
||||
generatePseudoRandomStringOfNumbers,
|
||||
initializeAdminServices,
|
||||
initializeUserServices,
|
||||
sleep,
|
||||
setCustomClaims
|
||||
} from "../utils"
|
||||
import { fakeUsersData } from "../data/samples"
|
||||
import { getCurrentFirebaseAuthUser } from "../../src"
|
||||
import { commonTerms, generateGetObjectPreSignedUrl, getCurrentFirebaseAuthUser } from "../../src"
|
||||
import { TestingEnvironment } from "../../src/types/enums"
|
||||
import { getDocumentById, queryCollection } from "../../src/helpers/database"
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
|
||||
describe("Security rules", () => {
|
||||
// Init admin services.
|
||||
/**
|
||||
* Test suite for the security rules and vulnerabilities fixes.
|
||||
*/
|
||||
describe("Security", () => {
|
||||
// Global config
|
||||
const { adminFirestore, adminAuth } = initializeAdminServices()
|
||||
const { userApp, userFirestore } = initializeUserServices()
|
||||
const { userApp, userFunctions, userFirestore } = initializeUserServices()
|
||||
const userAuth = getAuth(userApp)
|
||||
|
||||
const user1 = fakeUsersData.fakeUser1
|
||||
const user2 = fakeUsersData.fakeUser2
|
||||
const user3 = fakeUsersData.fakeUser3
|
||||
@@ -58,43 +62,88 @@ describe("Security rules", () => {
|
||||
await setCustomClaims(adminAuth, user3.uid, { coordinator: true })
|
||||
})
|
||||
|
||||
it("should allow a user to retrieve their own data from the firestore db", async () => {
|
||||
// login as user1
|
||||
await signInWithEmailAndPassword(userAuth, user1.data.email, user1Pwd)
|
||||
const userDoc = await getDocumentById(userFirestore, "users", user1.uid)
|
||||
expect(userDoc.data()).to.not.be.null
|
||||
describe("GeneratePreSignedURL", () => {
|
||||
it("should throw when given a bucket name that is not used for a ceremony", async () => {
|
||||
assert.isRejected(generateGetObjectPreSignedUrl(userFunctions, "nonExistent", "test"))
|
||||
})
|
||||
|
||||
// the emulator should run without .env file thus this test would not work.
|
||||
if (envType === TestingEnvironment.PRODUCTION) {
|
||||
it("should return a pre-signed URL when given the bucket name for a ceremony", async () => {
|
||||
// Create the mock data on Firestore.
|
||||
await adminFirestore
|
||||
.collection(commonTerms.collections.ceremonies.name)
|
||||
.doc(fakeCeremoniesData.fakeCeremonyOpenedFixed.uid)
|
||||
.set({
|
||||
...fakeCeremoniesData.fakeCeremonyOpenedFixed.data
|
||||
})
|
||||
|
||||
const url = await generateGetObjectPreSignedUrl(
|
||||
userFunctions,
|
||||
fakeCeremoniesData.fakeCeremonyOpenedFixed.data.prefix,
|
||||
"anObject"
|
||||
)
|
||||
/* eslint-disable no-useless-escape */
|
||||
const regex =
|
||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/
|
||||
expect(url).to.match(regex)
|
||||
})
|
||||
}
|
||||
|
||||
it("should throw when called without being authenticated", async () => {
|
||||
await signOut(getAuth(userApp))
|
||||
assert.isRejected(generateGetObjectPreSignedUrl(userFunctions, "nonExistent", "test"))
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
if (envType === TestingEnvironment.PRODUCTION)
|
||||
// Delete the ceremony.
|
||||
await adminFirestore
|
||||
.collection(commonTerms.collections.ceremonies.name)
|
||||
.doc(fakeCeremoniesData.fakeCeremonyOpenedFixed.uid)
|
||||
.delete()
|
||||
})
|
||||
})
|
||||
|
||||
it("should allow any authenticated user to query the ceremony collection", async () => {
|
||||
// login as user2
|
||||
await signInWithEmailAndPassword(userAuth, user2.data.email, user2Pwd)
|
||||
// query the ceremonies collection
|
||||
expect(await queryCollection(userFirestore, "ceremonies", [where("description", "!=", "")])).to.not.throw
|
||||
})
|
||||
|
||||
it("should throw an error if a coordiantor tries to read another user's document", async () => {
|
||||
// login as coordinator
|
||||
await signInWithEmailAndPassword(userAuth, user3.data.email, user3Pwd)
|
||||
// retrieve the document of another user
|
||||
assert.isRejected(getDocumentById(userFirestore, "users", user1.uid))
|
||||
})
|
||||
|
||||
it("should throw an error if an authenticated user tries to read another user's data", async () => {
|
||||
// login as user2
|
||||
await signInWithEmailAndPassword(userAuth, user2.data.email, user2Pwd)
|
||||
// @todo debug should return the error message "Missing or insufficient permissions."
|
||||
// below should fail because we are trying to retrieve a document from another user.
|
||||
// expect(getDocumentById(userFirestore, "users", user1.uid)).to.be.rejectedWith(
|
||||
// "Missing or insufficient permissions."
|
||||
// )
|
||||
assert.isRejected(getDocumentById(userFirestore, "users", user1.uid))
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
// Make sure to sign out.
|
||||
await signOut(userAuth)
|
||||
describe("Security rules", () => {
|
||||
it("should allow a user to retrieve their own data from the firestore db", async () => {
|
||||
// login as user1
|
||||
await signInWithEmailAndPassword(userAuth, user1.data.email, user1Pwd)
|
||||
const userDoc = await getDocumentById(userFirestore, commonTerms.collections.users.name, user1.uid)
|
||||
expect(userDoc.data()).to.not.be.null
|
||||
})
|
||||
|
||||
it("should allow any authenticated user to query the ceremony collection", async () => {
|
||||
// login as user2
|
||||
await signInWithEmailAndPassword(userAuth, user2.data.email, user2Pwd)
|
||||
// query the ceremonies collection
|
||||
expect(
|
||||
await queryCollection(userFirestore, commonTerms.collections.ceremonies.name, [
|
||||
where(commonTerms.collections.ceremonies.fields.description, "!=", "")
|
||||
])
|
||||
).to.not.throw
|
||||
})
|
||||
|
||||
it("should throw an error if a coordiantor tries to read another user's document", async () => {
|
||||
// login as coordinator
|
||||
await signInWithEmailAndPassword(userAuth, user3.data.email, user3Pwd)
|
||||
// retrieve the document of another user
|
||||
assert.isRejected(getDocumentById(userFirestore, commonTerms.collections.users.name, user1.uid))
|
||||
})
|
||||
|
||||
it("should throw an error if an authenticated user tries to read another user's data", async () => {
|
||||
// login as user2
|
||||
await signInWithEmailAndPassword(userAuth, user2.data.email, user2Pwd)
|
||||
assert.isRejected(getDocumentById(userFirestore, commonTerms.collections.users.name, user1.uid))
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
// Make sure to sign out.
|
||||
await signOut(userAuth)
|
||||
})
|
||||
})
|
||||
|
||||
// general clean up after all tests
|
||||
afterAll(async () => {
|
||||
// Clean user from DB.
|
||||
await adminFirestore.collection("users").doc(user1.uid).delete()
|
||||
|
||||
@@ -18,4 +18,6 @@ AWS_PRESIGNED_URL_EXPIRATION="900"
|
||||
# nb. right now, only one user could be a coordinator for all ceremonies deployed within the same instance.
|
||||
# Note that the email should be visible from third-party (e.g., Github public email).
|
||||
# @todo allow to use a custom domain to allow multiple coordinators.
|
||||
CUSTOM_CLAIMS_COORDINATOR_EMAIL_ADDRESS_OR_DOMAIN="YOUR-EMAIL-ADDRESS-OR-DOMAIN"
|
||||
CUSTOM_CLAIMS_COORDINATOR_EMAIL_ADDRESS_OR_DOMAIN="YOUR-EMAIL-ADDRESS-OR-DOMAIN"
|
||||
# The postfix that is used to create S3 buckets for storing the ceremonies data.
|
||||
CONFIG_CEREMONY_BUCKET_POSTFIX="-ph2-ceremony"
|
||||
@@ -106,22 +106,50 @@ export const checkIfObjectExist = functions.https.onCall(
|
||||
/**
|
||||
* Generate a new AWS S3 pre signed url to upload/download an object (GET).
|
||||
*/
|
||||
export const generateGetObjectPreSignedUrl = functions.https.onCall(async (data: any): Promise<any> => {
|
||||
if (!data.bucketName || !data.objectKey) logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
|
||||
export const generateGetObjectPreSignedUrl = functions.https.onCall(
|
||||
async (data: any, context: functions.https.CallableContext): Promise<any> => {
|
||||
if (!process.env.CONFIG_CEREMONY_BUCKET_POSTFIX) throw new Error(GENERIC_ERRORS.GENERR_WRONG_ENV_CONFIGURATION)
|
||||
// requires auth
|
||||
if (!context.auth) logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
|
||||
|
||||
// Connect w/ S3.
|
||||
const S3 = await getS3Client()
|
||||
if (!data.bucketName || !data.objectKey) logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
|
||||
|
||||
// Prepare the command.
|
||||
const command = new GetObjectCommand({ Bucket: data.bucketName, Key: data.objectKey })
|
||||
// extract the bucket name and object key from the data
|
||||
const { objectKey, bucketName } = data
|
||||
|
||||
// Get the PreSignedUrl.
|
||||
const url = await getSignedUrl(S3, command, { expiresIn: Number(process.env.AWS_PRESIGNED_URL_EXPIRATION!) })
|
||||
// get the firestore database
|
||||
const firestoreDatabase = admin.firestore()
|
||||
|
||||
logMsg(`Single Pre-Signed URL ${url}`, MsgType.LOG)
|
||||
// need to get the ceremony prefix from the bucket name
|
||||
const ceremonyPrefix = bucketName.replace(process.env.CONFIG_CEREMONY_BUCKET_POSTFIX!, "")
|
||||
|
||||
return url
|
||||
})
|
||||
// query the collection
|
||||
const ceremonyCollection = await firestoreDatabase
|
||||
.collection(commonTerms.collections.ceremonies.name)
|
||||
.where("prefix", "==", ceremonyPrefix)
|
||||
.get()
|
||||
|
||||
// if there is no collection with this name then we return
|
||||
if (ceremonyCollection.empty)
|
||||
logMsg(
|
||||
`Cannot get pre-signed url for this object: ${objectKey} in bucket: ${bucketName} because it does not belong to any ceremony.`,
|
||||
MsgType.ERROR
|
||||
)
|
||||
|
||||
// Connect w/ S3.
|
||||
const S3 = await getS3Client()
|
||||
|
||||
// Prepare the command.
|
||||
const command = new GetObjectCommand({ Bucket: bucketName, Key: objectKey })
|
||||
|
||||
// Get the PreSignedUrl.
|
||||
const url = await getSignedUrl(S3, command, { expiresIn: Number(process.env.AWS_PRESIGNED_URL_EXPIRATION!) })
|
||||
|
||||
logMsg(`Single Pre-Signed URL ${url}`, MsgType.LOG)
|
||||
|
||||
return url
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Initiate a multi part upload for a specific object in AWS S3 bucket.
|
||||
|
||||
Reference in New Issue
Block a user