Skip to content

fix: stop mutating token refresh errors into auth errors#329

Merged
rhamzeh merged 9 commits into
mainfrom
fix/issue-328-auth-error-retyping
May 7, 2026
Merged

fix: stop mutating token refresh errors into auth errors#329
rhamzeh merged 9 commits into
mainfrom
fix/issue-328-auth-error-retyping

Conversation

@aaguiarz

@aaguiarz aaguiarz commented Feb 16, 2026

Copy link
Copy Markdown
Member

Summary

Replaces error-instance mutation in credential token refresh with construction of a real FgaApiAuthenticationError.

Changes

  • Added regression test reproducing failed toThrow(FgaApiAuthenticationError) when token endpoint returns 500
  • Replaced constructor/name property mutation with explicit creation of FgaApiAuthenticationError
  • Preserved auth context (client_id, audience, grant_type) in the generated error payload

Potential Breaking Change

  • Token refresh failures now throw a real FgaApiAuthenticationError instance rather than a mutated API error object.
  • Consumers that depended on the previous underlying runtime type/prototype or exact message text may need to update error handling.

Fixes #328

Summary by CodeRabbit

  • Bug Fixes

    • Authentication errors now include additional diagnostic context (client ID, audience, grant type) for improved troubleshooting.
    • Enhanced error message standardization and handling for various authentication failure scenarios.
    • Improved support for token refresh failure detection and reporting.
  • Tests

    • Added test coverage for authentication error handling scenarios.

Copilot AI review requested due to automatic review settings February 16, 2026 18:53
@aaguiarz
aaguiarz requested a review from a team as a code owner February 16, 2026 18:53
@coderabbitai

coderabbitai Bot commented Feb 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5918baeb-3b31-4419-a63d-c5d797be545d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

The changes fix issue #328 by replacing error object mutation with proper instantiation of FgaApiAuthenticationError. The error class is enhanced to accept both AxiosError and FgaApiError with optional authentication context fields, supported by a new helper function for standardized error messaging.

Changes

Cohort / File(s) Summary
Error Class Enhancement
errors.ts
Added getAuthenticationErrorMessage() helper function to standardize error messaging from AxiosError or FgaApiError sources. Updated FgaApiAuthenticationError constructor to accept both AxiosError and FgaApiError with optional context fields (clientId, audience, grantType) instead of only AxiosError.
Error Handling Integration
credentials/credentials.ts
Replaced error mutation pattern with instantiation of new FgaApiAuthenticationError, passing structured context payload (clientId, audience, grantType) to the constructor instead of mutating error properties.
Test Coverage
tests/credentials.test.ts
Added import for FgaApiAuthenticationError and new test case verifying that HTTP 404 token refresh failures reject with FgaApiAuthenticationError.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5 | ❌ 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 (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: replacing error mutation with proper FgaApiAuthenticationError construction.
Linked Issues check ✅ Passed The PR fully addresses issue #328 by constructing real FgaApiAuthenticationError instances instead of mutating FgaApiError, preserving auth context and fixing instanceof checks.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the error mutation issue: modified error handling in credentials.ts, enhanced FgaApiAuthenticationError constructor, added helper function, and included a regression test.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-328-auth-error-retyping

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.

@dosubot

dosubot Bot commented Feb 16, 2026

Copy link
Copy Markdown

Related Documentation

Checked 8 published document(s) in 1 knowledge base(s). No updates required.

How did I do? Any feedback?  Join Discord

@codecov-commenter

codecov-commenter commented Feb 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 76.92308% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.48%. Comparing base (6b03882) to head (6a12c10).

Files with missing lines Patch % Lines
errors.ts 80.00% 2 Missing and 7 partials ⚠️
credentials/credentials.ts 57.14% 0 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #329      +/-   ##
==========================================
- Coverage   85.59%   85.48%   -0.11%     
==========================================
  Files          26       26              
  Lines        1270     1309      +39     
  Branches      250      265      +15     
==========================================
+ Hits         1087     1119      +32     
- Misses        110      112       +2     
- Partials       73       78       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Pull request overview

This PR fixes token refresh failures being surfaced as mutated FgaApiError objects (breaking instanceof FgaApiAuthenticationError) by constructing and throwing a real FgaApiAuthenticationError, and adds a regression test for the failure mode described in #328.

Changes:

  • Add a regression test asserting token refresh failures throw a real FgaApiAuthenticationError.
  • Replace error-instance mutation in Credentials.refreshAccessToken() with explicit FgaApiAuthenticationError construction, preserving auth context fields.

Reviewed changes

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

File Description
tests/credentials.test.ts Adds a regression test ensuring token refresh failures reject with FgaApiAuthenticationError.
credentials/credentials.ts Stops mutating FgaApiError instances and instead throws a newly constructed FgaApiAuthenticationError with auth context.

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

Comment thread tests/credentials.test.ts Outdated
Comment thread credentials/credentials.ts Outdated
Comment thread credentials/credentials.ts Outdated
@aaguiarz

Copy link
Copy Markdown
Member Author

Addressed the review comments and pushed commit 8cfe19f.

What changed:

  • Added a direct FgaApiError -> FgaApiAuthenticationError constructor path in errors.ts.
    • This removes the manual Axios-shape object cast from credentials.ts.
    • It preserves stack/request/auth context while staying type-safe at the call site.
  • Updated token refresh error mapping in credentials.ts to throw:
    • new FgaApiAuthenticationError(err, { clientId, audience, grantType })
  • Updated the regression test to avoid retry backoff sleep overhead by using a non-retryable API failure (single 404 response), while still validating the instanceof FgaApiAuthenticationError behavior.

Validated locally:

  • npm test -- tests/credentials.test.ts -t "real FgaApiAuthenticationError"
  • npm test -- tests/index.test.ts -t "401 during authentication should result in FgaApiAuthenticationError"

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

🤖 Fix all issues with AI agents
Verify each finding against the current code and only fix it if needed.


In `@errors.ts`:
- Around line 217-221: The constructor for the error class currently ignores the
optional context when err is an AxiosError; update the AxiosError branch in the
constructor to apply context overrides (clientId, audience, grantType) after
extracting values from the parsed request data so that if
context.clientId/audience/grantType is provided it replaces the parsed value;
locate the AxiosError handling block in the constructor and merge values (e.g.,
finalClientId = context?.clientId ?? parsedClientId) for each of the three
fields and use those merged values where the code currently uses the parsed
ones.

In `@tests/credentials.test.ts`:
- Around line 541-569: Add assertions to the existing test that checks thrown
error properties: after awaiting
expect(credentials.getAccessTokenHeader()).rejects.toThrow(FgaApiAuthenticationError)
also capture the rejected error and assert its context fields and status code
are set correctly (e.g., verify error.clientId equals OPENFGA_CLIENT_ID,
error.audience equals OPENFGA_API_AUDIENCE, error.grantType equals
CredentialsMethod.ClientCredentials or the literal grant type string used, and
error.statusCode equals 404). Use the same test-local variables (apiTokenIssuer,
OPENFGA_CLIENT_ID, OPENFGA_API_AUDIENCE) and the
Credentials.getAccessTokenHeader / FgaApiAuthenticationError symbols to locate
where to add these assertions.
🧹 Nitpick comments (2)
🤖 Fix all nitpicks with AI agents
Verify each finding against the current code and only fix it if needed.


In `@errors.ts`:
- Around line 217-221: The constructor for the error class currently ignores the
optional context when err is an AxiosError; update the AxiosError branch in the
constructor to apply context overrides (clientId, audience, grantType) after
extracting values from the parsed request data so that if
context.clientId/audience/grantType is provided it replaces the parsed value;
locate the AxiosError handling block in the constructor and merge values (e.g.,
finalClientId = context?.clientId ?? parsedClientId) for each of the three
fields and use those merged values where the code currently uses the parsed
ones.

In `@tests/credentials.test.ts`:
- Around line 541-569: Add assertions to the existing test that checks thrown
error properties: after awaiting
expect(credentials.getAccessTokenHeader()).rejects.toThrow(FgaApiAuthenticationError)
also capture the rejected error and assert its context fields and status code
are set correctly (e.g., verify error.clientId equals OPENFGA_CLIENT_ID,
error.audience equals OPENFGA_API_AUDIENCE, error.grantType equals
CredentialsMethod.ClientCredentials or the literal grant type string used, and
error.statusCode equals 404). Use the same test-local variables (apiTokenIssuer,
OPENFGA_CLIENT_ID, OPENFGA_API_AUDIENCE) and the
Credentials.getAccessTokenHeader / FgaApiAuthenticationError symbols to locate
where to add these assertions.
errors.ts (1)

217-221: context parameter is silently ignored when err is an AxiosError.

The AxiosError branch (lines 264–272) extracts clientId, audience, and grantType only from parsed request data, ignoring the context argument. Today no caller passes context with an AxiosError, but a future caller could hit this silently. Consider applying context overrides in the AxiosError branch as well, or documenting that context is only used for FgaApiError inputs.

Proposed fix
-    this.clientId = data?.client_id;
-    this.audience = data?.audience;
-    this.grantType = data?.grant_type;
+    this.clientId = context?.clientId || data?.client_id;
+    this.audience = context?.audience || data?.audience;
+    this.grantType = context?.grantType || data?.grant_type;

Also applies to: 253-272

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@errors.ts` around lines 217 - 221, The constructor for the error class
currently ignores the optional context when err is an AxiosError; update the
AxiosError branch in the constructor to apply context overrides (clientId,
audience, grantType) after extracting values from the parsed request data so
that if context.clientId/audience/grantType is provided it replaces the parsed
value; locate the AxiosError handling block in the constructor and merge values
(e.g., finalClientId = context?.clientId ?? parsedClientId) for each of the
three fields and use those merged values where the code currently uses the
parsed ones.
tests/credentials.test.ts (1)

541-569: Good regression test — consider asserting error properties too.

The instanceof check directly validates the fix for #328. For stronger coverage, you could also verify that context fields (clientId, audience, grantType) and statusCode are correctly propagated on the thrown error.

Example: assert error properties
-      await expect(credentials.getAccessTokenHeader()).rejects.toThrow(FgaApiAuthenticationError);
+      await expect(credentials.getAccessTokenHeader()).rejects.toSatisfy((err: FgaApiAuthenticationError) => {
+        expect(err).toBeInstanceOf(FgaApiAuthenticationError);
+        expect(err.statusCode).toBe(404);
+        expect(err.clientId).toBe(OPENFGA_CLIENT_ID);
+        expect(err.audience).toBe(OPENFGA_API_AUDIENCE);
+        expect(err.grantType).toBe("client_credentials");
+        return true;
+      });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/credentials.test.ts` around lines 541 - 569, Add assertions to the
existing test that checks thrown error properties: after awaiting
expect(credentials.getAccessTokenHeader()).rejects.toThrow(FgaApiAuthenticationError)
also capture the rejected error and assert its context fields and status code
are set correctly (e.g., verify error.clientId equals OPENFGA_CLIENT_ID,
error.audience equals OPENFGA_API_AUDIENCE, error.grantType equals
CredentialsMethod.ClientCredentials or the literal grant type string used, and
error.statusCode equals 404). Use the same test-local variables (apiTokenIssuer,
OPENFGA_CLIENT_ID, OPENFGA_API_AUDIENCE) and the
Credentials.getAccessTokenHeader / FgaApiAuthenticationError symbols to locate
where to add these assertions.

@aaguiarz

aaguiarz commented Feb 16, 2026

Copy link
Copy Markdown
Member Author

Addressed the remaining CodeRabbit suggestions in commit f9d8594.

Changes made:

  • Updated the AxiosError branch in FgaApiAuthenticationError to apply context overrides for clientId, audience, and grantType using nullish coalescing.
  • Strengthened the regression test in tests/credentials.test.ts to assert propagated auth error fields and status code (404) in addition to type.

Validation run:

  • npm test -- tests/credentials.test.ts -t "real FgaApiAuthenticationError"

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

Pull request overview

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


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

Comment thread tests/credentials.test.ts
Comment thread errors.ts Outdated
@aaguiarz

Copy link
Copy Markdown
Member Author

Followed up on Copilot review #329 (review) in commit 40c23cc.

Actionable fixes applied:

  • errors.ts: switched the FgaApiError-branch context fallbacks from || to ?? for clientId, audience, and grantType to match Axios-branch semantics and preserve intentional falsy values.
  • tests/credentials.test.ts: aligned the regression scenario with PR intent by stubbing token endpoint failure as 500 (internal error), updated expected status to 500, and accounted for built-in retries with .times(4).

Validation run:

  • npm test -- tests/credentials.test.ts

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

Pull request overview

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


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

Comment thread credentials/credentials.ts
Comment thread errors.ts Outdated
Comment thread credentials/credentials.ts Outdated
@aaguiarz

Copy link
Copy Markdown
Member Author

Addressed actionable feedback from #329 (review) in commit b54331c.

Applied changes:

  • credentials/credentials.ts
    • Added explicit handling for FgaApiAuthenticationError in refreshAccessToken() to preserve/enrich auth context (clientId, audience, grantType) for 401/403 token-endpoint failures.
    • Replaced raw grant type literal with CredentialsMethod.ClientCredentials.
  • errors.ts
    • Added robust request-data parsing helper that supports both JSON and URL-encoded payloads (URLSearchParams) so clientId/audience/grantType extraction works when axios stores form data as querystring text.
  • tests/credentials.test.ts
    • Added regression test: should preserve auth context when token endpoint returns 401.

Validation run:

  • npm test -- tests/credentials.test.ts

@rhamzeh
rhamzeh enabled auto-merge May 7, 2026 05:01
@rhamzeh
rhamzeh added this pull request to the merge queue May 7, 2026
Merged via the queue into main with commit d5cbccb May 7, 2026
6 checks passed
@rhamzeh
rhamzeh deleted the fix/issue-328-auth-error-retyping branch May 7, 2026 21:18
emilic added a commit that referenced this pull request May 8, 2026
Conflicts:
* errors.ts — main brought in PR #329's "stop mutating token refresh
  errors into auth errors" fix. Adapted for the fetch-era branch:
  FgaApiAuthenticationError now accepts HttpErrorContext | FgaApiError.
  When given an existing FgaApiError (re-wrap path from credentials/),
  it COPIES fields and preserves the original stack — the canonical fix
  for the audit-confirmed `instanceof` mutation P0. The HttpErrorContext
  path is unchanged. Also added the optional `context?: { clientId,
  audience, grantType }` second parameter from main so callers can
  override extracted values.
* package.json — took main's bumped @opentelemetry/api ^1.9.1; left
  axios out (the whole point of this branch).
* package-lock.json — regenerated via `npm install` after resolving
  package.json (don't hand-merge lockfiles).

Removed two helper functions (parseRequestData, getAuthenticationErrorMessage)
that main introduced for the AxiosError path; the fetch branch doesn't have
AxiosError as a type, and the helpers had no other callers.
emilic added a commit that referenced this pull request May 8, 2026
Pulls the fetch-based HTTP client + the FgaApiAuthenticationError
re-wrap path (PR #329) into the WIP ESM/CJS dual-build branch.

Conflicts:
* package.json — kept ESM build setup (main / module / exports fields,
  build:cjs / build:esm / build:post scripts) AND took drop-axios's
  newer dep versions inherited from main: jest 30.3.0, ts-jest 29.4.9,
  eslint ^10.3.0, typescript ^6.0.3, nock ^14.0.14, @types/node ^25.6.0,
  @typescript-eslint/* ^8.59.2, @opentelemetry/api ^1.9.1. Removed
  tiny-async-pool + @types/tiny-async-pool (this branch already
  replaced tiny-async-pool with a native impl).

* package-lock.json — regenerated via \`npm install\`. 0 axios entries,
  0 tiny-async-pool entries, 0 vulnerabilities.

* tests/apiExecutor.test.ts, tests/credentials.test.ts,
  tests/headers.test.ts — pure import-style conflicts. Kept this
  branch's \`.js\` extensions on every relative import (mandatory for
  ESM resolution) and drop-axios's default \`import nock from "nock"\`
  shape (nock 14's main entry is callable only via the default export;
  \`import * as nock\` works for namespace methods but breaks
  \`nock(url)\` calls). credentials.test.ts also kept the
  FgaApiAuthenticationError import that PR #329's tests need
  (it was lost on this branch when feat/esm-modules forked from
  the pre-#329 base).

Files auto-merged: credentials/credentials.ts, errors.ts (which now
includes PR #329's re-wrap branch in FgaApiAuthenticationError —
typed \`HttpErrorContext | FgaApiError\` with optional context arg),
tests/client.test.ts, tests/index.test.ts, tests/jest.config.js,
tests/helpers/nocks.ts, tsconfig.json.

Build (cjs + esm) clean. Tests: 302 passed across 15 suites.
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.

fix: token refresh error mutation breaks instanceof FgaApiAuthenticationError

5 participants