Skip to content

Feat/per specialty credential verification for doctors #50 - #146

Open
Dev-Marcy wants to merge 8 commits into
DocForum:mainfrom
Dev-Marcy:feat/per-specialty-credential-verification-for-doctors
Open

Dev-Marcy wants to merge 8 commits into
DocForum:mainfrom
Dev-Marcy:feat/per-specialty-credential-verification-for-doctors

Conversation

@Dev-Marcy

Copy link
Copy Markdown

Closes #50

Summary

This PR adds a configurable, globally-applied rate limiter to DocForum. The limiter
uses a token-bucket algorithm keyed by authenticated user ID or, for
unauthenticated requests, by the correctly-resolved client IP. It is designed to
work behind Render's single proxy hop (via X-Forwarded-For resolution) while
keeping the existing, stricter login throttling (issue #35) fully independent.

Design

Store

The limiter uses a pluggable RateLimitStore interface with two implementations:

  • Production: prismaRateLimitStore — Postgres-backed. Uses raw SQL UPSERT
    (INSERT ... ON CONFLICT DO UPDATE) for atomic counter increments, avoiding the
    read-then-write race condition that would otherwise allow concurrent requests to
    bypass the limit.
  • Testing: MemoryRateLimitStore — in-memory implementation for fast,
    deterministic unit tests.

A RateLimitRecord table tracks per-key consumption with a 60-second sliding
window. A scheduled prune job deletes records older than a configured threshold,
preventing unbounded store growth.

Token-bucket algorithm

Each keyed request checks consumption in the current window:

Buffy and others added 2 commits October 16, 2026 11:22
- Add specialty-level verification fields (status, reviewer, review date,
  rejection reason) to DoctorSpecialty via migration
- Add admin endpoints for listing and reviewing per-specialty verification
- Filter specialty-based search results to only include verified
  doctor-specialty pairs

Co-Authored-By: Buffy <noreply@codebuff.com>
…buckets

- Add configurable token-bucket rate limiter keyed by authenticated user
  ID or client IP (behind Render's proxy)
- Postgres-backed store with in-memory test implementation
- Return HTTP 429 with Retry-After and RateLimit-* headers
- Exempt /health endpoint
- Preserve existing login throttling without double-counting
- Add in-memory store for tests and overhead measurement
- Add unit tests for rate limit behavior

Co-Authored-By: Buffy <noreply@codebuff.com>
@drips-wave

drips-wave Bot commented Oct 16, 2026

Copy link
Copy Markdown

@Dev-Marcy Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@Dev-Marcy pls fix CI

@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

CI is failing on this PR. main itself is green, so the cause is in this branch:

Typecheck fails: src/modules/rate-limit/index.ts(3,31): Module './types' has no exported member 'RateLimitRecord'. RateLimitRecord is declared in rate-limit/repositories/index.ts, not types.ts. Fix the re-export path. This branch is also 3 commits behind main. Note that #145 has the same failure and overlaps heavily with this PR. Could you close one, or explain how they differ?

Please push a fix and CI will re-run. Before pushing, run npm run prisma:generate && npm run typecheck && npm test && npm run test:integration in backend/.

@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@Dev-Marcy

Heads-up: #145 and #146 point at the same commit (cecb3d3), so they're duplicates, even though one title says #50 and the other #51 and the branch names differ (feat/global-api-rate-limiting vs feat/per-specialty-credential-verification-for-doctors). Please close one of them, and give each issue its own branch with only its own changes.

CI is red on typecheck in the rate-limit module:

  • rate-limit/types doesn't export RateLimitRecord
  • prisma.$countRateLimitRecord doesn't exist (did you mean prisma.rateLimitRecord.count()?)
  • config.rateLimit isn't defined in src/config
  • there are unused-declaration errors too

Please rebase onto the latest main (it's on Prisma 7 now, #151) and get npm run typecheck passing locally.

@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@Dev-Marcy Thanks for the PR. It can't be merged yet. To unblock it:

CI is failing. Here's what broke:

Backend (typecheck + test + build):

src/modules/rate-limit/index.ts(3,31): error TS2305: Module './types' has no exported member 'RateLimitRecord'.
src/modules/rate-limit/repositories/index.ts(35,38): error TS2551: Property '$countRateLimitRecord' does not exist on PrismaClient
src/modules/rate-limit/services/index.ts(15,43): error TS2339: Property 'rateLimit' does not exist on config

Please fix these errors and run the same steps locally before pushing (npm run typecheck && npm test && npm run build, or cargo test for contracts).

Branch protection also requires the branch to be up to date with main, so please rebase onto the latest main when you push the fix.

Push to this same branch and CI will re-run. Once it's green and conflict-free, it'll be reviewed for merge.

@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@Dev-Marcy CI is failing on this branch, so it can't be merged yet. The errors from the latest run:

Backend (typecheck + test + build)

src/modules/rate-limit/index.ts(3,31): error TS2305: Module '"./types"' has no exported member 'RateLimitRecord'.
src/modules/rate-limit/repositories/index.ts(2,15): error TS6196: 'RateLimitStore' is declared but never used.
src/modules/rate-limit/repositories/index.ts(24,9): error TS6133: 'record' is declared but its value is never read.
src/modules/rate-limit/repositories/index.ts(35,38): error TS2551: Property '$countRateLimitRecord' does not exist on type 'PrismaClient<{ adapter: PrismaPg; }, never, DefaultArgs>'. Did you mean 'rateLimitRecord'?
src/modules/rate-limit/repositories/index.ts(58,38): error TS2551: Property '$countRateLimitRecord' does not exist on type 'PrismaClient<{ adapter: PrismaPg; }, never, DefaultArgs>'. Did you mean 'rateLimitRecord'?
src/modules/rate-limit/services/index.ts(126,9): error TS6133: 'result' is declared but its value is never read.
src/modules/rate-limit/services/index.ts(15,43): error TS2339: Property 'rateLimit' does not exist on type '{ readonly port: number; readonly databaseUrl: string; readonly jwt: { readonly accessSecret: string; readonly refreshSecret: string; readonly accessTtl
src/modules/rate-limit/services/index.ts(16,38): error TS2339: Property 'rateLimit' does not exist on type '{ readonly port: number; readonly databaseUrl: string; readonly jwt: { readonly accessSecret: string; readonly refreshSecret: string; readonly accessTtl

Please reproduce this locally, fix the errors above and push. CI will re-run on your next push.

@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@Dev-Marcy CI is failing at typecheck after the latest push:

src/config/index.ts(3,16): error TS2552: Cannot find name 'requireEnv'.
src/config/index.ts(23,15): error TS2304: Cannot find name 'parseTrustProxy'.
src/config/index.ts(50,16): error TS2304: Cannot find name 'parseCorsOrigins'.
src/config/index.ts(63,13): error TS2304: Cannot find name 'parsePaymentsConfig'.
src/modules/rate-limit/services/index.ts(44,14): error TS2554: Expected 2-3 arguments, but got 1.

src/config/index.ts lost the helper functions it calls (or their imports), probably while you resolved a conflict. Restore them from main, fix the rate-limit call, run npm run typecheck in backend/, and push.

This branch has not been deployed

No deployments
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.

[Phase 8] Global API rate limiting with per-route budgets

2 participants