Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions firebase.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"firestore": {
"rules": "firestore.rules"
}
}
38 changes: 35 additions & 3 deletions firestore.rules
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ service cloud.firestore {
&& (!('roast' in data) || (data.roast is string && data.roast.size() <= 8000))
&& (!('roastBrutal' in data) || (data.roastBrutal is string && data.roastBrutal.size() <= 8000))
&& (!('roastMild' in data) || (data.roastMild is string && data.roastMild.size() <= 8000))
// extraTagsByCategory: mapa de categoria → lista de nomes de skills criadas pelo usuário
&& (!('extraTagsByCategory' in data) || data.extraTagsByCategory is map)
Comment on lines +62 to +63

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Validate the contents of extraTagsByCategory, not just that it is a map.

The PR contract here is category → list of user-created skill names, but this rule accepts any nested structure. Malformed profile data can persist numbers or objects under a category and then break the onboarding code that reads these values as string arrays.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@firestore.rules` around lines 62 - 63, The validation for extraTagsByCategory
in the Firestore rules is too permissive because it only checks that the field
is a map. Update the rule around the extraTagsByCategory constraint to also
verify that each category value is an array of strings (user-created skill
names), not arbitrary nested data. Keep the check localized to the existing
profile validation logic so malformed onboarding data cannot be stored.

&& data.skills is map
&& data.skills.keys().hasAll(['frontend', 'backend', 'ux_ui', 'dados', 'hardware_android', 'vibe_coding'])
&& data.skills.frontend is number && data.skills.backend is number
Expand All @@ -71,6 +73,37 @@ service cloud.firestore {
&& data.canvas.veto is list && data.canvas.veto.size() <= 50;
}

// ─────────────────────────────────────────────
// SKILLS
// Catálogo global de skills. Qualquer usuário
// autenticado pode ler. Criação permitida apenas
// para skills com campos obrigatórios válidos.
// Edição e deleção bloqueadas para usuários comuns.
// ─────────────────────────────────────────────
function isValidSkill(data) {
return data.keys().hasAll(['id', 'name', 'normalizedName', 'status', 'usageCount', 'createdBy', 'createdAt'])
&& data.id is string && data.id.size() > 0 && data.id.size() <= 128
&& data.name is string && data.name.size() > 0 && data.name.size() <= 100
&& data.normalizedName is string && data.normalizedName.size() > 0 && data.normalizedName.size() <= 100
&& data.status is string && data.status in ['pending', 'approved', 'rejected']
&& data.usageCount is number && data.usageCount >= 0
&& data.createdBy is string
&& (!('category' in data) || data.category == null || (data.category is string && data.category.size() <= 50));
Comment on lines +83 to +91

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Lock down the /skills schema before opening client writes.

isValidSkill() only enforces a minimum field set. Any authenticated client can still add arbitrary extra fields, send a non-canonical id, and bypass the Skill timestamp contract because createdAt is never validated. FirebaseSkillRepository then casts that payload straight back to Skill.

Also applies to: 99-101

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@firestore.rules` around lines 83 - 91, Lock down isValidSkill() before
enabling client writes: it currently only checks a required subset and still
allows extra fields, unvalidated createdAt, and a potentially non-canonical id.
Update the validation in firestore.rules so isValidSkill enforces an exact /
skills document shape, validates createdAt as a proper timestamp, and ensures id
matches the canonical skill identifier expected by FirebaseSkillRepository. Also
apply the same stricter checks to the related skill update/create rule block
referenced in the review.

}

match /skills/{skillId} {
// Leitura pública para usuários autenticados (autocomplete)
allow read: if isSignedIn();

// Criação: usuário autenticado, skill válida, ID determinístico = normalizedName
allow create: if isSignedIn()
&& skillId == incoming().normalizedName
&& isValidSkill(incoming());

// Atualização e deleção: bloqueadas para usuários comuns (apenas admin via console)
allow update, delete: if false;
}

match /test/connection {
allow read: if true;
}
Expand Down Expand Up @@ -110,15 +143,15 @@ service cloud.firestore {
&& profileId == request.auth.uid
&& isValidProfile(incoming())
&& incoming().createdAt == request.time
&& incoming().keys().hasOnly(['userId', 'name', 'photoURL', 'github', 'linkedin', 'bio', 'primaryRole', 'secondaryRoles', 'skills', 'canvas', 'status', 'squadId', 'eventId', 'roast', 'roastBrutal', 'roastMild', 'createdAt', 'updatedAt', 'visibility']);
&& incoming().keys().hasOnly(['userId', 'name', 'photoURL', 'github', 'linkedin', 'bio', 'primaryRole', 'secondaryRoles', 'skills', 'canvas', 'status', 'squadId', 'eventId', 'roast', 'roastBrutal', 'roastMild', 'createdAt', 'updatedAt', 'visibility', 'extraTagsByCategory']);

allow update: if isSignedIn()
&& isValidId(profileId)
&& isValidProfile(incoming())
&& incoming().userId == existing().userId
&& incoming().createdAt == existing().createdAt
&& (
(incoming().diff(existing()).affectedKeys().hasOnly(['skills', 'canvas', 'name', 'photoURL', 'github', 'linkedin', 'bio', 'primaryRole', 'secondaryRoles', 'status', 'squadId', 'eventId', 'updatedAt', 'roast', 'roastBrutal', 'roastMild', 'visibility']))
(incoming().diff(existing()).affectedKeys().hasOnly(['skills', 'canvas', 'name', 'photoURL', 'github', 'linkedin', 'bio', 'primaryRole', 'secondaryRoles', 'status', 'squadId', 'eventId', 'updatedAt', 'roast', 'roastBrutal', 'roastMild', 'visibility', 'extraTagsByCategory']))
Comment on lines +146 to +154

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Don't whitelist visibility until isValidProfile() constrains it.

These lines allow visibility on profile create/update, but the profile validator never checks its type or allowed values. Right now any authenticated client can store arbitrary data in that field.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@firestore.rules` around lines 146 - 154, The profile rules currently
whitelist visibility in both the create and update paths, but isValidProfile()
does not validate that field. Update the firestore.rules profile validation so
isValidProfile() explicitly constrains visibility to the expected type/allowed
values, then keep the visibility key only if that validator enforces it;
otherwise remove visibility from the incoming().keys().hasOnly and
diff().affectedKeys().hasOnly checks.

||
false
);
Expand Down Expand Up @@ -171,7 +204,6 @@ service cloud.firestore {
&& isValidMessage(incoming())
&& incoming().createdAt == request.time;

// Allow listing for conversation thread queries (by conversationId, senderId or receiverId)
allow list: if isSignedIn()
&& (
resource.data.receiverId == request.auth.uid
Expand Down
22 changes: 22 additions & 0 deletions get-rules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import "dotenv/config";
import * as admin from "firebase-admin";

const serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT!);
const dbId = process.env.VITE_FIREBASE_FIRESTORE_DATABASE_ID!;

admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
Comment on lines +4 to +9

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard env parsing and app init inside the try.

A missing or malformed FIREBASE_SERVICE_ACCOUNT throws before your catch runs, so the script exits with an uncaught error instead of a clear diagnostic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@get-rules.ts` around lines 4 - 9, The Firebase setup currently parses
FIREBASE_SERVICE_ACCOUNT and initializes admin before the try/catch can handle
failures, so move the env parsing and admin.initializeApp setup inside the
existing try block in get-rules.ts. Keep the
JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT!) and
admin.credential.cert(serviceAccount) usage together with the rest of the
initialization, and let the catch report missing or malformed configuration
through the existing error handling path.


async function run() {
const securityRules = admin.securityRules();
Comment on lines +11 to +12

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does firebase-admin 14.1.0 expose a top-level securityRules() API for retrieving Firestore security rules?

💡 Result:

Yes, firebase-admin 14.1.0 provides a top-level securityRules API that allows you to access the SecurityRules service for managing and retrieving Firestore security rules [1][2][3]. You can access this service by calling admin.securityRules [2][3]. Once you have the SecurityRules instance, you can use the getFirestoreRuleset method to retrieve the ruleset currently applied to Cloud Firestore [4][5][6]. Example usage: const admin = require('firebase-admin'); admin.initializeApp; // Retrieve the currently applied Firestore ruleset admin.securityRules.getFirestoreRuleset.then((ruleset) => { console.log('Current Firestore ruleset:', ruleset); }).catch((error) => { console.error('Error retrieving ruleset:', error); }); This API was originally introduced in earlier versions of the SDK [1][7] and remains available in version 14.1.0 [8][9].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the target file.
git ls-files | rg '^get-rules\.ts$|/get-rules\.ts$|^.*get-rules\.ts$' || true
if [ -f get-rules.ts ]; then
  wc -l get-rules.ts
  cat -n get-rules.ts
fi

# Check the firebase-admin version declared in package files, if present.
rg -n '"firebase-admin"|firebase-admin' package.json package-lock.json pnpm-lock.yaml yarn.lock --no-heading || true

# Look for other uses of securityRules in the repo.
rg -n 'securityRules\(' . --no-heading || true

Repository: MatchDock/match-tech

Length of output: 2302


🌐 Web query:

firebase-admin 14.1.0 securityRules is it a function or a property? show TypeScript definitions or docs for admin.securityRules usage

💡 Result:

In the Firebase Admin SDK, admin.securityRules() is a function, not a property [1][2]. When you call admin.securityRules(), it returns an instance of the SecurityRules class, which provides the service interface for managing Cloud Firestore and Cloud Storage security rules [3][1]. TypeScript Definition The function signature for admin.securityRules() is defined as follows: export declare function getSecurityRules(app?: App): SecurityRules; Usage You call the function to obtain the service instance, which you then use to manage your rules. Example (Basic): const securityRules = admin.securityRules; await securityRules.releaseFirestoreRulesetFromSource(source); Example (Specific App): const otherSecurityRules = admin.securityRules(otherApp); The SecurityRules class itself contains various methods (such as getFirestoreRuleset, createRuleset, and releaseFirestoreRulesetFromSource) that you use to programmatically manage your rulesets [3][4]. Note that this service is specifically for Cloud Firestore and Cloud Storage; for Realtime Database rules, you instead use the getRules and setRules methods available on the admin.database() service [5][6].

Citations:


Call a ruleset method on the returned service get-rules.ts:12 The securityRules() call is valid, but the returned service is never used, so this script still doesn’t retrieve or print any Firestore rules.

🧰 Tools
🪛 ESLint

[error] 12-12: 'securityRules' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@get-rules.ts` around lines 11 - 12, The run() flow in get-rules.ts creates
the securityRules service but never uses it, so the script does not actually
fetch or display the Firestore rules. Update run() to call the appropriate
ruleset method on the service returned by admin.securityRules(), then print or
otherwise output the retrieved rules so the script performs the intended action.

try {
// The Rules API handles generic rules, let's try to get Firestore rules
// Using the REST API directly is easier since the SDK might not expose getRules for specific DBs easily.
console.log("Database ID from env:", dbId);
} catch(e) {
console.error(e);
}
}

run();
Loading
Loading