Skip to content

fix(api): rate limit sensitive serverless endpoints (#253) - #367

Closed
hrx01-dev wants to merge 3 commits into
mainfrom
fix/253-api-rate-limiting
Closed

hrx01-dev wants to merge 3 commits into
mainfrom
fix/253-api-rate-limiting

Conversation

@hrx01-dev

@hrx01-dev hrx01-dev commented Aug 8, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • add durable per-IP rate limiting to the Razorpay createOrder endpoint
  • keep the existing Firestore-backed rate limiting on the Gemini estimate endpoint
  • hash caller IPs with RATE_LIMIT_HASH_SECRET before storing rate-limit state
  • fail closed when rate limiting is not configured or Firestore is unavailable
  • return 429 with Retry-After when a caller exceeds the order-creation limit

This prevents unauthenticated callers from repeatedly invoking the paid Gemini/Razorpay operations described in #253.

Closes #253

Summary by CodeRabbit

  • New Features
    • Added request-rate protection when creating payment orders.
    • Requests exceeding five attempts within 15 minutes now receive a clear HTTP 429 response with retry information.
  • Bug Fixes
    • Temporary rate-limiter service issues no longer prevent valid orders from being created.
    • Existing validation, billing checks, payment verification, duplicate protection, and order recording continue to work as expected.

@vercel

vercel Bot commented Aug 8, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
servio Ready Ready Preview Aug 8, 2026 5:03pm

@codelens-ai

codelens-ai Bot commented Aug 8, 2026

Copy link
Copy Markdown

🤖 CodeLens AI Review

🔍 Code Review - api/razorpay.ts

Issues Found:

  • 🔴 Critical: Hardcoded secrets are not explicitly shown in this snippet, but the use of process.env.FIREBASE_SERVICE_ACCOUNT and process.env.VITE_FIREBASE_PROJECT_ID or process.env.FIREBASE_PROJECT_ID suggests that sensitive information might be stored in environment variables. Ensure these are properly secured and not committed to the repository.
  • 🟡 Warning: The initAdmin function catches all errors and logs them but returns a boolean value indicating success or failure. This could potentially mask critical errors. Consider re-throwing the error after logging or implementing a more robust error handling mechanism.
  • 🟡 Warning: The checkRateLimit function uses a simple HMAC with a secret to create a key for the rate limit collection in Firestore. If the secret is compromised, this could lead to unauthorized access or tampering with rate limit data. Ensure the secret is securely stored and consider using more secure methods for authentication and authorization.
  • 💡 Suggestion: The isValidEmail function uses a basic regex for email validation. While it covers most common cases, it might not handle all possible valid email formats according to the RFC 5322 standard. Consider using a more comprehensive validation library or function for better coverage.
  • 💡 Suggestion: The callerIp function relies on the X-Forwarded-For header to determine the caller's IP. This header can be spoofed. Consider using a more secure method to determine the client's IP, such as using the CF-Connecting-IP header if you're behind Cloudflare or ensuring your server configuration properly sets and secures the X-Forwarded-For header.
  • 💡 Suggestion: The code does not explicitly handle cases where req.headers.origin is missing or null. While it sets a default allowed origin, consider adding explicit checks for null or undefined values to prevent potential security issues.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

api/razorpay.ts adds Firestore-backed HMAC-IP rate limiting to createOrder. The handler allows five requests per 15-minute window, returns HTTP 429 with retry metadata when limited, and continues order creation if rate-limit storage fails.

Changes

Razorpay createOrder protection

Layer / File(s) Summary
Firestore-backed IP rate limiting
api/razorpay.ts
The handler extracts client IPs, hashes them with HMAC, and tracks request timestamps in Firestore transactions. It returns HTTP 429 and Retry-After after five requests in 15 minutes.
Order and payment processing
api/razorpay.ts
Rate-limit enforcement runs after billing validation and before Razorpay order creation. Existing order validation, payment verification, duplicate protection, and Firestore updates remain in place. Rate-limit failures are logged and do not block order creation.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant createOrder
  participant Firestore
  participant Razorpay
  Client->>createOrder: Submit createOrder request
  createOrder->>Firestore: Validate billing and update rate-limit state
  Firestore-->>createOrder: Allow request or return retry delay
  alt Request allowed
    createOrder->>Razorpay: Create order
    Razorpay-->>createOrder: Return order details
    createOrder-->>Client: Return order response
  else Request limited
    createOrder-->>Client: Return HTTP 429 with Retry-After
  end
Loading

Possibly related PRs

  • hrx01-dev/Servio#245: Both PRs modify api/razorpay.ts; this PR adds Firestore-backed rate limiting to the createOrder flow.
  • hrx01-dev/Servio#249: Both PRs add server-side HMAC-IP rate limiting to API endpoints.
  • hrx01-dev/Servio#267: Both PRs implement Firestore-backed HMAC-hashed per-IP rate limiting for API handlers.

Suggested labels: enhancement

Suggested reviewers: harsh-goswami-103

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: rate limiting sensitive API endpoints.
Linked Issues check ✅ Passed The PR adds durable per-IP rate limiting to createOrder and retains rate limiting for estimate, addressing issue #253.
Out of Scope Changes check ✅ Passed The changes are limited to rate limiting and related error handling in the targeted Razorpay endpoint.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/253-api-rate-limiting

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.

@github-actions

github-actions Bot commented Aug 8, 2026 •

Copy link
Copy Markdown
Contributor

Visit the preview URL for this PR (updated for commit 8b6e910):

https://servio-0--pr367-fix-253-api-rate-lim-8205mbg4.web.app

(expires Sat, 15 Aug 2026 17:03:37 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: 15915abb5951eb298a844eda460b24f444d93a69

@codelens-ai

codelens-ai Bot commented Aug 8, 2026

Copy link
Copy Markdown

🤖 CodeLens AI Review

🔍 Code Review - api/razorpay.ts

Issues Found:

  • 🔴 Critical: Hardcoded secret in checkRateLimit function. The secret parameter should be stored securely as an environment variable, not passed directly in the function.
  • 🟡 Warning: Potential bug in isValidEmail function. The function does not check for null or undefined input, which could cause errors. It should be modified to handle these cases.
  • 🟡 Warning: In the initAdmin function, if process.env.FIREBASE_SERVICE_ACCOUNT is not set, the function will attempt to initialize the Firebase app with the project ID. However, if process.env.VITE_FIREBASE_PROJECT_ID or process.env.FIREBASE_PROJECT_ID is not set, this will cause an error. The function should be modified to handle this case.
  • 💡 Suggestion: The callerIp function does not handle cases where the "x-forwarded-for" header is not set or is empty. It should be modified to handle these cases and return a default value or throw an error.
  • 💡 Suggestion: The checkRateLimit function uses a simple HMAC to generate a key for the rate limit collection. This may not be secure enough for production use. Consider using a more secure method, such as a cryptographically secure pseudorandom number generator.

@codelens-ai

codelens-ai Bot commented Aug 8, 2026

Copy link
Copy Markdown

🤖 CodeLens AI Review

⚠️ Review failed for api/razorpay.ts: All LLM providers failed: gemini: 429 You exceeded your current quota, please check your plan and bi

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 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 `@api/razorpay.ts`:
- Around line 101-112: Update createOrder’s rate-limit setup to require
RATE_LIMIT_HASH_SECRET, returning a server error before order creation when it
is missing; pass that secret to checkRateLimit instead of razorpayKeySecret, and
change its catch path to return a 503 response rather than continuing with
payment creation.
🪄 Autofix

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 Plus

Run ID: ffaa3496-435d-45de-be42-4aa814f68cad

📥 Commits

Reviewing files that changed from the base of the PR and between ab27752 and 8b6e910.

📒 Files selected for processing (1)
  • api/razorpay.ts

Comment thread api/razorpay.ts
Comment on lines +101 to 112
// Use the already-required server-side Razorpay secret to hash the IP.
// No additional environment variable is needed. Rate-limit persistence is
// best-effort; only an actual limit violation blocks order creation.
try {
const verdict = await checkRateLimit(callerIp(req), razorpayKeySecret, Date.now());
if (!verdict.allowed) {
res.setHeader("Retry-After", String(Math.ceil(verdict.retryAfterMs / 1000)));
return res.status(429).json({ error: "Too many order requests. Please try again later.", retryAfterMs: verdict.retryAfterMs });
}
} catch (error) {
console.error("Razorpay rate-limit check failed; continuing with payment creation:", error);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Fail closed when rate limiting is unavailable.

Lines 101-112 use RAZORPAY_KEY_SECRET instead of RATE_LIMIT_HASH_SECRET. They also continue order creation when checkRateLimit fails. An attacker can bypass the protection during Firestore failures, so this does not meet the required fail-closed behavior.

Require RATE_LIMIT_HASH_SECRET for createOrder. Return a server error when it is missing. Return a 503 when the rate-limit transaction fails.

Proposed fix
-      // Use the already-required server-side Razorpay secret to hash the IP.
-      // No additional environment variable is needed. Rate-limit persistence is
-      // best-effort; only an actual limit violation blocks order creation.
+      const rateLimitHashSecret = process.env.RATE_LIMIT_HASH_SECRET;
+      if (!rateLimitHashSecret) {
+        return res.status(500).json({ error: "Rate limiting is not configured. Please try again later." });
+      }
       try {
-        const verdict = await checkRateLimit(callerIp(req), razorpayKeySecret, Date.now());
+        const verdict = await checkRateLimit(callerIp(req), rateLimitHashSecret, Date.now());
         if (!verdict.allowed) {
           res.setHeader("Retry-After", String(Math.ceil(verdict.retryAfterMs / 1000)));
           return res.status(429).json({ error: "Too many order requests. Please try again later.", retryAfterMs: verdict.retryAfterMs });
         }
       } catch (error) {
-        console.error("Razorpay rate-limit check failed; continuing with payment creation:", error);
+        console.error("Razorpay rate-limit check failed:", error);
+        return res.status(503).json({ error: "Please try again later." });
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Use the already-required server-side Razorpay secret to hash the IP.
// No additional environment variable is needed. Rate-limit persistence is
// best-effort; only an actual limit violation blocks order creation.
try {
const verdict = await checkRateLimit(callerIp(req), razorpayKeySecret, Date.now());
if (!verdict.allowed) {
res.setHeader("Retry-After", String(Math.ceil(verdict.retryAfterMs / 1000)));
return res.status(429).json({ error: "Too many order requests. Please try again later.", retryAfterMs: verdict.retryAfterMs });
}
} catch (error) {
console.error("Razorpay rate-limit check failed; continuing with payment creation:", error);
}
const rateLimitHashSecret = process.env.RATE_LIMIT_HASH_SECRET;
if (!rateLimitHashSecret) {
return res.status(500).json({ error: "Rate limiting is not configured. Please try again later." });
}
try {
const verdict = await checkRateLimit(callerIp(req), rateLimitHashSecret, Date.now());
if (!verdict.allowed) {
res.setHeader("Retry-After", String(Math.ceil(verdict.retryAfterMs / 1000)));
return res.status(429).json({ error: "Too many order requests. Please try again later.", retryAfterMs: verdict.retryAfterMs });
}
} catch (error) {
console.error("Razorpay rate-limit check failed:", error);
return res.status(503).json({ error: "Please try again later." });
}
🤖 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 `@api/razorpay.ts` around lines 101 - 112, Update createOrder’s rate-limit
setup to require RATE_LIMIT_HASH_SECRET, returning a server error before order
creation when it is missing; pass that secret to checkRateLimit instead of
razorpayKeySecret, and change its catch path to return a 503 response rather
than continuing with payment creation.

@hrx01-dev hrx01-dev closed this Aug 8, 2026
@hrx01-dev
hrx01-dev deleted the fix/253-api-rate-limiting branch September 1, 2026 14:56

This branch was successfully deployed

1 active deployment
Preview — 8b6e910e Deployed Aug 8, 2026 by vercel[bot]
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.

api/estimate.ts and api/razorpay.ts createOrder have no auth, App Check, or rate limiting

1 participant