Add full application: receipt scanning, auth, billing, and account deletion
Brings the working codebase (Next.js app, auth system, Stripe billing, Docker/deploy config, tests, docs) into version control on top of the placeholder initial commit, and adds account self-deletion (Danger Zone in Settings, password + typed-email confirmation, cascading DB cleanup, Stripe cancellation) per GDPR right-to-erasure. Excludes local build caches, node_modules, and internal agent scratch files; .gitignore hardened to keep those out going forward. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
94
scripts/db-permissions.sql
Normal file
94
scripts/db-permissions.sql
Normal file
@@ -0,0 +1,94 @@
|
||||
-- ============================================================================
|
||||
-- Database least-privilege provisioning — receipt scanner app
|
||||
-- ============================================================================
|
||||
-- Creates the runtime role `receipt_app` and grants it ONLY the privileges the
|
||||
-- application needs at runtime:
|
||||
--
|
||||
-- * CONNECT on the database
|
||||
-- * USAGE on the public schema — explicitly NO CREATE (no DDL of any kind)
|
||||
-- * SELECT / INSERT / UPDATE / DELETE on all tables in public
|
||||
-- * USAGE / SELECT on all sequences in public
|
||||
-- * the same DML grants as DEFAULT PRIVILEGES, so tables/sequences created
|
||||
-- by the owner during later migrations are covered automatically
|
||||
--
|
||||
-- The role is deliberately NOT a superuser and has no CREATEDB / CREATEROLE /
|
||||
-- CREATE-on-schema rights: if the application is compromised, the attacker's
|
||||
-- database blast radius is limited to reading and modifying rows. They cannot
|
||||
-- create/drop tables, alter the schema, or grant themselves more rights.
|
||||
--
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- Who must run this? The database owner or a superuser.
|
||||
-- * `receipt_user` (docker-compose's POSTGRES_USER / the owner URL) KEEPS all
|
||||
-- of its current rights — including DDL for migrations and schema init —
|
||||
-- and is the only role this script ever grants privileges to (besides the
|
||||
-- new runtime role). Nothing here weakens the owner.
|
||||
-- * REVOKE CREATE ON SCHEMA public FROM PUBLIC requires the schema owner or
|
||||
-- a superuser. The official postgres image's /docker-entrypoint-initdb.d
|
||||
-- scripts run as POSTGRES_USER, so that path satisfies this.
|
||||
--
|
||||
-- How is it executed?
|
||||
-- * Fresh database / volume: docker-compose mounts this file into
|
||||
-- /docker-entrypoint-initdb.d/10-db-permissions.sql; the postgres image
|
||||
-- runs it once, before the app starts, as POSTGRES_USER. (Init scripts do
|
||||
-- NOT run on an existing volume.)
|
||||
-- * Existing database (e.g. the local dev DB): `node scripts/apply-db-permissions.mjs`
|
||||
--
|
||||
-- Idempotent: safe to run any number of times. CREATE ROLE is guarded by an
|
||||
-- existence check; every GRANT / REVOKE / ALTER DEFAULT PRIVILEGES is a no-op
|
||||
-- when already applied.
|
||||
--
|
||||
-- Runtime role password:
|
||||
-- The default below (receipt_app_secure_password) matches the docker-compose
|
||||
-- default APP_DATABASE_PASSWORD and is for LOCAL DEVELOPMENT only. For
|
||||
-- production, choose a strong password:
|
||||
-- * docker init path: edit the CREATE ROLE statement below before first
|
||||
-- volume creation (this file runs as-is under psql), or
|
||||
-- * apply-script path: set APP_DATABASE_PASSWORD when running
|
||||
-- scripts/apply-db-permissions.mjs — the script substitutes that value
|
||||
-- and rotates the password of an already-existing role (ALTER ROLE).
|
||||
-- ============================================================================
|
||||
|
||||
-- 1. Create the least-privilege runtime role (guarded -> re-runnable).
|
||||
DO $dbpermissions$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'receipt_app') THEN
|
||||
CREATE ROLE receipt_app
|
||||
LOGIN
|
||||
PASSWORD 'receipt_app_secure_password' -- documented default, see header
|
||||
NOSUPERUSER
|
||||
NOCREATEDB
|
||||
NOCREATEROLE;
|
||||
END IF;
|
||||
END
|
||||
$dbpermissions$;
|
||||
|
||||
-- 2. Allow the app to connect. The database name matches POSTGRES_DB /
|
||||
-- the DATABASE_URL database (receipt_scanner); adjust if you use a
|
||||
-- different database name.
|
||||
GRANT CONNECT ON DATABASE receipt_scanner TO receipt_app;
|
||||
|
||||
-- 3. Schema access: strip anything pre-existing, then grant USAGE only.
|
||||
-- NOTE: CREATE is deliberately NOT granted — receipt_app can never create,
|
||||
-- alter or drop schema objects (no DDL).
|
||||
REVOKE ALL ON SCHEMA public FROM receipt_app;
|
||||
GRANT USAGE ON SCHEMA public TO receipt_app;
|
||||
|
||||
-- 4. DML on the tables / sequences that exist right now. (On a fresh database
|
||||
-- this grants nothing yet — step 5 covers the objects the owner creates
|
||||
-- during migrations.)
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO receipt_app;
|
||||
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO receipt_app;
|
||||
|
||||
-- 5. Future objects: when the owner (receipt_user) creates tables / sequences
|
||||
-- during later migrations, the runtime role gets the same DML automatically.
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO receipt_app;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
||||
GRANT USAGE, SELECT ON SEQUENCES TO receipt_app;
|
||||
|
||||
-- 6. Harden the schema: take CREATE away from the PUBLIC pseudo-role so no
|
||||
-- other database user can create objects in public either. Must be run by
|
||||
-- the schema owner / a superuser (see header). The owner (receipt_user) is
|
||||
-- unaffected — it keeps full rights through pg_database_owner — so
|
||||
-- migrations and schema init keep working.
|
||||
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
|
||||
Reference in New Issue
Block a user