Skip to content

Commit

Permalink
feat(firestore-create-game): add game creation rules and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
albertodigioacchino committed Jan 14, 2021
1 parent 56a0f67 commit 83cef7c
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 22 deletions.
49 changes: 49 additions & 0 deletions packages/firestore/firestore.rules
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,45 @@ service cloud.firestore {
allow write: if false;
}

match /cards/{cardId} {
allow read: if isAuthenticated();
}

match /decks/{deckId} {
allow read: if isAuthenticated();
}

match /users/{userId} {
allow read: if userId == authUserUid();
allow create: if userId == authUserUid() && isValidUserCreate();
}

match /games/{gameId} {
allow read: if isAuthenticated();
allow create: if isAuthenticated() && isValidGameCreate();
}

function isValidGameCreate() {
let facilitatorId = authUserUid();
let gameData = getDataToWrite();
let scenarioCard = getDocumentData(/cards/$(gameData.scenarioCardId));

return gameData.keys().hasOnly(['scenarioTitle', 'scenarioContent', 'scenarioCardId', 'deckId', 'facilitator', 'createdAt']) &&
(
(gameData.scenarioCardId != null && exists(/databases/$(database)/documents/cards/$(gameData.scenarioCardId)) &&
getDocumentData(/cards/$(gameData.scenarioCardId)).title == gameData.scenarioTitle &&
getDocumentData(/cards/$(gameData.scenarioCardId)).content == gameData.scenarioContent)
||
(gameData.scenarioCardId == null &&
isStringShorterThan(gameData.scenarioTitle, 100) &&
isStringShorterThan(gameData.scenarioContent, 3000))
) &&
exists(/databases/$(database)/documents/decks/$(gameData.deckId)) &&
isMapBigAs(gameData.facilitator, 1) &&
isServerCreationTime(gameData.createdAt) &&
facilitatorId == gameData.facilitator.id;
}

function isValidUserCreate() {
let userData = getDataToWrite();
let allowedRoles = getDocumentData(/dynamicData/gameRoles).roles;
Expand All @@ -28,6 +62,17 @@ service cloud.firestore {
&& allowedMaturities.hasAny([userData.devOpsMaturity]);
}

function isMapBigAs(val, size) {
return val is map && val.size() == size;
}

function isStringShorterThan(val, len) {
return val is string && val.size() < len;
}

function isServerCreationTime(val) {
return val == request.time;
}

function getDocumentData(documentPath) {
return get(/databases/$(database)/documents/$(documentPath)).data;
Expand All @@ -41,5 +86,9 @@ service cloud.firestore {
return request.auth.uid;
}

function isAuthenticated() {
return request.auth != null && request.auth.token.email_verified;
}

}
}
2 changes: 1 addition & 1 deletion packages/firestore/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"private": true,
"scripts": {
"test": "mocha -r ts-node/register test/*.spec.ts",
"test:local": "npx env-cmd -f .env mocha -r ts-node/register -r tsconfig-paths/register test/*.spec.ts"
"test:local": "npx env-cmd -f .env mocha -r ts-node/register test/*.spec.ts --timeout 5000"
},
"devDependencies": {
"@firebase/rules-unit-testing": "^1.1.6",
Expand Down
26 changes: 8 additions & 18 deletions packages/firestore/test/create-user.spec.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
import * as firebase from "@firebase/rules-unit-testing";
import {reinitializeFirestore} from "./utils";
import {getAuthedFirestore, reinitializeFirestore} from "./utils";
import {FirebaseCollections, FirebaseDocs} from '@pipeline/common/build/cjs'

const PROJECT_ID = "firestore-emulator-example-" + Math.floor(Math.random() * 1000);


const COVERAGE_URL = `http://${process.env.FIRESTORE_EMULATOR_HOST}/emulator/v1/projects/${PROJECT_ID}:ruleCoverage.html`;

type Auth = Parameters<typeof firebase.initializeTestApp>[0]['auth'];

/**
* Creates a new client FirebaseApp with authentication and returns the Firestore instance.
*/
function getAuthedFirestore(auth: Auth) {
return firebase
.initializeTestApp({projectId: PROJECT_ID, auth})
.firestore();
}


beforeEach(async () => {
Expand All @@ -31,7 +21,7 @@ after(async () => {
describe("User create", () => {

it("should not allow user creation if not authenticated", async () => {
const db = getAuthedFirestore(undefined);
const db = getAuthedFirestore(PROJECT_ID, undefined);
const profile = db.collection(FirebaseCollections.Users).doc("alice");
const rolesDoc = await db.doc(`${FirebaseCollections.DynamicData}/${FirebaseDocs.GameRoles}`).get();
const maturitiesDoc = await db.doc(`${FirebaseCollections.DynamicData}/${FirebaseDocs.DevOpsMaturities}`).get();
Expand All @@ -47,7 +37,7 @@ describe("User create", () => {
it("should allow user creation with correct data", async () => {
const userUID = 'test';
const email = 'test@email.com';
const db = getAuthedFirestore({uid: 'test', email});
const db = getAuthedFirestore(PROJECT_ID, {uid: 'test', email});
const profile = db.collection(FirebaseCollections.Users).doc(userUID);
const rolesDoc = await db.doc(`${FirebaseCollections.DynamicData}/${FirebaseDocs.GameRoles}`).get();
const maturitiesDoc = await db.doc(`${FirebaseCollections.DynamicData}/${FirebaseDocs.DevOpsMaturities}`).get();
Expand All @@ -63,7 +53,7 @@ describe("User create", () => {
it("should not allow user creation with invalid role", async () => {
const userUID = 'test';
const email = 'test@email.com';
const db = getAuthedFirestore({uid: 'test', email});
const db = getAuthedFirestore(PROJECT_ID, {uid: 'test', email});
const profile = db.collection(FirebaseCollections.Users).doc(userUID);
const maturitiesDoc = await db.doc(`${FirebaseCollections.DynamicData}/${FirebaseDocs.DevOpsMaturities}`).get();
const realMaturity = maturitiesDoc.data().maturities[0];
Expand All @@ -77,7 +67,7 @@ describe("User create", () => {
it("should not allow user creation with invalid maturity", async () => {
const userUID = 'test';
const email = 'test@email.com';
const db = getAuthedFirestore({uid: 'test', email});
const db = getAuthedFirestore(PROJECT_ID, {uid: 'test', email});
const profile = db.collection(FirebaseCollections.Users).doc(userUID);
const rolesDoc = await db.doc(`${FirebaseCollections.DynamicData}/${FirebaseDocs.GameRoles}`).get();
const realRole = rolesDoc.data().roles[0];
Expand All @@ -91,7 +81,7 @@ describe("User create", () => {
it("should not allow a user creation if the authenticated request has an email different from the user that is being created", async () => {
const userUID = 'test';
const email = 'test@email.com';
const db = getAuthedFirestore({uid: 'test', email});
const db = getAuthedFirestore(PROJECT_ID, {uid: 'test', email});
const profile = db.collection(FirebaseCollections.Users).doc(userUID);
const rolesDoc = await db.doc(`${FirebaseCollections.DynamicData}/${FirebaseDocs.GameRoles}`).get();
const maturitiesDoc = await db.doc(`${FirebaseCollections.DynamicData}/${FirebaseDocs.DevOpsMaturities}`).get();
Expand All @@ -107,7 +97,7 @@ describe("User create", () => {
it("should not allow user creation if the request contains unexpected fields", async () => {
const userUID = 'test';
const email = 'test@email.com';
const db = getAuthedFirestore({uid: 'test', email});
const db = getAuthedFirestore(PROJECT_ID, {uid: 'test', email});
const profile = db.collection(FirebaseCollections.Users).doc(userUID);
const rolesDoc = await db.doc(`${FirebaseCollections.DynamicData}/${FirebaseDocs.GameRoles}`).get();
const maturitiesDoc = await db.doc(`${FirebaseCollections.DynamicData}/${FirebaseDocs.DevOpsMaturities}`).get();
Expand All @@ -124,7 +114,7 @@ describe("User create", () => {
it("should not allow user creation if the request does not contain all fields", async () => {
const userUID = 'test';
const email = 'test@email.com';
const db = getAuthedFirestore({uid: 'test', email});
const db = getAuthedFirestore(PROJECT_ID, {uid: 'test', email});
const profile = db.collection(FirebaseCollections.Users).doc(userUID);
const rolesDoc = await db.doc(`${FirebaseCollections.DynamicData}/${FirebaseDocs.GameRoles}`).get();
const realRole = rolesDoc.data().roles[0];
Expand Down
19 changes: 16 additions & 3 deletions packages/firestore/test/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ const collectionObjects = files.map((file) => {
* @param projectId
*/
export async function reinitializeFirestore(projectId: string) {
await firebase.clearFirestoreData({projectId});
await loadData(projectId);
await firebase.loadFirestoreRules({projectId, rules});
try {
await firebase.clearFirestoreData({projectId});
await loadData(projectId);
await firebase.loadFirestoreRules({projectId, rules});
} catch (e) {
console.error(e);
}

}

Expand All @@ -50,3 +54,12 @@ async function loadData(projectId: string) {

await batch.commit();
}

type Auth = Parameters<typeof firebase.initializeTestApp>[0]['auth'];

/**
* Creates a new client FirebaseApp with authentication and returns the Firestore instance.
*/
export function getAuthedFirestore(projectId: string, auth: Auth) {
return firebase.initializeTestApp({ projectId, auth }).firestore();
}

0 comments on commit 83cef7c

Please sign in to comment.