Given that this project uses CMS Medicare data — the same category of data that falls under HIPAA in production environments — I treated security as a first-class concern. I implemented role-based access control, row-level security, and trigger-based audit logging covering authentication, authorization, data visibility, and audit trail.
Every role has exactly the privileges it needs — nothing more. I created four group roles representing distinct job functions, then four login users assigned to those roles. No application user has superuser privileges. No user can escalate their own access.
I enabled Row-Level Security on cms.providers and created a user_state_access table that maps users to the states they are permitted to see. The app_user account is restricted to Virginia and Maryland only. This restriction is enforced transparently by the database engine — the user cannot bypass it by changing how their query is written.
| Policy | Role | Rule |
|---|---|---|
| app_state_access | app_readonly | Rows where state_abbr matches user's assigned states |
| analyst_full_access | analyst | All rows visible |
| dba_full_access | dba_admin | All rows, all commands |
I created a dedicated audit schema with a data_access_log table that captures every INSERT, UPDATE, and DELETE on sensitive tables. The trigger function uses SECURITY DEFINER so it runs with the privileges of the function owner regardless of which user triggers it — meaning no user can bypass the audit log by manipulating their own permissions.
Each audit record captures: timestamp to microsecond precision, database user, client IP address, application name, schema and table affected, operation type, and full before/after data stored as JSONB.
| Column | Type | Purpose |
|---|---|---|
| log_timestamp | TIMESTAMPTZ | Microsecond-precision event time |
| db_user | VARCHAR | PostgreSQL user who made the change |
| client_addr | INET | IP address of the connection |
| application | VARCHAR | Application name from connection string |
| operation | VARCHAR | INSERT, UPDATE, or DELETE |
| old_data | JSONB | Complete before-state for UPDATE/DELETE |
| new_data | JSONB | Complete after-state for INSERT/UPDATE |
Five phases. Schema design, performance baselining, optimization, backup and recovery, and security. Every decision documented, every result measured, every script versioned. The full source code and documentation are on GitHub.