Skip to content

🛡️ Sentinel: dual rate-limiting#77

Open
projectamazonph wants to merge 2 commits into
mainfrom
fix/dual-rate-limiting-13284815474013834007
Open

🛡️ Sentinel: dual rate-limiting#77
projectamazonph wants to merge 2 commits into
mainfrom
fix/dual-rate-limiting-13284815474013834007

Conversation

@projectamazonph

@projectamazonph projectamazonph commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Implemented dual rate-limiting (combining target-based/email rate limiting and IP-based rate limiting) in auth actions. Updated Next.js mocks for testing next/headers headers() function correctly, and wrote comprehensive unit tests ensuring robustness.


PR created automatically by Jules for task 13284815474013834007 started by @projectamazonph

Summary by CodeRabbit

  • New Features

    • Added IP-based rate limiting to sign-in and sign-up attempts (using forwarded client IP headers).
    • Users exceeding the IP limit receive a retry-time error message.
  • Tests

    • Added authentication test cases for both email-based and IP-based rate-limiting.
    • Added dedicated unit tests for rate-limit behavior, including sliding windows and automatic cleanup.

Add client IP extraction and IP-based rate limiting as a defense-in-depth layer on top of email-based (target-based) rate limiting for both signUpAction and signInAction.

Co-authored-by: projectamazonph <286085559+projectamazonph@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

Copilot AI review requested due to automatic review settings July 23, 2026 12:41

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Authentication sign-in and sign-up actions now enforce email- and client-IP-based rate limits. Header and rate-limit mocks support the updated tests, which cover rejection messages, retry intervals, rate-limit keys, sliding windows, and bucket cleanup.

Changes

Authentication rate limiting

Layer / File(s) Summary
IP rate-limit enforcement
src/app/actions/auth.ts
Sign-in and sign-up derive IP addresses from forwarded headers, apply IP-specific limits, and return IP-specific errors when limits are exceeded.
Authentication rate-limit tests
src/__tests__/setup.ts, src/app/actions/__tests__/auth-actions.test.ts
Test mocks support request headers and rate-limit responses, with coverage for email- and IP-based sign-in and sign-up rejection paths.
Rate-limit behavior tests
src/lib/__tests__/rate-limit.test.ts
Tests cover hit limits, sliding-window expiration, retry intervals, and cleanup of expired buckets.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant signInAction
  participant headers
  participant rateLimit
  Client->>signInAction: Submit sign-in request
  signInAction->>headers: Read forwarded IP headers
  headers-->>signInAction: Return client IP
  signInAction->>rateLimit: Check email and IP limits
  rateLimit-->>signInAction: Return allowed or retry interval
  signInAction-->>Client: Return authentication result or rate-limit error
Loading

Possibly related PRs

🚥 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 clearly matches the main change: adding dual rate-limiting to authentication actions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/dual-rate-limiting-13284815474013834007

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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

Add client IP extraction and IP-based rate limiting as a defense-in-depth layer on top of email-based (target-based) rate limiting for both signUpAction and signInAction. Also add unit tests to ensure 100% test coverage of the rate-limit utility and meet repository quality gates.

Co-authored-by: projectamazonph <286085559+projectamazonph@users.noreply.github.com>

@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

🧹 Nitpick comments (1)
src/app/actions/auth.ts (1)

36-50: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate dual-rate-limit logic between signUpAction and signInAction.

The IP derivation plus target/IP rate-limit-and-throw pattern is copy-pasted verbatim across both actions. Extracting a shared helper (e.g., getClientIp(heads) and/or enforceRateLimits(prefix, email, ip)) would centralize the fix location for the parsing/trust issue above and reduce future drift between the two call sites.

Also applies to: 139-153

🤖 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 `@src/app/actions/auth.ts` around lines 36 - 50, Extract the duplicated
client-IP derivation and target/IP rate-limit enforcement from signUpAction and
signInAction into shared helpers such as getClientIp and enforceRateLimits.
Update both actions to call the helpers while preserving their existing
prefixes, limits, retry messages, and headers input.
🤖 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 `@src/app/actions/auth.ts`:
- Around line 36-37: Update the IP extraction in both sign-up and sign-in paths
around the headers() calls to normalize x-forwarded-for by parsing its first
comma-separated IP before using it as the rate-limit key. Preserve the x-real-ip
fallback and 127.0.0.1 default when x-forwarded-for is absent.

---

Nitpick comments:
In `@src/app/actions/auth.ts`:
- Around line 36-50: Extract the duplicated client-IP derivation and target/IP
rate-limit enforcement from signUpAction and signInAction into shared helpers
such as getClientIp and enforceRateLimits. Update both actions to call the
helpers while preserving their existing prefixes, limits, retry messages, and
headers input.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 59efa99b-629b-4f54-b09d-f99de5aa01cd

📥 Commits

Reviewing files that changed from the base of the PR and between dabee94 and 3f5a293.

📒 Files selected for processing (3)
  • src/__tests__/setup.ts
  • src/app/actions/__tests__/auth-actions.test.ts
  • src/app/actions/auth.ts

Comment thread src/app/actions/auth.ts
Comment on lines +36 to +37
const heads = await headers();
const ip = heads.get('x-forwarded-for') ?? heads.get('x-real-ip') ?? '127.0.0.1';

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== auth.ts ==\n'
sed -n '1,220p' src/app/actions/auth.ts

printf '\n== search for x-forwarded-for / x-real-ip / trusted proxy ==\n'
rg -n "x-forwarded-for|x-real-ip|trusted proxy|forwarded-for|rate-limit|rate limit|rate limiting|headers\(\)" src . -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**'

printf '\n== deployment-related files ==\n'
git ls-files | rg '(^|/)(vercel|netlify|docker|nginx|traefik|caddy|proxy|middleware|next\.config|README|docs|\.env\.example|\.github/workflows).*'

Repository: projectamazonph/amph-v2

Length of output: 15157


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the exact auth action implementation with line numbers.
cat -n src/app/actions/auth.ts | sed -n '1,220p'

# Look for deployment or middleware code that normalizes client IPs.
rg -n "x-forwarded-for|x-real-ip|headers\(\)|NextRequest|middleware|proxy|rateLimit|rate limit|rate-limit" src .github docs README.md next.config.* -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**'

Repository: projectamazonph/amph-v2

Length of output: 17004


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== deployment docs mentioning Vercel / proxy / headers ==\n'
rg -n "Vercel|x-forwarded-for|x-real-ip|proxy|CDN|edge|headers" README.md docs/LAUNCH-DEPLOY.md docs/runbooks/production-deploy.md docs/SECURITY.md docs/ARCHITECTURE.md docs/security/code-audit-2026-07-15.md next.config.ts src/middleware.ts src/lib/admin-audit.ts

printf '\n== next.config.ts ==\n'
cat -n next.config.ts | sed -n '1,220p'

printf '\n== production deploy runbook ==\n'
sed -n '1,240p' docs/runbooks/production-deploy.md

printf '\n== launch deploy doc ==\n'
sed -n '1,220p' docs/LAUNCH-DEPLOY.md

Repository: projectamazonph/amph-v2

Length of output: 24340


Normalize x-forwarded-for before using it as the IP rate-limit key. The header can contain multiple hops, so the same client can land in different buckets depending on proxy chain. Parse the first IP in both sign-up and sign-in paths.

🤖 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 `@src/app/actions/auth.ts` around lines 36 - 37, Update the IP extraction in
both sign-up and sign-in paths around the headers() calls to normalize
x-forwarded-for by parsing its first comma-separated IP before using it as the
rate-limit key. Preserve the x-real-ip fallback and 127.0.0.1 default when
x-forwarded-for is absent.

@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: 2

🤖 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 `@src/lib/__tests__/rate-limit.test.ts`:
- Around line 1-2: Move the rate-limit unit test from the __tests__ directory to
sit beside rate-limit.ts at the src/lib level, preserving its Vitest imports and
test contents unchanged.
- Around line 49-63: Update the rate-limit test around rateLimit so it directly
verifies opportunistic cleanup removed the expired bucket, rather than inferring
cleanup from a subsequent allowed request. Expose cleanup through a separately
testable helper or a test-only state inspection hook in the rate-limit
implementation, and assert that expired-key is absent after inserting more than
10,000 entries.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 990ac93f-36f0-4987-b456-dcda38a7b1c0

📥 Commits

Reviewing files that changed from the base of the PR and between 3f5a293 and e6df084.

📒 Files selected for processing (1)
  • src/lib/__tests__/rate-limit.test.ts

Comment on lines +1 to +2
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { rateLimit } from '../rate-limit';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Keep this test next to rate-limit.ts.

Move src/lib/__tests__/rate-limit.test.ts to src/lib/rate-limit.test.ts. This file is a unit test for src/lib/rate-limit.ts, so it should live beside the module it tests.

As per coding guidelines, tests must use Vitest and live next to the code they test.

🤖 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 `@src/lib/__tests__/rate-limit.test.ts` around lines 1 - 2, Move the rate-limit
unit test from the __tests__ directory to sit beside rate-limit.ts at the
src/lib level, preserving its Vitest imports and test contents unchanged.

Source: Coding guidelines

Comment on lines +49 to +63
it('triggers opportunistic cleanup when buckets map exceeds 10,000 entries', () => {
// First, let's add some expired hits to the map
rateLimit('expired-key', 1, 1000);
vi.advanceTimersByTime(2000); // Expiry passed

// Now, insert 10,001 entries to force buckets.size > 10,000
for (let i = 0; i < 10005; i++) {
rateLimit(`key-${i}`, 1, 1000);
}

// Since 'expired-key' is past its expiry, it should have been cleaned up by the opportunistic cleanup.
// Let's verify that hitting it again behaves as a fresh hit
const res = rateLimit('expired-key', 1, 1000);
expect(res.allowed).toBe(true);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the cleanup assertion observable.

In src/lib/rate-limit.ts:25-49, expired hits are filtered on every request. Therefore, lines 61-62 pass even if the opportunistic cleanup loop is removed. Extract cleanup into a separately testable helper or add a test-only state inspection hook so this test proves that expired buckets are deleted.

🤖 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 `@src/lib/__tests__/rate-limit.test.ts` around lines 49 - 63, Update the
rate-limit test around rateLimit so it directly verifies opportunistic cleanup
removed the expired bucket, rather than inferring cleanup from a subsequent
allowed request. Expose cleanup through a separately testable helper or a
test-only state inspection hook in the rate-limit implementation, and assert
that expired-key is absent after inserting more than 10,000 entries.

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.

2 participants