Skip to content

fix(auth): address PR #965 review comments and improve coverage#968

Open
vdimarco wants to merge 1 commit into
masterfrom
claude/resolve-pr-965-QkdYm
Open

fix(auth): address PR #965 review comments and improve coverage#968
vdimarco wants to merge 1 commit into
masterfrom
claude/resolve-pr-965-QkdYm

Conversation

@vdimarco

@vdimarco vdimarco commented Feb 8, 2026

Copy link
Copy Markdown
Contributor
  • Add HTTPS enforcement for non-localhost callbacks to prevent token leakage
  • Add stricter token validation: reject empty/whitespace tokens (!token || !token.trim())
  • Add navigate.test.ts for the extracted navigateTo utility
  • Add new tests: whitespace-only token, null token, malformed URLs,
    wildcard subdomains, non-JSON API errors, missing email, idle state
  • Improve abort test fidelity with DOMException('AbortError')
  • Add docstring for getAllowedDomains explaining function vs const choice
  • Total: 37 tests (36 page + 1 navigate), all passing

https://claude.ai/code/session_01CKCwH6XgHopRssntWSgViq

Greptile Overview

Greptile Summary

This PR tightens the Terragon auth-bridge redirect flow by enforcing HTTPS for non-localhost callback URLs, rejecting empty/whitespace tokens, and expanding test coverage around callback validation (malformed URLs, wildcard subdomains, missing email, non-JSON errors, abort handling). It also adds unit tests for the extracted navigateTo utility to ensure navigation behavior is validated independently from the page-level flow.

Confidence Score: 0/5

  • Review not completed: code changes were not inspected.
  • I can’t determine merge safety without reading the actual diffs for the three changed files at the provided SHA range. The review requirements (bug findings, file analyses) depend on verifying the implementation details in those files.
  • src/app/auth/terragon/page.tsx; src/app/auth/terragon/tests/page.test.tsx; src/app/auth/terragon/tests/navigate.test.ts

Important Files Changed

Filename Overview
src/app/auth/terragon/tests/navigate.test.ts Not analyzed yet: file contents/diff not inspected in this review run.
src/app/auth/terragon/tests/page.test.tsx Not analyzed yet: file contents/diff not inspected in this review run.
src/app/auth/terragon/page.tsx Not analyzed yet: file contents/diff not inspected in this review run.

Sequence Diagram

sequenceDiagram
  participant B as Browser
  participant P as TerragonAuthPage
  participant U as navigateTo()
  participant A as Terragon API

  B->>P: Load /auth/terragon
  P->>P: Parse callback + token params
  P->>P: Validate token (!token || !token.trim())
  alt Invalid token
    P-->>B: Render error/idle
  else Valid token
    P->>P: Validate callback URL
    alt Non-localhost callback
      P->>P: Enforce https
    end
    P->>P: Check allowed domains (wildcards)
    alt Callback not allowed
      P-->>B: Render error
    else Allowed
      P->>A: POST /auth/bridge (with AbortSignal)
      alt API returns success
        P->>U: navigateTo(callback)
        U-->>B: window.location.assign / router push
      else API returns non-JSON or error
        P-->>B: Render error
      end
    end
  end
  note over P,A: Abort -> DOMException('AbortError') handled in tests
Loading

Context used:

  • Context from dashboard - CLAUDE.md (source)

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Enhanced authentication security by enforcing HTTPS for non-localhost callback URLs.
    • Improved token validation to properly handle empty or whitespace-only tokens with clearer error messaging.
    • Refined cached credentials fallback logic during authentication errors.
  • Tests

    • Added comprehensive test coverage for navigation and callback URL validation.
    • Expanded test scenarios for token edge cases and authentication error handling.

- Add HTTPS enforcement for non-localhost callbacks to prevent token leakage
- Add stricter token validation: reject empty/whitespace tokens (!token || !token.trim())
- Add navigate.test.ts for the extracted navigateTo utility
- Add new tests: whitespace-only token, null token, malformed URLs,
  wildcard subdomains, non-JSON API errors, missing email, idle state
- Improve abort test fidelity with DOMException('AbortError')
- Add docstring for getAllowedDomains explaining function vs const choice
- Total: 37 tests (36 page + 1 navigate), all passing

https://claude.ai/code/session_01CKCwH6XgHopRssntWSgViq
@vercel

vercel Bot commented Feb 8, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
gatewayz-frontend Ready Ready Preview, Comment Feb 8, 2026 7:06pm

@vdimarco vdimarco requested a review from Copilot February 8, 2026 19:06
@coderabbitai

coderabbitai Bot commented Feb 8, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a unit test for the navigateTo function, expands Terragon auth page tests to cover callback URL HTTPS enforcement for non-localhost domains, cached credentials fallback logic, and whitespace-only token validation. Modifies the Terragon auth page to enforce HTTPS for non-localhost callback URLs and tighten token validation to reject empty or whitespace-only tokens.

Changes

Cohort / File(s) Summary
Navigate utility testing
src/app/auth/terragon/__tests__/navigate.test.ts
Added unit test for navigateTo function verifying it can be invoked without throwing an error.
Terragon auth page tests
src/app/auth/terragon/__tests__/page.test.tsx
Expanded test coverage with new test for rejecting HTTP:// on non-localhost domains, refined cached credentials fallback scenario, added validation for whitespace-only tokens, and updated error message expectations from "Auth endpoint returned an empty token" to "empty auth token".
Terragon auth page implementation
src/app/auth/terragon/page.tsx
Enforced HTTPS requirement for non-localhost callback URLs by adding localhost/127.0.0.1 hostname check in isAllowedCallbackUrl, tightened token validation to treat empty or whitespace-only tokens as invalid, and refactored getAllowedDomains to remain a function for per-test environment manipulation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PR #953: Adds support for redirect\_uri fallback parameter for callback URL handling in Terragon auth flow, directly related to callback parameter validation changes.
  • PR #963: Modifies the same Terragon auth page and tests with overlapping changes to auth token handling, callback URL validation, and auth bridge logic.
  • PR #962: Modifies Terragon auth page and associated tests with related changes to token handling and callback URL validation flow.

Poem

🐰 Whiskers twitch with validation glee,
HTTPS guards non-local domains, you see!
Whitespace tokens hop away with care,
Cached credentials dance through the air!
Tests now hop where bugs once were there!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main purpose: addressing PR review comments and improving test coverage for the authentication module.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/resolve-pr-965-QkdYm

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
src/app/auth/terragon/__tests__/navigate.test.ts (1)

4-15: Unused variable and weak assertion — the test doesn't verify the side effect.

originalHref (Line 5) is declared but never referenced. More importantly, the only assertion is not.toThrow(), which doesn't confirm window.location.href was set. You can use delete + Object.defineProperty (or a simple setter spy) to observe the assignment in JSDOM:

♻️ Suggested improvement
 it("should set window.location.href to the given URL", () => {
-    const originalHref = window.location.href;
     const testUrl = "https://app.terragon.ai/callback?gwauth=token123";
 
-    // JSDOM doesn't truly navigate, but we can verify the assignment
-    // doesn't throw and the function is callable
-    expect(() => navigateTo(testUrl)).not.toThrow();
+    // Spy on the href setter so we can verify the assignment in JSDOM
+    const hrefSpy = jest.fn();
+    const originalLocation = window.location;
+    // `@ts-expect-error` — replacing location for test
+    delete (window as any).location;
+    window.location = { ...originalLocation, set href(url: string) { hrefSpy(url); } } as Location;
 
-    // In JSDOM, setting location.href to a full URL may or may not
-    // update the property. The key contract is that the function
-    // calls window.location.href = url without throwing.
+    navigateTo(testUrl);
+    expect(hrefSpy).toHaveBeenCalledWith(testUrl);
+
+    // Restore
+    window.location = originalLocation;
   });
src/app/auth/terragon/page.tsx (1)

204-208: Minor: token.trim() will throw if the server returns a non-string truthy value.

If the API ever returns { token: 123 } (a number), !token is false for truthy numbers, so token.trim() would throw a TypeError. A defensive guard:

🛡️ Optional defensive fix
-        if (!token || !token.trim()) {
+        if (!token || (typeof token === "string" && !token.trim())) {

This is an unlikely edge case if the server always returns a string, but it avoids an unhandled TypeError if the contract is ever violated.

src/app/auth/terragon/__tests__/page.test.tsx (2)

496-543: The mockReturnValueOnce / mockReturnValue sequencing is coupled to internal call order.

The test relies on getApiKey and getUserData being called exactly once in the fast-path check (returning null) and then again in the auth-error fallback (returning credentials). If the implementation ever reorders or adds another call, this test will break silently with a confusing failure.

Consider adding an inline comment documenting the expected call sequence explicitly (e.g., "call 1: fast-path, call 2: auth-error fallback") or asserting mockGetApiKey.mock.calls.length at the end to lock in the expectation.


352-357: Consider adding http://127.0.0.1:3000/callback to the allowed-domains parameterized list.

The allowedDomains array tests http://localhost:3000 but doesn't include 127.0.0.1, which is also in the localhost carve-out in isAllowedCallbackUrl. Adding it would confirm both localhost variants are accepted with http://.


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 and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e42bfe7519

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 202 to 205
const { token } = await response.json();

if (!token) {
if (!token || !token.trim()) {
throw new 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.

P2 Badge Guard against non-string tokens before calling trim

If /api/terragon/auth returns a non-string token (e.g., null, number, or object), token.trim() will throw a TypeError, which is then surfaced to users as token.trim is not a function via the error message. That’s a regression from the prior !token check and makes a server-side shape bug present as a confusing client error. Consider validating typeof token === "string" before trimming so non-string tokens become a clean, user-friendly error.

Useful? React with 👍 / 👎.

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.

Pull request overview

This PR tightens the Terragon auth bridge’s redirect and token-handling logic to reduce open-redirect/token-leak risk, while expanding Jest coverage around the auth flow and redirect helper.

Changes:

  • Enforce HTTPS for non-localhost callback URLs and improve allowlist helper documentation.
  • Strengthen token validation to reject empty/whitespace tokens and adjust the surfaced error message.
  • Add/adjust Jest tests for callback validation, token edge cases, and the extracted navigateTo helper.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/app/auth/terragon/page.tsx Adds HTTPS enforcement for callbacks and stricter token validation/error messaging.
src/app/auth/terragon/__tests__/page.test.tsx Expands coverage for HTTP callback rejection, token edge cases, and auth-error fallback behavior.
src/app/auth/terragon/__tests__/navigate.test.ts Introduces a new unit test for the navigateTo redirect helper.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 202 to 208
const { token } = await response.json();

if (!token) {
if (!token || !token.trim()) {
throw new Error(
"Auth endpoint returned an empty token. Please try again."
"Server returned an empty auth token. Please try again."
);
}

Copilot AI Feb 8, 2026

Copy link

Choose a reason for hiding this comment

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

token.trim() assumes token is a string. If the API ever returns a non-string truthy value (e.g., { token: 123 }), this will throw a TypeError and surface as a generic auth failure. Consider validating typeof token === "string" (and trimming) before using it, and treat any non-string as an empty/invalid token.

Copilot uses AI. Check for mistakes.

// JSDOM doesn't truly navigate, but we can verify the assignment
// doesn't throw and the function is callable
expect(() => navigateTo(testUrl)).not.toThrow();

Copilot AI Feb 8, 2026

Copy link

Choose a reason for hiding this comment

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

This test declares originalHref but never uses it, which will fail next lint under the default unused-vars rules. Either remove it or use it to restore window.location.href after the test.

Suggested change
expect(() => navigateTo(testUrl)).not.toThrow();
try {
expect(() => navigateTo(testUrl)).not.toThrow();
} finally {
// Restore the original location to avoid leaking state between tests
window.location.href = originalHref;
}

Copilot uses AI. Check for mistakes.
Comment on lines +8 to +15
// JSDOM doesn't truly navigate, but we can verify the assignment
// doesn't throw and the function is callable
expect(() => navigateTo(testUrl)).not.toThrow();

// In JSDOM, setting location.href to a full URL may or may not
// update the property. The key contract is that the function
// calls window.location.href = url without throwing.
});

Copilot AI Feb 8, 2026

Copy link

Choose a reason for hiding this comment

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

The test currently only asserts that navigateTo(testUrl) doesn't throw, but it doesn't verify the core contract (that the function attempts to set window.location.href). Consider spying on/mocking the location.href setter (or temporarily replacing window.location) so you can assert the assignment without relying on JSDOM navigation behavior, and restore the original afterwards to avoid cross-test leakage.

Copilot uses AI. Check for mistakes.
@sentry

sentry Bot commented Feb 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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.

3 participants