fix(FR-3058): send empty JSON body for non-GET requests with null body#7651
fix(FR-3058): send empty JSON body for non-GET requests with null body#7651agatha197 wants to merge 1 commit into
Conversation
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has required the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
Coverage Report for root-coverage
File CoverageNo changed files found. |
ed46ce5 to
4e8dcc7
Compare
There was a problem hiding this comment.
Pull request overview
Adjusts the Node/Electron-side Backend.AI client request builder to send a valid empty JSON body ({}) when call sites pass null/undefined for non-GET/HEAD requests, addressing manager endpoints that reject an empty string body with 400.
Changes:
- Update
Client.newSignedRequest()to send'{}'instead of''whenbodyisnull/undefinedfor non-GET/HEADmethods. - Preserve
''behavior forGET/HEADrequests.
nowgnuesLee
left a comment
There was a problem hiding this comment.
Please resolve the copilot's reviews.
When body is null/undefined, newSignedRequest() was sending an empty
string '' as the request body. Some backends now require at least a
valid empty JSON body '{}' for POST/PATCH/DELETE requests.
Send '{}' for non-GET/HEAD methods while keeping authBody as ''
to preserve HMAC signatures for legacy (v<4) backends.
4e8dcc7 to
04ccea6
Compare
|
@nowgnuesLee Addressed both of Copilot's review comments in the latest commit:
Both threads are resolved and |
nowgnuesLee
left a comment
There was a problem hiding this comment.
If we modify this part, it seems like it could cause side effects across the entire project. Is this the intended area to change? And add a number of jira issue on title and body.
|
@nowgnuesLee on the side-effect concern: the change is scoped to the null/undefined-body branch of On the Jira number: this generalized fix needs its own issue key (it's broader than FR-2978, which #7609 covers specifically). I'll update the title to |
| // Send '{}' for methods that carry a body; keep '' for GET/HEAD. | ||
| // authBody mirrors requestBody so the v<4 signature (which hashes the | ||
| // body) matches what is actually sent; v4+ excludes the body anyway. | ||
| requestBody = method !== 'GET' && method !== 'HEAD' ? '{}' : ''; |
There was a problem hiding this comment.
Please check the backend changes related to this part again. The fact that {} must be sent unconditionally is strange.
|
Closing in favor of the targeted fix in #7609. Why: This PR made #7609 fixes the real bug in a targeted way (explicit Separately discovered (follow-ups, not addressed here): |
…400 (#7609) **Jira**: [FR-2978](https://lablup.atlassian.net/browse/FR-2978) > Targets `main` directly. The previously-stacked generalized fix (#7651, which made `newSignedRequest` send `{}` for *every* null-body non-GET request) was dropped: on legacy backends `@check_api_params` only falls back to `dict(request.query)` when the body is empty, so a blanket `{}` body would suppress that fallback and silently drop query-string params on `destroy`/`restart` (e.g. `forced`, `owner_access_key`). This PR keeps the fix targeted to `leave_invited`, the one endpoint whose handler (`BodyParam[LeaveVFolderReq]`) actually rejects a null body. It also extends the client signature with the optional `sharedUserUuid` parameter (admin-on-behalf-of). ## Summary Fixes a 400 error when a user tries to leave a shared folder via the **Shared Folder Permission** modal (both V1 `SharedFolderPermissionInfoModal` and V2 `SharedFolderPermissionInfoModalV2`). ## Root cause The Backend.AI manager's `POST /folders/{name}/leave` REST route parses its body via `BodyParam[LeaveVFolderReq]`. All fields on `LeaveVFolderReq` are optional, but the body itself must still be valid JSON. The frontend client's `VFolder.leave_invited` was passing `null` as the body. `newSignedRequest` serializes `null` to an empty string `''` and sends it with `Content-Type: application/json`. An empty string is not valid JSON, so the manager rejects the request with 400 before the handler runs. Both `SharedFolderPermissionInfoModal.tsx` (V1) and `SharedFolderPermissionInfoModalV2.tsx` (V2) call the same `leave_invited` client method, so the bug reproduces in both flows. ## Change The fix is applied in **both** copies of the API client to keep them consistent: - `packages/backend.ai-client/src/resources/vfolder.ts` — **load-bearing for the React UI**. `react/src/global-stores.ts` registers `globalThis.BackendAIClient = ai.backend.Client` from this workspace package. This is what runs in the browser. - `src/lib/backend.ai-client-node.ts` — legacy ESM client; only consumed by `src/wsproxy/` (Electron desktop's websocket proxy). Kept in sync to avoid a divergent regression if the Electron side ever calls the leave flow. - `react/src/hooks/index.tsx` — extends the `BackendAIClient.vfolder.leave_invited` TypeScript signature with the new optional `sharedUserUuid` parameter (matches `LeaveVFolderReq.shared_user_uuid` for future admin-on-behalf-of flows). Both methods now send `{}` as the JSON body by default. When `sharedUserUuid` is provided, they send `{ shared_user_uuid: sharedUserUuid }`. No existing call site needs to change for the immediate fix — both modals continue calling `leave_invited(folderId)` with a single argument. ## Test plan - [ ] As a regular user with an accepted folder invitation, open the data page → click on the shared folder → permission modal → leave button → confirm. Expect a success toast and the folder to disappear from the list (no 400). - [ ] Repeat for both V1 modal (regular page) and V2 modal (project admin data page). - [ ] CI: lint / format / relay drift / TypeScript pass. ## Verification `bash scripts/verify.sh` was run locally. Relay, Lint, Format pass. The TypeScript step reports pre-existing environmental errors (duplicate `@types/react` between `react/` and `packages/backend.ai-ui/`) that affect files unrelated to this change — CI's clean install is authoritative. [FR-2978]: https://lablup.atlassian.net/browse/FR-2978?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

Jira: FR-3058
Summary
When the request
bodyisnull/undefined,Client.newSignedRequest()previously sent an empty string''as the HTTP body. Some backend REST endpoints now require at least a valid empty JSON body ({}) for methods that carry a body (e.g.POST /vfolders/{name}/leave), and reject''with a 400.This centralizes the fix in
newSignedRequest()so every call site that passes anullbody is handled at once:'{}'for non-GET/HEADmethods when no body is provided.''forGET/HEAD(query-based requests are unchanged).authBodyas''so HMAC signatures are unaffected — on API v4+ the body is already excluded from the signature, and on legacy (v<4) backends keeping''preserves the existing signature.Affected call sites (previously passing
null)VFolder.leave_invited()—POST /vfolders/{name}/leave(confirmed broken without{})Client.restart()—PATCH /kernel/{id}Client.refreshSSHKeypair()—PATCH /auth/ssh-keypairClient.destroy()—DELETE /kernel/{id}Client.check_login()—POST /server/login-check(SESSION mode)Endpoints that already pass an explicit object or
{}are unaffected. GET requests using query strings are intentionally left as-is.Test plan
PATCH /kernel/{id}succeeds.PATCH /auth/ssh-keypairsucceeds.