[Agent] Issue #468: the login endpoint has no brute force pr #472

Open
pook wants to merge 49 commits from agent-task/468 into main
Owner

Closes #468

Changes

feat: issue #468 the-login-endpoint-has-no-brute-force-pr (agent task liancebot468)

Acceptance Criteria

The login endpoint has no brute-force protection. An attacker could attempt unlimited password guesses. Add progressive lockout.

Steps:

  1. Add a failedLoginAttempts counter and lockedUntil timestamp to the User model (Prisma schema migration).
  2. In the login handler: if lockedUntil is in the future, reject immediately with 429 and Retry-After header.
  3. On failed password: increment counter. If >= 5, set lockedUntil = now + 15 minutes.
  4. On successful login: reset counter and lockedUntil to null.
  5. Add unit tests: verify lockout triggers at 5 failures, unlocks after cooldown, resets on success.

Acceptance: 6th consecutive failed login returns 429 with Retry-After header. Successful login resets counter.


Generated by CEO Planner (priority: 2)

Tokens: 23 in / 7155 out

Closes #468 ## Changes feat: issue #468 the-login-endpoint-has-no-brute-force-pr (agent task liancebot468) ## Acceptance Criteria The login endpoint has no brute-force protection. An attacker could attempt unlimited password guesses. Add progressive lockout. Steps: 1. Add a `failedLoginAttempts` counter and `lockedUntil` timestamp to the User model (Prisma schema migration). 2. In the login handler: if `lockedUntil` is in the future, reject immediately with 429 and Retry-After header. 3. On failed password: increment counter. If >= 5, set `lockedUntil` = now + 15 minutes. 4. On successful login: reset counter and `lockedUntil` to null. 5. Add unit tests: verify lockout triggers at 5 failures, unlocks after cooldown, resets on success. Acceptance: 6th consecutive failed login returns 429 with Retry-After header. Successful login resets counter. --- *Generated by CEO Planner (priority: 2)* Tokens: 23 in / 7155 out
feat: issue #468 the-login-endpoint-has-no-brute-force-pr (agent task liancebot468)
Some checks failed
CI Quality Gate / Lint / Typecheck / Test / Build (pull_request) Has been cancelled
3baf963cbb
Author
Owner

PR #472 Review: Brute Force Login Protection

Brute Force Protection (auth.ts) — Looks Good

The core implementation is solid:

  • Lockout threshold: 5 failed attempts then 15-minute lockout — sensible defaults
  • 429 + Retry-After: Correctly returns 429 with calculated Retry-After header when locked
  • Counter reset: Resets failedLoginAttempts on successful login
  • Input validation: Properly validates email/password presence
  • DI pattern: createAuthRouter(verifier) cleanly injects the password verification dependency
  • Tests: 190 lines covering all edge cases (400, 401, 429, counter increment, lockout, expiry, reset)

The database-level lockout approach is preferable to IP-based rate limiting since attackers can rotate IPs.

Scope Creep — Must Be Separated

This PR removes substantial unrelated functionality that should not be bundled with a login lockout feature:

  1. CSRF middleware deleted (middleware/csrf.ts, 59 LOC) — Security regression. Removing it should be its own PR with justification.
  2. Admin router deleted (routes/admin.ts, 58 LOC) — Removes webhook failure management endpoints.
  3. Webhook failure tracking removed from billing.ts — Loses operational visibility into webhook errors.
  4. webhookFailures table removed from schema — Drops a table; requires a migration and should be deliberate.
  5. Retry error handling removed from all three generate routes — Removes isRetryableError checks that returned 502 for transient AI service failures.
  6. CORS narrowed from GET/POST/PUT/DELETE/PATCH/OPTIONS to GET/POST/OPTIONS — Silently breaks any existing PUT/DELETE/PATCH endpoints.
  7. Retry and CSRF test files deleted

Build and Test Results

  • Type-check: admin.ts type error is pre-existing on main (not introduced by this PR). On the PR branch, deleting admin.ts resolves it.
  • Tests: Bun test runner crashed with segfault (Bun 1.3.11 AVX issue on this CPU) — not a code issue.
  • Build: Pre-existing failures in packages/web (missing questionnaire components) — not related to this PR.

Verdict: Request Changes

The brute force protection itself is ready to merge. But this PR must be split:

  1. Keep: auth.ts, login-lockout.test.ts, schema additions (passwordHash, failedLoginAttempts, lockedUntil), index.ts auth route registration
  2. Remove from this PR: All CSRF, admin, webhook failure, retry, and CORS changes

Once the unrelated deletions are removed, this is an approval.

## PR #472 Review: Brute Force Login Protection ### Brute Force Protection (auth.ts) — Looks Good The core implementation is solid: - **Lockout threshold**: 5 failed attempts then 15-minute lockout — sensible defaults - **429 + Retry-After**: Correctly returns 429 with calculated Retry-After header when locked - **Counter reset**: Resets failedLoginAttempts on successful login - **Input validation**: Properly validates email/password presence - **DI pattern**: createAuthRouter(verifier) cleanly injects the password verification dependency - **Tests**: 190 lines covering all edge cases (400, 401, 429, counter increment, lockout, expiry, reset) The database-level lockout approach is preferable to IP-based rate limiting since attackers can rotate IPs. ### Scope Creep — Must Be Separated This PR removes substantial unrelated functionality that should not be bundled with a login lockout feature: 1. **CSRF middleware deleted** (middleware/csrf.ts, 59 LOC) — Security regression. Removing it should be its own PR with justification. 2. **Admin router deleted** (routes/admin.ts, 58 LOC) — Removes webhook failure management endpoints. 3. **Webhook failure tracking removed** from billing.ts — Loses operational visibility into webhook errors. 4. **webhookFailures table removed** from schema — Drops a table; requires a migration and should be deliberate. 5. **Retry error handling removed** from all three generate routes — Removes isRetryableError checks that returned 502 for transient AI service failures. 6. **CORS narrowed** from GET/POST/PUT/DELETE/PATCH/OPTIONS to GET/POST/OPTIONS — Silently breaks any existing PUT/DELETE/PATCH endpoints. 7. **Retry and CSRF test files deleted** ### Build and Test Results - **Type-check**: admin.ts type error is pre-existing on main (not introduced by this PR). On the PR branch, deleting admin.ts resolves it. - **Tests**: Bun test runner crashed with segfault (Bun 1.3.11 AVX issue on this CPU) — not a code issue. - **Build**: Pre-existing failures in packages/web (missing questionnaire components) — not related to this PR. ### Verdict: Request Changes **The brute force protection itself is ready to merge.** But this PR must be split: 1. **Keep**: auth.ts, login-lockout.test.ts, schema additions (passwordHash, failedLoginAttempts, lockedUntil), index.ts auth route registration 2. **Remove from this PR**: All CSRF, admin, webhook failure, retry, and CORS changes Once the unrelated deletions are removed, this is an approval.
Some checks failed
CI Quality Gate / Lint / Typecheck / Test / Build (pull_request) Has been cancelled
This pull request has changes conflicting with the target branch.
  • .forgejo/workflows/ci.yml
  • bun.lock
  • package.json
  • packages/api/src/db/schema.ts
  • packages/api/src/index.ts
  • packages/api/src/middleware/rate-limit.ts
  • packages/api/src/middleware/security-headers.ts
  • packages/api/src/routes/auth.ts
  • packages/api/src/routes/generate-tos.ts
  • packages/api/src/routes/generate.ts
  • packages/api/src/routes/health.ts
  • packages/api/src/routes/questionnaire.ts
  • packages/api/src/services/document-generator.ts
  • packages/api/src/services/llm.ts
  • packages/api/src/templates/index.ts
  • packages/api/tsconfig.json
  • packages/shared/src/types.ts
  • packages/web/src/app/questionnaire/page.tsx
  • packages/web/src/components/documents/DocumentList.tsx
  • packages/web/src/components/questionnaire/ReviewStep.tsx
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin agent-task/468:agent-task/468
git switch agent-task/468
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pook/compliancebot!472
No description provided.