Skip to content

Fix OAuth signature generation in validateRestApiAccess - #2

Closed
Mwalek wants to merge 4 commits into
GravityKit:mainfrom
Mwalek:fix/oauth-validation-headers
Closed

Fix OAuth signature generation in validateRestApiAccess#2
Mwalek wants to merge 4 commits into
GravityKit:mainfrom
Mwalek:fix/oauth-validation-headers

Conversation

@Mwalek

@Mwalek Mwalek commented Dec 30, 2025

Copy link
Copy Markdown
Contributor

Summary

  • Fix OAuth 1.0a signature generation in validateRestApiAccess function
  • Resolve "REST API validation failed: undefined" error when using HTTP connections

Problem

The validateRestApiAccess function was calling getAuthHeaders() without the required URL parameter for OAuth 1.0a signature generation. This caused authentication failures when using HTTP connections (which fall back to OAuth instead of Basic Auth).

Steps to Reproduce

  1. Clone and configure GravityMCP with HTTP base URL:

    git clone https://github.com/GravityKit/GravityMCP.git
    cd GravityMCP
    npm install
    cp .env.example .env
    # Configure .env with HTTP URL (e.g., http://localhost:31337)
  2. Add to Claude Code:

    claude mcp add gravitymcp --scope user -- node /path/to/GravityMCP/src/index.js
  3. Check MCP server status:

    claude mcp list
  4. Expected: gravitymcp shows "✓ Connected"

  5. Actual: gravitymcp shows "✗ Failed to connect" with error "REST API validation failed: undefined"

Solution

  • Get baseURL from httpClient.defaults.baseURL
  • Construct full URL for each endpoint being validated
  • Pass proper parameters (method, url, params) to getAuthHeaders()

Test plan

  • Verified fix with npm run check-env on HTTP localhost connection
  • All 3 endpoints (forms, entries, feeds) now validate successfully with OAuth
  • Confirmed claude mcp list shows gravitymcp as connected after fix

Summary by CodeRabbit

  • Bug Fixes

    • OAuth signing now uses the full request URL derived from the client's base URL, improving signature accuracy and ensuring authenticated API calls validate correctly.
    • Validation now surfaces an error when a base URL is unavailable, preventing ambiguous requests and improving endpoint reliability.
  • Tests

    • Test helpers now initialize a default mock client base URL for more consistent test behavior.

The validateRestApiAccess function was calling getAuthHeaders() without
the required URL parameter for OAuth 1.0a signature generation. This
caused authentication failures when using HTTP connections (which fall
back to OAuth instead of Basic Auth).

Changes:
- Get baseURL from httpClient.defaults.baseURL
- Construct full URL for each endpoint
- Pass proper parameters (method, url, params) to getAuthHeaders()

This fixes the "REST API validation failed: undefined" error when
connecting via HTTP/OAuth.
@coderabbitai

coderabbitai Bot commented Dec 30, 2025

Copy link
Copy Markdown

Walkthrough

validateRestApiAccess now reads baseURL from httpClient.defaults.baseURL, constructs full endpoint URLs, and passes method, full URL, and query params to getAuthHeaders when generating OAuth headers for each endpoint.

Changes

Cohort / File(s) Summary
OAuth signature update
src/config/auth.js
validateRestApiAccess now derives baseURL from httpClient.defaults.baseURL, builds a fullUrl per endpoint, and calls getAuthHeaders('GET', fullUrl, { per_page: 1 }) instead of calling getAuthHeaders() with no args.
Test helper defaults
src/tests/helpers.js
MockHttpClient constructor now initializes this.defaults = { baseURL: 'https://test.example.com' }.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 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 pull request title 'Fix OAuth signature generation in validateRestApiAccess' directly and accurately summarizes the main change—fixing how OAuth headers are generated in the validateRestApiAccess function by passing proper URL parameters to getAuthHeaders().
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

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.

@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

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 958811a and 9b9247e.

📒 Files selected for processing (1)
  • src/config/auth.js
🧰 Additional context used
🧬 Code graph analysis (1)
src/config/auth.js (2)
scripts/check-env.js (1)
  • httpClient (109-112)
src/tests/authentication.test.js (2)
  • headers (46-46)
  • headers (113-113)

Comment thread src/config/auth.js

@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

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9b9247e and 75dccf1.

📒 Files selected for processing (1)
  • src/config/auth.js
🧰 Additional context used
🧬 Code graph analysis (1)
src/config/auth.js (2)
scripts/check-env.js (2)
  • httpClient (109-112)
  • authManager (106-106)
src/tests/authentication.test.js (2)
  • headers (46-46)
  • headers (113-113)
🔇 Additional comments (1)
src/config/auth.js (1)

330-332: LGTM! OAuth signature generation fix is correct.

The fix properly constructs the full URL and passes it to getAuthHeaders along with the HTTP method and query parameters. This ensures OAuth1Handler can generate valid signatures for API requests.

The implementation is consistent with OAuth1Handler.testConnection (lines 159-160) and correctly addresses the authentication failures described in the PR objectives.

Comment thread src/config/auth.js
Mwalek and others added 2 commits December 30, 2025 13:52
Prevents TypeError when httpClient.defaults is undefined by using
optional chaining (httpClient?.defaults?.baseURL).

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

🧹 Nitpick comments (1)
src/config/auth.js (1)

328-336: Core fix is correct — OAuth signatures now use the full URL.

Constructing fullUrl from baseURL + endpoint.path and passing method, fullUrl, and params to getAuthHeaders properly aligns the OAuth 1.0a signature base string with the actual request, fixing the authentication failure.

One minor robustness note: if baseURL ever ends with a trailing /, the concatenation would produce a double-slash (e.g., .../gf/v2//forms), causing an OAuth signature mismatch. Currently this is safe because callers strip trailing slashes, but a one-line normalization would make this resilient to future callers:

🛡️ Optional: normalize trailing slash
     const baseURL = httpClient?.defaults?.baseURL;

     if (!baseURL) {
       throw new Error('httpClient baseURL is not configured');
     }

     const results = [];
     for (const endpoint of endpoints) {
       try {
         // Generate proper OAuth headers with full URL for signature
-        const fullUrl = `${baseURL}${endpoint.path}`;
+        const fullUrl = `${baseURL.replace(/\/+$/, '')}${endpoint.path}`;
         const headers = authManager.getAuthHeaders('GET', fullUrl, { per_page: 1 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/config/auth.js` around lines 328 - 336, The concatenation of baseURL and
endpoint.path can yield a double slash if baseURL ends with '/', so normalize
baseURL before building fullUrl: trim any trailing '/' (or ensure exactly one
'/' separator) prior to creating fullUrl used by getAuthHeaders and the
httpClient call; update the code around fullUrl, baseURL, endpoint.path, and
getAuthHeaders to perform this one-line normalization to make OAuth signature
generation resilient.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/config/auth.js`:
- Around line 320-325: The current change correctly guards against missing
defaults by using optional chaining to read baseURL (const baseURL =
httpClient?.defaults?.baseURL) and throws a clear error (throw new
Error('httpClient baseURL is not configured')) to avoid generating malformed
URLs; keep this defensive check in src/config/auth.js, ensure the
httpClient?.defaults?.baseURL expression is used wherever baseURL is required
(e.g., OAuth signature generation) and preserve the early throw to fail fast
when baseURL is absent.

---

Nitpick comments:
In `@src/config/auth.js`:
- Around line 328-336: The concatenation of baseURL and endpoint.path can yield
a double slash if baseURL ends with '/', so normalize baseURL before building
fullUrl: trim any trailing '/' (or ensure exactly one '/' separator) prior to
creating fullUrl used by getAuthHeaders and the httpClient call; update the code
around fullUrl, baseURL, endpoint.path, and getAuthHeaders to perform this
one-line normalization to make OAuth signature generation resilient.

@zackkatz

Copy link
Copy Markdown
Member

Closing — all changes from this PR were included in PR #3, which has been merged as part of v1.0.5. Thank you @Mwalek!

@zackkatz zackkatz closed this Feb 18, 2026
zackkatz added a commit that referenced this pull request Mar 20, 2026
Tests written first (22 regression tests), then code fixed:

- #2: Replace console.log with logger in 7 files (prevents stdout corruption)
- #4: Update mcp.json: remove phantom gf_submit_form, add 4 field ops, fix count
- #5: Add MCP tool annotations to all 27 tools (readOnlyHint, destructiveHint, etc.)
- #7: Strip _variant/_meta from validated fields before API payload
- #8: Let field operation errors propagate to wrapHandler (sets isError: true)
- #9: Fix name field sub-input IDs (.2=prefix not first, .3=first not prefix)
- #20: Remove deprecated crypto npm dependency (Node built-in)
- #21: Remove unused form-data dependency
- #23: Sync mcp.json version with package.json (1.4.0)
- #24: Fix feature filter key: supportsConditional → supportsConditionalLogic
- #25: Add ALLOW_DELETE to gf_delete_feed description

Test runner expanded from 7 to 10 suites. 234 tests, 100% pass rate.
zackkatz added a commit that referenced this pull request Jun 16, 2026
…— TDD

All four findings fixed test-first (RED watched, then GREEN). Logic for the
three index.js issues was extracted into src/server-runtime.js so it's
unit-testable (index.js self-runs main() and isn't importable).

- #1 serial init: WordPress plane now starts before the Gravity Forms REST
  probe is awaited, so a slow/bad GF config no longer stalls the WP plane or
  the abilities load. (runPlaneInit)
- #2 tool advertising: gf_* + field-op tools are listed only when the GF
  plane is live, so a WP-only install doesn't advertise tools that error on
  call. (buildToolList, gated on gravityFormsClient)
- #3 dispatch: the call router no longer hard-codes name.startsWith('gv_').
  It routes by ability-handler-map membership, so any GravityKit product
  prefix (gc_, …) dispatches, not just GravityView's gv_. (classifyAbilityCall)
- #4 wp-client: WordPressClient refuses to send Basic auth over a remote
  plain-HTTP URL (credential exposure) unless GRAVITY_FORMS_ALLOW_HTTP_BASIC_AUTH
  =true, reusing isLocalUrl — matching the GF plane's guard.

Tests: test/server-runtime.test.js (10), test/wp-client.test.js (4), wired
into test:node. server-runtime.js added to the AGENTS.md repo map.

Verified: test:node 165, test:unit 269, prepublishOnly gate green; live MCP
smoke (SDK client → node src/index.js) shows 76 tools (26 gf_ + 49 gv_ +
gk_reload_abilities) and gv_layouts_list dispatches.
justinritchie added a commit to justinritchie/MCP that referenced this pull request Aug 26, 2026
…ped in transit

TICKET items GravityKit#2 and GravityKit#3. gf_submit_form_data reported every field empty ('This
field is required' for all of them) and gf_update_entry returned a clean 200
over a no-op.

Root cause proven before writing code (scripts/dynamic-key-probe.mjs): the
values DO reach the server over stdio and land correctly. An MCP client strips
loose top-level input_N / numeric keys before the request is sent, and
additionalProperties:true — which BOTH tools already declared — does not protect
them. Third instance of this failure mode today, after markupVersion and
set_content's object params.

Adds a declared `values` object to both tools, expanded server-side: bare '1',
prefixed 'input_1' and sub-input '1.3' all normalise to GF's wire form. Loose
keys still work and are merged first, so direct/stdio callers are unaffected.

gf_update_entry now REFUSES an update carrying neither field values nor status,
naming the stripping as the likely cause. That call could previously only be a
no-op returning 200 — the exact shape that cost the ticket author a debugging
session.

Verified 8/8 on aaru65 (scripts/values-param-verify.mjs): submit lands and the
value reads back off the created entry, input_-prefixed keys normalise, update
lands, the empty update is refused, and a status-only update still works.

One fixture note worth keeping: the first run failed the submit cases because
aaru65 rejects @example.com addresses outright. That looked exactly like the
values param not working. Fixture now uses a real domain.

Suite unchanged at 11/16. Scratch forms 27 and 28 trashed; entry 2357 — the
blank row the ticket left on the live waitlist, which this test had filled with
a visible marker — moved to trash so it stops rendering in View 4839.
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