fix(cloud_function): return full base64 state for v0.0.9+ compatibility#325
fix(cloud_function): return full base64 state for v0.0.9+ compatibility#325bs-nubank wants to merge 4 commits intogemini-cli-extensions:mainfrom
Conversation
The workspace-server v0.0.9 changed how the OAuth state parameter is validated on callback. It now expects to receive the full base64-encoded JSON state back from the cloud function, then decodes it to extract the `csrf` field for comparison. The cloud function was returning only `payload.csrf` (the raw hex token), which worked for ≤ v0.0.7 but breaks v0.0.9+: `JSON.parse` fails on the raw hex, csrf is set to null, and every auth attempt ends with: "State mismatch. Possible CSRF attack." Fix: return the original `state` parameter unchanged so the client can decode it regardless of version.
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request updates the handleCallback function in cloud_function/index.js to return the original base64-encoded state parameter instead of the extracted hex CSRF token, aiming to support newer workspace-server versions. Feedback indicates that this change is currently incompatible with the existing server-side validation logic in AuthManager.ts, which expects the raw hex token, and suggests that a coordinated update is necessary to avoid breaking the authentication flow.
| if (state) { | ||
| finalUrl.searchParams.append('state', state); | ||
| } |
There was a problem hiding this comment.
The change to return the full base64-encoded state string is incompatible with the current implementation of the callback validation in workspace-server/src/auth/AuthManager.ts.
In AuthManager.ts (lines 356-361), the server expects the state parameter to be the raw hex CSRF token and performs a direct comparison:
const returnedState = qs.get('state');
if (returnedState !== csrfToken) {
res.end('State mismatch. Possible CSRF attack.');
// ...
}If the cloud function is updated to return the base64-encoded JSON string, this comparison will fail because returnedState (base64) will not match csrfToken (hex). This will break the authentication flow for all users of the current server implementation.
To resolve this, the server-side logic in AuthManager.ts must be updated to decode the base64 state and extract the csrf field before validation, as mentioned in the PR description. Please ensure that the server-side changes are included in this PR or a coordinated update.
…F validation The cloud function fix (return full base64 state instead of raw payload.csrf) is not backward-compatible with workspace-server ≤v0.0.7, which compared the returned state directly against the raw hex csrfToken. AuthManager.authWithWeb now tries to decode the returned state as base64 JSON and extract the `csrf` field (v0.0.9+ cloud function format). If that fails (parse error or missing field), it falls back to treating the value as a raw hex token (≤v0.0.7 cloud function format). This means the same AuthManager binary works regardless of which cloud function version is deployed — no coordinated rollout required.
Adds the extractCsrfFromState helper (mirrors AuthManager logic) and a dedicated describe block with 6 test cases covering: - base64 JSON format (v0.0.9+ cloud function) - raw hex format (≤v0.0.7 cloud function) - null / empty state - base64 JSON without csrf field (fallback) - non-JSON base64 garbage (fallback)
Adds index.test.js with 6 test cases that verify the state parameter is returned unchanged to the client (instead of just payload.csrf). Key assertions: - Returned value is the full base64 state (client can decode csrf from it) - Returned value is NOT the raw hex csrf (old buggy behaviour is gone) - manual=true flow returns null (no redirect) - Oversized state (>4KB) throws - Full roundtrip preserves uri, manual and csrf fields Also adds jest as a devDependency and a test npm script to package.json.
c074336 to
4469af1
Compare
Problem
Starting with workspace-server v0.0.9, the OAuth callback state validation was updated to decode the full base64 JSON payload returned by the cloud function, then extract the
csrffield from it:However, the cloud function still returns only the raw
csrfhex string in thestateparameter:When the extension receives this raw hex value and tries to
Buffer.from(hex, 'base64')→JSON.parse, it fails silently, setscsrftonull, and the validation always returns:This makes every browser-based auth attempt on v0.0.9 fail. Users are stuck in a broken auth loop — the only workaround is the headless login (
node index.js login).Root cause
The extension code was updated in v0.0.9 to a new validation protocol (decode the full state, extract csrf) but the cloud function was not updated to match — it still uses the old protocol (return only the raw csrf hex).
Fix — 3 commits
1.
fix(cloud_function): Return originalstateparameter unchanged2.
fix(auth): Support both state formats in AuthManager for backward compatibilityThe cloud function fix changes the
statevalue from raw hex to full base64 JSON.workspace-server ≤v0.0.7compares the returned state directly against the localcsrfToken(raw hex) — so after the cloud function is updated, those users would break.AuthManager.authWithWebnow handles both formats:This means both old and new cloud function versions work without a coordinated rollout.
3. Tests
cloud_function/index.test.js— 6 tests for state passthrough logic (verifies fix is correct, verifies old behaviour is gone)workspace-server/src/__tests__/auth/AuthManager.test.ts— 6 tests for CSRF extraction covering both format variants, null/empty state, and edge casesHow to reproduce
State mismatch. Possible CSRF attack.in the browserWorkaround (until this is deployed)
node "/path/to/Claude Extensions/ant.dir.ant.0x55979e1.google-workspace/server/index.js" login