The six roles, what each may see and do, where those rules are enforced, and how keys and personal data are protected.
Nigraan's rule is that the database is the gate. Each app hides the buttons a role cannot use, but that is a convenience, not the protection. Every permission is enforced by PostgreSQL row-level security and database functions, so it holds for the website, the mobile app and anyone calling the API directly.
| Role | Console tabs on the website | Can do |
|---|---|---|
| Signed out | — | Read guides and approved data. |
public |
— | Submit reports and relief listings; read their own reports at any stage; earn badges. |
validator |
Review queue, Badge reviews | Approve or refuse undecided reports; link duplicates; confirm or decline badges; expire or delete records. |
partner |
Partner tools | Upload datasets; read the Data page. |
institutional |
Institutional dashboard | Approve or refuse undecided reports from the Data page; expire or delete records. |
donor |
Donor dashboard | A read-only view. In the database a donor has exactly a public user's rights. |
admin |
All six tabs | Everything above, plus reversing decisions, changing roles, setting badges and removing accounts. |
The rule for console tabs is in src/lib/roleAccess.ts, and the rule for review actions in src/lib/reviewStages.ts. Both are covered by tests.
| Rule | Enforced by |
|---|---|
| Only approved records are public | RLS on map_records |
| Reporters see their own reports at any stage | RLS on map_records |
| Only a reviewer can set or change a decision | A trigger on map_records, which also covers the record's owner |
| A record is published or refused, never both | A table constraint |
| Badges are reviewed by validators, and set directly only by admins | SECURITY DEFINER functions that check the caller's role |
| Only admins manage users | RLS on profiles, and the admin_remove_user function |
| Official data is written only by the ingest | No write access for app users; the job uses the service key |
| Attachments have a size limit | Column constraints (900,000 characters each) |
The mobile app shows badges but never awards them. Awarding happens only in the website's console.
| Key | Where it lives | Power |
|---|---|---|
| Publishable key | In both apps, by design | None by itself: every request is limited by RLS to what the caller's role allows. |
| Service key | GitHub Actions secrets only | Bypasses RLS. Used by the ingest and facilities jobs. Never in the apps, the repository, chat or screenshots. |
profiles, which only administrators can read. No public page or API call returns them.reporter_attribution() returns a role for them, and names only for staff.If you find a way to see or change data you should not, please do not test it further on the live site. Email info@codeforpakistan.org with what you found.