Skip to content

feat(api): harden auth surface (#68) - #109

Merged
leon0399 merged 4 commits into
masterfrom
stack/overnight-auth-hardening
Jul 5, 2026
Merged

feat(api): harden auth surface (#68)#109
leon0399 merged 4 commits into
masterfrom
stack/overnight-auth-hardening

Conversation

@leon0399

@leon0399 leon0399 commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds fail-closed global auth guard behavior, atomic session handling, TRUST_PROXY support, rate limiting, and session cleanup cron behavior.
  • Includes migration 0015 in sequence.

Issues

Deferred in later stack PRs

  • Chat stream resume and browser e2e coverage remain stacked above this PR.

Summary by CodeRabbit

  • New Features

    • Added login and registration rate limiting, with clear API responses when limits are exceeded.
    • Added support for cancelling running jobs from the API.
    • Improved reverse-proxy support so client IPs are recorded more accurately.
  • Bug Fixes

    • Strengthened authentication and session handling to reduce race conditions and prevent stale or duplicate session issues.
    • Improved streaming and run-execution reliability, including better abort handling and safer worker processing.
    • Added automatic cleanup for expired sessions and better handling of inactive accounts.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@leon0399, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 50 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 814f0ba8-10e2-4b85-88e3-6e413951e84b

📥 Commits

Reviewing files that changed from the base of the PR and between 8dd01f1 and 6b29b59.

📒 Files selected for processing (2)
  • apps/api/src/app.module.ts
  • docs/scaling.md
📝 Walkthrough

Walkthrough

This PR adds a global fail-closed SessionAuthGuard with a @Public() opt-out decorator (removing per-controller guards), replaces multi-step session validation with an atomic findActiveAndTouch/deleteStaleByTokenHash flow plus periodic pg-boss cleanup, adds @nestjs/throttler rate limiting, and adds TRUST_PROXY configuration.

Changes

Auth Surface Hardening

Layer / File(s) Summary
Public routes and global guard
apps/api/src/auth/public.decorator.ts, apps/api/src/auth/session-auth.guard.ts, apps/api/src/auth/session-auth.guard.spec.ts, apps/api/src/app.controller.ts, apps/api/src/app.module.ts, apps/api/src/auth/auth.controller.ts, apps/api/src/chats/chats.controller.ts, apps/api/src/runs/runs.controller.ts
Adds @Public() metadata and a Reflector-driven opt-out in SessionAuthGuard, registers ThrottlerGuard and SessionAuthGuard globally via APP_GUARD, marks getHello, register, and login as public, and removes now-redundant @UseGuards(SessionAuthGuard) from chats, runs, and auth session controllers.
Atomic session validation and cleanup
apps/api/src/auth/auth.service.ts, apps/api/src/auth/sessions.repository.ts, apps/api/src/auth/session-cleanup.service.ts, apps/api/src/auth/constants.ts, apps/api/src/auth/auth.module.ts, apps/api/src/db/schema/auth.ts, apps/api/src/db/migrations/0014_glamorous_speed_demon.sql, apps/api/src/db/migrations/meta/*, apps/api/src/auth/auth.service.spec.ts, apps/api/src/chats/chats-rls.integration.spec.ts
Introduces SESSION_TOUCH_DEBOUNCE_MS, replaces select/update/isExpiredOrIdle logic with a single atomic findActiveAndTouch query and deleteStaleByTokenHash/deleteExpired cleanup, adds findByIdForUser and idle-aware listForUser, wires a new SessionCleanupService pg-boss hourly sweep, adds sessions indexes/migration, and updates unit/integration tests.
Rate limiting and proxy trust
apps/api/package.json, apps/api/openapi.json, apps/api/.env.example, apps/api/src/app.setup.ts, apps/api/test/auth.e2e-spec.ts, CHANGELOG.md
Adds @nestjs/throttler dependency, documents 429 responses for register/login, introduces getTrustProxySetting() to parse TRUST_PROXY and set Express trust proxy, adds a login-throttle e2e test, and updates the changelog.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ThrottlerGuard
  participant SessionAuthGuard
  participant Reflector
  participant AuthService
  participant SessionsRepository

  Client->>ThrottlerGuard: HTTP request
  ThrottlerGuard->>SessionAuthGuard: within rate limit
  SessionAuthGuard->>Reflector: getAllAndOverride(IS_PUBLIC_KEY)
  alt public route
    SessionAuthGuard-->>Client: allow request
  else protected route
    SessionAuthGuard->>SessionAuthGuard: extract bearer token or cookie
    SessionAuthGuard->>AuthService: validateToken(token)
    AuthService->>SessionsRepository: findActiveAndTouch(tokenHash, idleTtl, debounce)
    SessionsRepository-->>AuthService: session or undefined
    alt session missing userId
      AuthService->>SessionsRepository: deleteStaleByTokenHash(tokenHash)
      AuthService-->>SessionAuthGuard: undefined
      SessionAuthGuard-->>Client: 401 Unauthorized
    else valid session
      AuthService-->>SessionAuthGuard: userId, sessionId
      SessionAuthGuard-->>Client: allow request
    end
  end
Loading

Possibly related PRs

  • leon0399/llame#67: Implements the same TRUST_PROXY/Express trust proxy configuration referenced in app.setup.ts and .env.example.
  • leon0399/llame#69: Introduces the SessionAuthGuard, AuthService, and SessionsRepository session validation logic that this PR directly modifies.
  • leon0399/llame#106: Shares the durable run/run_events lifecycle updated by this PR's changelog and schema migration.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main auth-hardening changes tied to issue #68.
Linked Issues check ✅ Passed The PR implements the listed auth hardening items: global fail-closed guard, atomic session handling, trust-proxy support, throttling, and session cleanup.
Out of Scope Changes check ✅ Passed I don't see any clearly unrelated changes; the docs, tests, migrations, and controller updates all support the auth-hardening scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch stack/overnight-auth-hardening

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements robust authentication hardening, including a fail-closed global session guard, rate limiting on credential endpoints, atomic session validation with a read-only debounce, and an automated hourly session cleanup cron job. Feedback on these changes highlights a bug in getTrustProxySetting where string representations of booleans are not correctly cast for Express's proxy configuration, a potential database performance issue where the session cleanup query will trigger full table scans due to missing indexes on expires and lastSeenAt, and a recommendation to explicitly catch and log errors in the background cleanup service.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread apps/api/src/app.setup.ts
Comment thread apps/api/src/db/schema/auth.ts
Comment thread apps/api/src/auth/session-cleanup.service.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

8 issues found across 26 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/api/src/app.setup.ts Outdated
Comment thread apps/api/src/db/schema/auth.ts
Comment thread apps/api/src/app.setup.ts Outdated
Comment thread apps/api/src/auth/sessions.repository.ts Outdated
Comment thread apps/api/src/auth/auth.service.ts Outdated
Comment thread apps/api/src/auth/auth.service.ts
Comment thread apps/api/src/auth/session-cleanup.service.ts Outdated
Comment thread apps/api/src/chats/chats-rls.integration.spec.ts Outdated
@leon0399
leon0399 force-pushed the stack/overnight-loop-hardening branch from 8a1a6e8 to 675ecca Compare July 2, 2026 15:40
@leon0399
leon0399 force-pushed the stack/overnight-auth-hardening branch from 5c9967f to 20be567 Compare July 2, 2026 15:40
@leon0399
leon0399 force-pushed the stack/overnight-loop-hardening branch 2 times, most recently from d386ae7 to 97d1bce Compare July 4, 2026 22:43
Base automatically changed from stack/overnight-loop-hardening to master July 4, 2026 22:46
leon0399 and others added 2 commits July 5, 2026 00:49
…, TRUST_PROXY (#68)

First tranche of the #60 auth-surface hardening list:

- Fail-closed by default: SessionAuthGuard registers as a global
  APP_GUARD; @public() is the explicit, reviewable opt-out (login,
  register, the liveness root). Per-route @UseGuards were REMOVED so
  the global guard is load-bearing — every existing 401 e2e assertion
  now proves the default-deny path, not a per-controller annotation.
  This kills the bug class behind the original /users exposure.
- Atomic session validation: findActiveAndTouch validates and stamps
  last_seen_at in one UPDATE … RETURNING (no SELECT→check→UPDATE
  TOCTOU), with a read-only fast path inside a 60s debounce window
  that takes the per-request write off the hot path entirely. Stale
  rows are deleted as housekeeping on the miss path.
- Session listing returns active (unexpired) sessions only, backed by
  a new (user_id, expires) index (migration 0015); the current-session
  lookup is a single WHERE user_id AND id query instead of
  list-then-find.
- TRUST_PROXY (off by default — trusting proxy headers with no proxy
  lets clients spoof their IP): hop count or Express subnet spec, so
  session.ip records the real client behind a reverse proxy
  (SPEC §22.0 prerequisite for IP/subnet policies).

Still open in #68: login/register rate limiting (@nestjs/throttler),
the cross-site SameSite=None + CSRF posture, token-free responses for
cookie clients, session rotation (vacuous until a change-password
endpoint exists), and periodic expired-session cleanup.

Refs #68

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015BMxH1qw3rCdjXAywDrJU9
… cron (#68)

- Rate limiting (@nestjs/throttler): instance-wide 300/min default with
  strict 10/min per-IP overrides on POST /auth/v1/login and /register —
  each attempt burns a bcrypt compare, an unbounded brute-force + DoS
  amplification surface. Guard order is deliberate: ThrottlerGuard
  registers BEFORE SessionAuthGuard, so a flood is rejected without
  paying the session lookup. Uses req.ip, so the TRUST_PROXY setting
  from tranche 1 feeds directly into per-client fairness. Proven by an
  e2e that rapid-fires logins into a 429 (placed last in the suite —
  it spends the instance's login budget).

- Expired-session housekeeping on a pg-boss cron: SessionCleanupService
  schedules 'sessions.cleanup' hourly (off the :00 mark) and purges
  expired/idle rows — the #47 scheduler's first production consumer.
  Cross-user by design (sessions carry no RLS; they are consulted
  pre-authentication and expiry is a global fact, not tenant data);
  idempotent across concurrent instances. The purge SQL is proven
  against real Postgres in the integration suite: expired rows go,
  live rows stay.

Remaining in #68: cross-site SameSite=None + CSRF posture, token-free
responses for cookie clients, and session rotation (vacuous until a
change-password endpoint exists).

Refs #68

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015BMxH1qw3rCdjXAywDrJU9
@leon0399
leon0399 force-pushed the stack/overnight-auth-hardening branch from 20be567 to 9f39e2b Compare July 5, 2026 12:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/src/auth/auth.service.ts (1)

96-122: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Avoid the extra DELETE on failed auth checks.

findActiveAndTouch() already does the lookup/update work; when it returns undefined, deleteStaleByTokenHash() adds another blocking DB round-trip on every invalid token, including tokens that never matched a session row. Since this is best-effort housekeeping, move it off the request path or remove it here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/auth/auth.service.ts` around lines 96 - 122, The failed-auth
path in validateToken is doing an extra blocking delete after findActiveAndTouch
returns no usable session, which adds unnecessary DB work for every invalid
token. Remove the inline deleteStaleByTokenHash call from
AuthService.validateToken, or move that housekeeping to a background/periodic
cleanup path so the request path only performs the existing findActiveAndTouch
lookup and returns undefined on failure.
🧹 Nitpick comments (3)
apps/api/src/app.module.ts (1)

36-38: 🩺 Stability & Availability | 🔵 Trivial

Use shared throttler storage before scaling replicas.
ThrottlerModule.forRoot() falls back to per-process in-memory counters, so the limit is only enforced per instance. If this API can run with multiple pods/replicas, back it with a shared store (for example Redis) so the login/register ceiling stays global.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/app.module.ts` around lines 36 - 38, The throttling setup in
ThrottlerModule.forRoot currently uses the default in-memory counters, so limits
are enforced per process rather than across replicas. Update the AppModule
throttler configuration to use a shared storage backend such as Redis, wiring it
into the throttler options so the default login/register rate limit is global
across all instances.
apps/api/src/auth/session-cleanup.service.ts (1)

26-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider guarding bootstrap wiring and adding shutdown handling.

onApplicationBootstrap has no error handling around ensureQueue/schedule/consume, so a transient queue-backend hiccup at startup could crash the whole API rather than just disabling cleanup. Also, there's no OnModuleDestroy to stop consuming on shutdown.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/auth/session-cleanup.service.ts` around lines 26 - 49, Wrap the
startup wiring in SessionCleanupService.onApplicationBootstrap with error
handling so failures in queue.ensureQueue, queue.schedule, or queue.consume
don’t take down the API if the queue backend is temporarily unavailable. Add a
fallback path that logs the problem and skips session cleanup setup rather than
crashing, and introduce OnModuleDestroy on SessionCleanupService to stop the
SESSIONS_CLEANUP_QUEUE consumer during shutdown. Use the existing queue and
logger fields, and keep the cleanup logic in cleanup() unchanged.
apps/api/src/auth/sessions.repository.ts (1)

167-186: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider an index to support deleteExpired's cross-user scan.

The new sessions_user_expires_idx is composite on (userId, expires) and is intended for listForUser (per its inline comment); it won't be efficiently usable by this query since deleteExpired filters globally on expires/lastSeenAt without a leading userId predicate. As the sessions table grows, this periodic cross-user cleanup could degrade into a sequential scan. Consider a standalone index on expires and/or lastSeenAt if cleanup runs frequently against a large table.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/auth/sessions.repository.ts` around lines 167 - 186, The
`deleteExpired` housekeeping query scans sessions globally by `expires` and
`lastSeenAt`, so the existing `sessions_user_expires_idx` on `(userId, expires)`
won’t help this path. Add an index that matches the cross-user predicates used
in `deleteExpired`—for example a standalone index on `sessions.expires` and/or
`sessions.lastSeenAt`—so the periodic cleanup stays efficient as the table
grows.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/app.setup.ts`:
- Around line 34-43: The getTrustProxySetting helper currently forwards any
non-numeric TRUST_PROXY value back to Express, which can cause proxy-addr to
fail startup on unsupported strings. Update this function to validate and
whitelist only the supported trust-proxy formats before returning, and make
invalid values fail with a clear configuration error instead of passing raw
strings through to app.set('trust proxy', ...). Keep the fix centered in
getTrustProxySetting so all callers inherit the stricter parsing.

In `@apps/api/src/auth/sessions.repository.ts`:
- Around line 106-113: `listForUser` currently treats sessions as active based
only on `expires`, but `findActiveAndTouch` also requires `lastSeenAt` to be
within `idleTtlMs`. Update `SessionsRepository.listForUser` to accept and use
`idleTtlMs` so it filters out idle sessions too, mirroring the same usability
rule as `findActiveAndTouch`; use the existing `sessions`, `and`, `eq`, `gt`,
and `desc` query pattern to add the `lastSeenAt`/idle constraint.

---

Outside diff comments:
In `@apps/api/src/auth/auth.service.ts`:
- Around line 96-122: The failed-auth path in validateToken is doing an extra
blocking delete after findActiveAndTouch returns no usable session, which adds
unnecessary DB work for every invalid token. Remove the inline
deleteStaleByTokenHash call from AuthService.validateToken, or move that
housekeeping to a background/periodic cleanup path so the request path only
performs the existing findActiveAndTouch lookup and returns undefined on
failure.

---

Nitpick comments:
In `@apps/api/src/app.module.ts`:
- Around line 36-38: The throttling setup in ThrottlerModule.forRoot currently
uses the default in-memory counters, so limits are enforced per process rather
than across replicas. Update the AppModule throttler configuration to use a
shared storage backend such as Redis, wiring it into the throttler options so
the default login/register rate limit is global across all instances.

In `@apps/api/src/auth/session-cleanup.service.ts`:
- Around line 26-49: Wrap the startup wiring in
SessionCleanupService.onApplicationBootstrap with error handling so failures in
queue.ensureQueue, queue.schedule, or queue.consume don’t take down the API if
the queue backend is temporarily unavailable. Add a fallback path that logs the
problem and skips session cleanup setup rather than crashing, and introduce
OnModuleDestroy on SessionCleanupService to stop the SESSIONS_CLEANUP_QUEUE
consumer during shutdown. Use the existing queue and logger fields, and keep the
cleanup logic in cleanup() unchanged.

In `@apps/api/src/auth/sessions.repository.ts`:
- Around line 167-186: The `deleteExpired` housekeeping query scans sessions
globally by `expires` and `lastSeenAt`, so the existing
`sessions_user_expires_idx` on `(userId, expires)` won’t help this path. Add an
index that matches the cross-user predicates used in `deleteExpired`—for example
a standalone index on `sessions.expires` and/or `sessions.lastSeenAt`—so the
periodic cleanup stays efficient as the table grows.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1da6ad6f-7f6c-4f31-ab66-fc3afff84db1

📥 Commits

Reviewing files that changed from the base of the PR and between 526a1e4 and 9f39e2b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (25)
  • CHANGELOG.md
  • apps/api/.env.example
  • apps/api/openapi.json
  • apps/api/package.json
  • apps/api/src/app.controller.ts
  • apps/api/src/app.module.ts
  • apps/api/src/app.setup.ts
  • apps/api/src/auth/auth.controller.ts
  • apps/api/src/auth/auth.module.ts
  • apps/api/src/auth/auth.service.spec.ts
  • apps/api/src/auth/auth.service.ts
  • apps/api/src/auth/constants.ts
  • apps/api/src/auth/public.decorator.ts
  • apps/api/src/auth/session-auth.guard.spec.ts
  • apps/api/src/auth/session-auth.guard.ts
  • apps/api/src/auth/session-cleanup.service.ts
  • apps/api/src/auth/sessions.repository.ts
  • apps/api/src/chats/chats-rls.integration.spec.ts
  • apps/api/src/chats/chats.controller.ts
  • apps/api/src/db/migrations/0014_greedy_maddog.sql
  • apps/api/src/db/migrations/meta/0014_snapshot.json
  • apps/api/src/db/migrations/meta/_journal.json
  • apps/api/src/db/schema/auth.ts
  • apps/api/src/runs/runs.controller.ts
  • apps/api/test/auth.e2e-spec.ts
💤 Files with no reviewable changes (2)
  • apps/api/src/runs/runs.controller.ts
  • apps/api/src/chats/chats.controller.ts

Comment thread apps/api/src/app.setup.ts
Comment thread apps/api/src/auth/sessions.repository.ts Outdated
All eight distinct bot findings verified and fixed:

- TRUST_PROXY: 'true'/'false' now become real booleans (as strings,
  Express would treat 'true' as a subnet list, fail to parse, and
  silently trust nothing — breaking req.ip, per-IP throttling, and
  session IP attribution); a non-integer or negative hop count now
  fails startup loudly instead of skewing req.ip silently
- sessions cleanup: indexes on expires and last_seen_at (the hourly
  delete filters on each alone; neither was reachable via the
  (user_id, expires) composite) — migration 0014 regenerated with all
  three indexes and a journal timestamp strictly after 0013's (the
  existing-database upgrade lesson from #108)
- cleanup sweep failures are logged locally, then rethrown — pg-boss
  retry/dead-letter semantics unchanged, but the failure is visible in
  app logs, not just the queue tables
- listForUser and findByIdForUser now apply the SAME validity filter
  as findActiveAndTouch (expiry AND idle TTL) — a session the list
  shows is a session that would authenticate, and an expired-but-not-
  purged row can never surface as the "current" session
- the stale-session housekeeping delete on the null-auth path is
  best-effort: a DB error there logs a warning instead of turning an
  unauthenticated request into a 500 (the cleanup cron covers misses)
- the purge integration test asserts PHYSICAL deletion via the raw
  table (listForUser filters expired rows, so it would have passed
  even if the delete no-opped)

Verified: tsgo + oxlint (type-aware) + prettier clean, unit 130
(exit-code checked, maxWorkers capped), full rls-test.sh green
(RLS 13, queue 8, e2e 35).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014peA4x8efDtokaggcbyokP
@leon0399

leon0399 commented Jul 5, 2026

Copy link
Copy Markdown
Owner Author

Rebased onto current master (post #108): only the two auth commits replay; migration renumbered 0015 → 0014 and regenerated from the schema (journal timestamp strictly after 0013's — the existing-database upgrade lesson from #108), session-cleanup ported to the typed queue API, lockfile regenerated against the pnpm catalog. All eight distinct review findings fixed and each thread answered individually — highlights: TRUST_PROXY now handles boolean strings and rejects malformed hop counts at startup (as a string, 'true' would have made Express silently trust nothing); listForUser/findByIdForUser share findActiveAndTouch's exact validity semantics; cleanup-path failures can no longer 500 an unauthenticated request; the purge test asserts physical deletion. With this, all umbrella #101 threads are also resolved (fixed here, fixed by earlier slices, tracked in #125, or deferred to the browser-resume slice). Verified per push: unit 130, RLS 13, queue 8, e2e 35.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/api/src/db/migrations/0014_glamorous_speed_demon.sql (1)

1-3: 🚀 Performance & Scalability | 🔵 Trivial

Operational note: index build briefly blocks session writes.

These plain CREATE INDEX statements take a lock that blocks concurrent writes (new logins, last_seen_at touches) on sessions while they build. For small self-hosted tables this is negligible, but on a large sessions table on a live deployment consider building these CONCURRENTLY out-of-band (drizzle runs migrations in a transaction, so CONCURRENTLY can't be emitted here). Otherwise this is fine.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/db/migrations/0014_glamorous_speed_demon.sql` around lines 1 -
3, The session index migration currently uses plain CREATE INDEX statements,
which can block writes on the sessions table during deployment. Update the
migration for the sessions indexes to avoid doing these builds inside the
transactional Drizzle migration path; either leave them as-is only if blocking
is acceptable, or move the index creation out-of-band and use CONCURRENTLY for
the sessions_user_expires_idx, sessions_expires_idx, and
sessions_last_seen_at_idx indexes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/api/src/db/migrations/0014_glamorous_speed_demon.sql`:
- Around line 1-3: The session index migration currently uses plain CREATE INDEX
statements, which can block writes on the sessions table during deployment.
Update the migration for the sessions indexes to avoid doing these builds inside
the transactional Drizzle migration path; either leave them as-is only if
blocking is acceptable, or move the index creation out-of-band and use
CONCURRENTLY for the sessions_user_expires_idx, sessions_expires_idx, and
sessions_last_seen_at_idx indexes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d756901d-374c-4506-a441-daa44e35520b

📥 Commits

Reviewing files that changed from the base of the PR and between 9f39e2b and 8dd01f1.

📒 Files selected for processing (9)
  • apps/api/src/app.setup.ts
  • apps/api/src/auth/auth.service.ts
  • apps/api/src/auth/session-cleanup.service.ts
  • apps/api/src/auth/sessions.repository.ts
  • apps/api/src/chats/chats-rls.integration.spec.ts
  • apps/api/src/db/migrations/0014_glamorous_speed_demon.sql
  • apps/api/src/db/migrations/meta/0014_snapshot.json
  • apps/api/src/db/migrations/meta/_journal.json
  • apps/api/src/db/schema/auth.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/api/src/db/migrations/meta/0014_snapshot.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/api/src/db/schema/auth.ts
  • apps/api/src/auth/session-cleanup.service.ts
  • apps/api/src/auth/auth.service.ts
  • apps/api/src/chats/chats-rls.integration.spec.ts

The rate-limit counters are in-memory per process: api × N replicas
multiply the effective ceiling by N and restarts reset it. Fine
single-node; a shared ThrottlerStorage becomes necessary with #116 —
noted at the wiring site and in docs/scaling.md so the limit is never
mistaken for a fleet-wide guarantee.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014peA4x8efDtokaggcbyokP
Comment thread apps/api/src/app.module.ts
@leon0399
leon0399 merged commit 2bda6cc into master Jul 5, 2026
8 checks passed
@leon0399
leon0399 deleted the stack/overnight-auth-hardening branch July 5, 2026 14:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(api): auth surface hardening (guard default, atomic sessions, throttle, trust-proxy)

1 participant