Skip to content

Commit ad615ed

Browse files
committed
fix: security scan
1 parent 1ad4431 commit ad615ed

5 files changed

Lines changed: 729 additions & 24 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ __pycache__/
1111
.pytest_cache/
1212
.mypy_cache/
1313
.ruff_cache/
14+
.cache/
1415

1516
# Environment variables (secret keys)
1617
.env

Makefile

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,7 @@ import-check:
5252

5353
security-scan:
5454
@echo "[make:security-scan] Running dependency vulnerability scan"
55-
@if ! command -v pip-audit >/dev/null 2>&1; then \
56-
echo "[make:security-scan] pip-audit is not installed. Install it with: pip install pip-audit"; \
57-
exit 1; \
58-
fi
59-
@pip-audit
55+
@PIP_CACHE_DIR=.cache/pip $(POETRY) run pip-audit --cache-dir .cache/pip-audit
6056

6157
check: test lint import-check
6258
@echo "[make:check] All checks completed"

README.md

Lines changed: 86 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,24 @@ The API is currently versioned under `/api/v1`.
3434
## Features
3535

3636
- User registration.
37-
- User login with JWT access tokens.
37+
- User login with JWT access and refresh tokens.
38+
- Refresh token rotation and logout/token revocation.
3839
- Authentication middleware that validates bearer tokens.
40+
- RBAC authorization with Casbin-backed roles and permissions.
3941
- Current-user lookup from authenticated request state.
4042
- Todo creation.
41-
- Todo listing by authenticated user.
43+
- Cursor-paginated todo listing by authenticated user.
4244
- Todo update with ownership checks.
4345
- Todo delete with ownership checks.
46+
- Role and permission management APIs.
47+
- Redis-backed rate limiting.
48+
- Request ID, structured logging, audit logging, and security headers.
49+
- Request size limiting and idempotency support.
50+
- Health, liveness, and readiness endpoints.
4451
- API route grouping under `/api/v1`.
4552
- Async SQLAlchemy persistence.
4653
- Alembic database migrations.
54+
- Database seeders for authorization data and optional users.
4755
- Pytest regression tests.
4856
- Ruff linting.
4957

@@ -59,6 +67,8 @@ The API is currently versioned under `/api/v1`.
5967
- Pydantic and Pydantic Settings
6068
- Python JOSE for JWT handling
6169
- Passlib for password hashing
70+
- Casbin for authorization policies
71+
- Redis and fastapi-limiter for rate limiting and token revocation
6272
- Pytest
6373
- Ruff
6474
- Poetry
@@ -73,14 +83,22 @@ The API is currently versioned under `/api/v1`.
7383
│ ├── main.py # FastAPI application entrypoint
7484
│ ├── core/
7585
│ │ ├── bootstrap/ # Application bootstrap helpers
86+
│ │ ├── authorization/ # RBAC permissions, Casbin services, auth models
7687
│ │ ├── config/ # Runtime settings
77-
│ │ ├── database/ # SQLAlchemy async session setup
78-
│ │ ├── middleware/ # Authentication middleware
88+
│ │ ├── database/ # PostgreSQL and Redis connection setup
89+
│ │ ├── dependency/ # Shared FastAPI dependencies
90+
│ │ ├── exceptions/ # Exception registration and handlers
91+
│ │ ├── middleware/ # Auth, audit, security, logging, request middleware
7992
│ │ ├── routers/ # API router composition
80-
│ │ ├── security/ # JWT and password helpers
81-
│ │ ├── di.py # Shared dependency helpers
93+
│ │ ├── schemas/ # Shared response schemas
94+
│ │ ├── security/ # JWT, password, revocation, audit helpers
95+
│ │ ├── seed/ # Database seeding orchestration
96+
│ │ ├── utils/ # Cursor pagination helpers
8297
│ │ └── lifespan.py # FastAPI lifespan hook
8398
│ ├── modules/
99+
│ │ ├── authorization/
100+
│ │ │ ├── domain/ # Role and permission entities
101+
│ │ │ └── presenter/ # Role and permission routers/schemas
84102
│ │ ├── user/
85103
│ │ │ ├── application/ # User commands, queries, handlers
86104
│ │ │ ├── domain/ # User entity, exceptions, repository port
@@ -99,14 +117,14 @@ The API is currently versioned under `/api/v1`.
99117
├── poetry.lock # Poetry lock file
100118
├── alembic.ini # Alembic configuration
101119
├── Dockerfile # Docker image definition
102-
└── docker-compose.yml # Local API and PostgreSQL services
120+
└── docker-compose.yml # Local API, PostgreSQL, and Redis services
103121
```
104122

105123
## Architecture
106124

107125
### Modulith
108126

109-
This is a single deployable application with module boundaries inside the codebase. The `user` and `todo` modules are independent feature areas under `src/modules`.
127+
This is a single deployable application with module boundaries inside the codebase. The `user`, `todo`, and `authorization` modules are independent feature areas under `src/modules`.
110128

111129
### Clean Architecture Direction
112130

@@ -168,7 +186,7 @@ POST /api/v1/auth/login
168186
Request with Authorization: Bearer <token>
169187
-> AuthenticationMiddleware validates JWT
170188
-> request.state.user_id is set
171-
-> route dependency resolves current user
189+
-> route dependency checks role permission
172190
-> todo handler executes use case
173191
-> TodoRepository port
174192
-> SQLAlchemyTodoRepository
@@ -188,17 +206,35 @@ Current routes:
188206
```text
189207
POST /api/v1/auth/register
190208
POST /api/v1/auth/login
209+
POST /api/v1/auth/refresh
191210
GET /api/v1/auth/me
211+
POST /api/v1/auth/logout
192212
POST /api/v1/todos/
193-
GET /api/v1/todos/
213+
GET /api/v1/todos/?cursor=<cursor>&limit=10
194214
PATCH /api/v1/todos/{todo_id}
195215
DELETE /api/v1/todos/{todo_id}
216+
POST /api/v1/roles/
217+
GET /api/v1/roles/?cursor=<cursor>&limit=10
218+
GET /api/v1/roles/{role_id}
219+
PATCH /api/v1/roles/{role_id}
220+
DELETE /api/v1/roles/{role_id}
221+
POST /api/v1/roles/{role_id}/permissions/{permission_id}
222+
DELETE /api/v1/roles/{role_id}/permissions/{permission_id}
223+
POST /api/v1/permissions/
224+
GET /api/v1/permissions/?cursor=<cursor>&limit=10
225+
GET /api/v1/permissions/{permission_id}
226+
PATCH /api/v1/permissions/{permission_id}
227+
DELETE /api/v1/permissions/{permission_id}
196228
GET /health
229+
GET /live
230+
GET /ready
197231
```
198232

199233
Public routes:
200234

201235
- `/health`
236+
- `/live`
237+
- `/ready`
202238
- `/docs`
203239
- `/redoc`
204240
- `/openapi.json`
@@ -211,6 +247,8 @@ Protected routes require:
211247
Authorization: Bearer <access_token>
212248
```
213249

250+
Swagger UI, ReDoc, and OpenAPI JSON are disabled when `APP_ENV=production`.
251+
214252
## Prerequisites
215253

216254
- Python `3.14` or compatible with the project constraint.
@@ -230,18 +268,39 @@ Expected values:
230268

231269
```env
232270
APP_NAME=Todo Modulith API
271+
APP_ENV=production
233272
POSTGRES_USER=postgres
234273
POSTGRES_PASSWORD=
235274
POSTGRES_DB=todo_db
236275
REDIS_PASSWORD=
237276
DATABASE_URL=
277+
DATABASE_POOL_SIZE=20
278+
DATABASE_MAX_OVERFLOW=10
279+
DATABASE_POOL_TIMEOUT=30
280+
DATABASE_POOL_RECYCLE=3600
238281
REDIS_URL=
239282
SECRET_KEY=
283+
MAX_REQUEST_SIZE_MB=5242880
240284
ALGORITHM=HS256
241285
JWT_ISSUER=todo-modulith-api
242286
JWT_AUDIENCE=todo-modulith-client
243287
ACCESS_TOKEN_EXPIRE_MINUTES=30
244288
REFRESH_TOKEN_EXPIRE_MINUTES=10080
289+
RATE_LIMIT=100/minute
290+
CORS_ALLOW_ORIGINS=http://localhost:3000
291+
CORS_ALLOW_METHODS=*
292+
CORS_ALLOW_HEADERS=*
293+
SECURITY_CONTENT_SECURITY_POLICY=default-src 'self'; frame-ancestors 'none'
294+
IDEMPOTENCY_TTL_SECONDS=86400
295+
ACCOUNT_LOCKOUT_MAX_ATTEMPTS=5
296+
ACCOUNT_LOCKOUT_WINDOW_MINUTES=15
297+
ACCOUNT_LOCKOUT_DURATION_MINUTES=15
298+
LOG_FORMAT=json
299+
SEED_ADMIN_EMAIL=
300+
SEED_ADMIN_PASSWORD=
301+
SEED_ADMIN_USERNAME=admin
302+
SEED_ADMIN_FULLNAME=System Administrator
303+
SEED_DEVELOPMENT_USERS_PASSWORD=
245304
```
246305

247306
For local development without Docker, point `DATABASE_URL` at your local PostgreSQL host, for example:
@@ -298,6 +357,13 @@ Health check:
298357
http://localhost:8000/health
299358
```
300359

360+
Operational checks:
361+
362+
```text
363+
http://localhost:8000/live
364+
http://localhost:8000/ready
365+
```
366+
301367
## Database and Migrations
302368

303369
Alembic is configured in:
@@ -394,6 +460,12 @@ Current check set:
394460
- `ruff check src tests scripts`
395461
- import check for `src.main`
396462

463+
Dependency scanning is available separately:
464+
465+
```bash
466+
make security-scan
467+
```
468+
397469
## Makefile Commands
398470

399471
```bash
@@ -403,6 +475,7 @@ make run
403475
make test
404476
make lint
405477
make import-check
478+
make security-scan
406479
make check
407480
make migrate
408481
make seed
@@ -416,7 +489,9 @@ make clean
416489

417490
## Docker Notes
418491

419-
Run database and API services:
492+
Before starting Docker Compose, set non-empty `POSTGRES_PASSWORD`, `REDIS_PASSWORD`, and `SECRET_KEY` in `.env`. Compose intentionally fails fast when database or Redis passwords are missing.
493+
494+
Run API, PostgreSQL, and Redis services:
420495

421496
```bash
422497
make db-up
@@ -434,8 +509,6 @@ Follow service logs:
434509
make db-logs
435510
```
436511

437-
Known Dockerfile note: `Dockerfile` currently references `start.sh`, while the actual script is under `scripts/start.sh`. If you plan to rely on Docker builds, align those paths first.
438-
439512
## Development Guide
440513

441514
### Adding a New Use Case
@@ -569,5 +642,4 @@ Legend: `Implemented` means code exists in the repository. `Partial` means code
569642

570643
- `src/core/lifespan.py` still calls `Base.metadata.create_all`; with Alembic in place, production environments normally rely on migrations instead.
571644
- The project has a Pydantic v2 deprecation warning for class-based settings config.
572-
- The Dockerfile start script path needs alignment before relying on Docker builds.
573645
- The current architecture is clean enough for a learning modulith, but some flows can be made stricter by moving remaining business orchestration out of routers and into application handlers.

0 commit comments

Comments
 (0)