Skip to content

Commit 7d97282

Browse files
committed
feat(security,db): ✨ Implement Argon2id password service with pepper
- Introduce `password.service.ts` for secure Argon2id hashing with a server-side `PASSWORD_PEPPER` environment variable for defense-in-depth (NIST IA-5). - Add performance indexes to all major database tables (`sessions`, `foia_requests`, `audit_logs`, etc.) via a new Drizzle migration (`0001_performance_indexes.sql`). - Refactor `auth.service.ts` to use the new `passwordService`. - Implement `LRUCache` utility for performance caching (agencies, sessions, IP lookups). - Enhance `pino-logger.ts` with extensive PII redaction paths for compliance (NIST AU-3, NY SHIELD). - Update compliance documentation (control catalog, gap analysis, PII data flow) reflecting these security and performance improvements. - Refactor Astro frontend API client to use Effect-based fetcher utilities from `@foia-stream/shared` for robust error handling and validation. - Introduce `request-queue` in Astro to handle request deduplication and rate limiting.
1 parent 83df068 commit 7d97282

File tree

134 files changed

+8340
-2477
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

134 files changed

+8340
-2477
lines changed

.github/copilot-instructions.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
<!--
2+
Copyright (c) 2025 Foia Stream
3+
4+
Permission is hereby granted, free of charge, to any person obtaining a copy
5+
of this software and associated documentation files (the "Software"), to deal
6+
in the Software without restriction, including without limitation the rights
7+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the Software is
9+
furnished to do so, subject to the following conditions:
10+
11+
The above copyright notice and this permission notice shall be included in all
12+
copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
SOFTWARE.
21+
-->
22+
123
# FOIA Stream - Copilot Instructions
224

325
You are an expert Senior Full-Stack Engineer and Security Compliance Officer working on **FOIA Stream**, a transparency and audit application for public records.

.github/workflows/security.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
# Copyright (c) 2025 Foia Stream
2+
#
3+
# Permission is hereby granted, free of charge, to any person obtaining a copy
4+
# of this software and associated documentation files (the "Software"), to deal
5+
# in the Software without restriction, including without limitation the rights
6+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
# copies of the Software, and to permit persons to whom the Software is
8+
# furnished to do so, subject to the following conditions:
9+
#
10+
# The above copyright notice and this permission notice shall be included in all
11+
# copies or substantial portions of the Software.
12+
#
13+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
# SOFTWARE.
20+
121
# FOIA Stream CI/CD Security Pipeline
222
#
323
# Implements comprehensive security scanning in CI/CD

.gitignore

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
# Copyright (c) 2025 Foia Stream
2+
#
3+
# Permission is hereby granted, free of charge, to any person obtaining a copy
4+
# of this software and associated documentation files (the "Software"), to deal
5+
# in the Software without restriction, including without limitation the rights
6+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
# copies of the Software, and to permit persons to whom the Software is
8+
# furnished to do so, subject to the following conditions:
9+
#
10+
# The above copyright notice and this permission notice shall be included in all
11+
# copies or substantial portions of the Software.
12+
#
13+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
# SOFTWARE.
20+
121
# dependencies (bun install)
222
node_modules
323

@@ -47,5 +67,6 @@ drizzle/meta/
4767

4868
.turbo/
4969
.next/
70+
.drizzle/
5071

5172
./apps/api/data/*

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
<!--
2+
Copyright (c) 2025 Foia Stream
3+
4+
Permission is hereby granted, free of charge, to any person obtaining a copy
5+
of this software and associated documentation files (the "Software"), to deal
6+
in the Software without restriction, including without limitation the rights
7+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the Software is
9+
furnished to do so, subject to the following conditions:
10+
11+
The above copyright notice and this permission notice shall be included in all
12+
copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
SOFTWARE.
21+
-->
22+
123
<!--
224
Generated by AI-Powered README Generator
325
Repository: https://github.com/WomB0ComB0/foia-stream

apps/api/.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ JWT_SECRET=your-super-secret-jwt-key-at-least-32-characters-long
1414
JWT_EXPIRES_IN=7d
1515
DATA_ENCRYPTION_KEY=other-super-secret-key-at-least-32-char-long
1616

17+
# Password Security
18+
# Server-side pepper for password hashing (must be at least 32 characters)
19+
# WARNING: Changing this will invalidate ALL existing passwords!
20+
PASSWORD_PEPPER=your-password-pepper-secret-at-least-32-chars
21+
1722
# File Uploads
1823
UPLOAD_DIR=./uploads
1924
MAX_FILE_SIZE=104857600 # 100MB in bytes

apps/api/data/foia-stream.db-shm

0 Bytes
Binary file not shown.

apps/api/data/foia-stream.db-wal

-2.01 MB
Binary file not shown.

apps/api/drizzle.config.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
/**
2+
* Copyright (c) 2025 Foia Stream
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
* SOFTWARE.
21+
*/
22+
123
import { defineConfig } from 'drizzle-kit';
224

325
export default defineConfig({

apps/api/drizzle/0000_gigantic_longshot.sql

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
-- Copyright (c) 2025 Foia Stream
2+
--
3+
-- Permission is hereby granted, free of charge, to any person obtaining a copy
4+
-- of this software and associated documentation files (the "Software"), to deal
5+
-- in the Software without restriction, including without limitation the rights
6+
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
-- copies of the Software, and to permit persons to whom the Software is
8+
-- furnished to do so, subject to the following conditions:
9+
--
10+
-- The above copyright notice and this permission notice shall be included in all
11+
-- copies or substantial portions of the Software.
12+
--
13+
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
-- SOFTWARE.
20+
121
CREATE TABLE `agencies` (
222
`id` text PRIMARY KEY NOT NULL,
323
`name` text NOT NULL,
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
-- Copyright (c) 2025 Foia Stream
2+
--
3+
-- Permission is hereby granted, free of charge, to any person obtaining a copy
4+
-- of this software and associated documentation files (the "Software"), to deal
5+
-- in the Software without restriction, including without limitation the rights
6+
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
-- copies of the Software, and to permit persons to whom the Software is
8+
-- furnished to do so, subject to the following conditions:
9+
--
10+
-- The above copyright notice and this permission notice shall be included in all
11+
-- copies or substantial portions of the Software.
12+
--
13+
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
-- SOFTWARE.
20+
21+
-- Performance Optimization Migration
22+
-- Adds indexes for common query patterns to prevent N+1 and improve query performance
23+
-- Target: N+3 maximum queries for any operation
24+
25+
-- ============================================
26+
-- Users Table Indexes
27+
-- ============================================
28+
29+
-- Email lookup (login)
30+
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
31+
32+
-- Role filtering (admin views)
33+
CREATE INDEX IF NOT EXISTS idx_users_role ON users(role);
34+
35+
-- Account lockout checks
36+
CREATE INDEX IF NOT EXISTS idx_users_locked_until ON users(locked_until) WHERE locked_until IS NOT NULL;
37+
38+
-- ============================================
39+
-- Sessions Table Indexes
40+
-- ============================================
41+
42+
-- Token lookup (auth middleware - hot path)
43+
CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token);
44+
45+
-- User sessions listing
46+
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
47+
48+
-- Expired session cleanup (data retention job)
49+
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);
50+
51+
-- Composite: user + expiration for active session queries
52+
CREATE INDEX IF NOT EXISTS idx_sessions_user_expires ON sessions(user_id, expires_at);
53+
54+
-- ============================================
55+
-- FOIA Requests Table Indexes (Critical)
56+
-- ============================================
57+
58+
-- User's requests (my requests page)
59+
CREATE INDEX IF NOT EXISTS idx_foia_requests_user_id ON foia_requests(user_id);
60+
61+
-- Agency's requests (agency dashboard)
62+
CREATE INDEX IF NOT EXISTS idx_foia_requests_agency_id ON foia_requests(agency_id);
63+
64+
-- Status filtering (common filter)
65+
CREATE INDEX IF NOT EXISTS idx_foia_requests_status ON foia_requests(status);
66+
67+
-- Category filtering
68+
CREATE INDEX IF NOT EXISTS idx_foia_requests_category ON foia_requests(category);
69+
70+
-- Due date for deadline queries (overdue, upcoming)
71+
CREATE INDEX IF NOT EXISTS idx_foia_requests_due_date ON foia_requests(due_date) WHERE due_date IS NOT NULL;
72+
73+
-- Public requests listing
74+
CREATE INDEX IF NOT EXISTS idx_foia_requests_is_public ON foia_requests(is_public) WHERE is_public = 1;
75+
76+
-- Composite: user + status (my pending requests)
77+
CREATE INDEX IF NOT EXISTS idx_foia_requests_user_status ON foia_requests(user_id, status);
78+
79+
-- Composite: agency + status (agency dashboard filtering)
80+
CREATE INDEX IF NOT EXISTS idx_foia_requests_agency_status ON foia_requests(agency_id, status);
81+
82+
-- Composite: status + due_date (deadline queries)
83+
CREATE INDEX IF NOT EXISTS idx_foia_requests_status_due ON foia_requests(status, due_date);
84+
85+
-- Created at for sorting (most recent)
86+
CREATE INDEX IF NOT EXISTS idx_foia_requests_created_at ON foia_requests(created_at DESC);
87+
88+
-- ============================================
89+
-- Documents Table Indexes
90+
-- ============================================
91+
92+
-- Request's documents
93+
CREATE INDEX IF NOT EXISTS idx_documents_request_id ON documents(request_id);
94+
95+
-- Agency's documents
96+
CREATE INDEX IF NOT EXISTS idx_documents_agency_id ON documents(agency_id);
97+
98+
-- Document type filtering
99+
CREATE INDEX IF NOT EXISTS idx_documents_type ON documents(type);
100+
101+
-- Public documents
102+
CREATE INDEX IF NOT EXISTS idx_documents_is_public ON documents(is_public) WHERE is_public = 1;
103+
104+
-- ============================================
105+
-- Agencies Table Indexes
106+
-- ============================================
107+
108+
-- Jurisdiction filtering
109+
CREATE INDEX IF NOT EXISTS idx_agencies_jurisdiction ON agencies(jurisdiction_level);
110+
111+
-- State filtering
112+
CREATE INDEX IF NOT EXISTS idx_agencies_state ON agencies(state) WHERE state IS NOT NULL;
113+
114+
-- Composite: jurisdiction + state
115+
CREATE INDEX IF NOT EXISTS idx_agencies_jurisdiction_state ON agencies(jurisdiction_level, state);
116+
117+
-- Name search (LIKE queries)
118+
CREATE INDEX IF NOT EXISTS idx_agencies_name ON agencies(name);
119+
120+
-- ============================================
121+
-- Audit Logs Table Indexes (Critical for compliance)
122+
-- ============================================
123+
124+
-- User activity lookup
125+
CREATE INDEX IF NOT EXISTS idx_audit_logs_user_id ON audit_logs(user_id) WHERE user_id IS NOT NULL;
126+
127+
-- Action filtering (security reports)
128+
CREATE INDEX IF NOT EXISTS idx_audit_logs_action ON audit_logs(action);
129+
130+
-- Resource lookup
131+
CREATE INDEX IF NOT EXISTS idx_audit_logs_resource ON audit_logs(resource_type, resource_id);
132+
133+
-- Time-based queries (date range reports)
134+
CREATE INDEX IF NOT EXISTS idx_audit_logs_created_at ON audit_logs(created_at DESC);
135+
136+
-- Composite: action + created_at (security event timeline)
137+
CREATE INDEX IF NOT EXISTS idx_audit_logs_action_time ON audit_logs(action, created_at DESC);
138+
139+
-- Composite: user + action (user activity report)
140+
CREATE INDEX IF NOT EXISTS idx_audit_logs_user_action ON audit_logs(user_id, action);
141+
142+
-- Security events only (fast security dashboard queries)
143+
CREATE INDEX IF NOT EXISTS idx_audit_logs_security ON audit_logs(action, created_at DESC)
144+
WHERE action LIKE 'security_%';
145+
146+
-- ============================================
147+
-- Comments Table Indexes
148+
-- ============================================
149+
150+
-- Document comments
151+
CREATE INDEX IF NOT EXISTS idx_comments_document_id ON comments(document_id);
152+
153+
-- User's comments
154+
CREATE INDEX IF NOT EXISTS idx_comments_user_id ON comments(user_id);
155+
156+
-- Comment type filtering
157+
CREATE INDEX IF NOT EXISTS idx_comments_type ON comments(type);
158+
159+
-- ============================================
160+
-- Appeals Table Indexes
161+
-- ============================================
162+
163+
-- Request's appeals
164+
CREATE INDEX IF NOT EXISTS idx_appeals_request_id ON appeals(request_id);
165+
166+
-- User's appeals
167+
CREATE INDEX IF NOT EXISTS idx_appeals_user_id ON appeals(user_id);
168+
169+
-- Appeal status
170+
CREATE INDEX IF NOT EXISTS idx_appeals_status ON appeals(status);
171+
172+
-- ============================================
173+
-- API Keys Table Indexes
174+
-- ============================================
175+
176+
-- Key lookup (auth)
177+
CREATE INDEX IF NOT EXISTS idx_api_keys_key_hash ON api_keys(key_hash);
178+
179+
-- User's API keys
180+
CREATE INDEX IF NOT EXISTS idx_api_keys_user_id ON api_keys(user_id);
181+
182+
-- ============================================
183+
-- Request Templates Table Indexes
184+
-- ============================================
185+
186+
-- Category filtering
187+
CREATE INDEX IF NOT EXISTS idx_templates_category ON request_templates(category);
188+
189+
-- Official templates
190+
CREATE INDEX IF NOT EXISTS idx_templates_official ON request_templates(is_official) WHERE is_official = 1;
191+
192+
-- Jurisdiction filtering
193+
CREATE INDEX IF NOT EXISTS idx_templates_jurisdiction ON request_templates(jurisdiction_level) WHERE jurisdiction_level IS NOT NULL;
194+
195+
-- Usage ranking
196+
CREATE INDEX IF NOT EXISTS idx_templates_usage ON request_templates(usage_count DESC);
197+
198+
-- ============================================
199+
-- Knowledge Articles Table Indexes
200+
-- ============================================
201+
202+
-- Category browsing
203+
CREATE INDEX IF NOT EXISTS idx_articles_category ON knowledge_articles(category);
204+
205+
-- Published articles only
206+
CREATE INDEX IF NOT EXISTS idx_articles_published ON knowledge_articles(is_published) WHERE is_published = 1;
207+
208+
-- State-specific guides
209+
CREATE INDEX IF NOT EXISTS idx_articles_state ON knowledge_articles(state) WHERE state IS NOT NULL;
210+
211+
-- Popular articles
212+
CREATE INDEX IF NOT EXISTS idx_articles_views ON knowledge_articles(view_count DESC);
213+
214+
-- ============================================
215+
-- Stats Tables Indexes
216+
-- ============================================
217+
218+
-- Use of force by agency and year
219+
CREATE INDEX IF NOT EXISTS idx_uof_stats_agency_year ON use_of_force_stats(agency_id, year);

0 commit comments

Comments
 (0)