Skip to content

feat(console): the installer OAuth client form shows the deployment's redirect URI and a reconnect keeps the stored client - #29772

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-80b6qg
Nov 3, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-80b6qg

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #28869

Clause-②: yes
Metadata-protocol: none

The card's second PR (claim 6581428199 as amended by 6581522588, option B, PR 2 leg). The card's first PR, #29496, landed the client form; this one adds item 2 and the "keep unless replaced" half of item 3, now that the server answers both (#29463, server PR #29701 c779611e; spec key ConnectorConnectionState.redirectUri from #29618 744211fc).

维护者速读

  • 改了什么:连接器连接页的 OAuth 客户端表单现在显示服务端给出的重定向 URI(连接状态读取里的 redirectUri),带「复制」按钮;服务端给不出时明确提示「此部署没有重定向 URI」,绝不自行拼接。重新连接已保存客户端的连接时,默认「沿用已存储的客户端」,发起连接请求不带请求体,由服务端复用已存的客户端和密钥;点「替换已存储的客户端」才显示 ID(预填)和空的密钥框并发送新客户端。断开连接后页面重新读取状态,下次「连接」仍能显示 URI。
  • 为什么改:console: the connector connection form asks the administrator for the OAuth client (client id + secret) when the instance takes an installer-supplied client (#28702 B, ui slice) #28869 第 2、3 项的后半,服务端 connector connection state: answer the derived OAuth redirect URI so the Console can show it before an installer registers a client #29463 已提供 URI 和「无请求体即复用」。
  • 风险与回滚:只改控制台一页和语言包。语言包新增 8 个键,改写 clientIntro、clientResend 两个键的文案,不删键。回滚即撤掉本 PR,控制台回到第一个 PR 的行为(重新连接需重输密钥)。
  • 先看:ui/packages/app-shell/src/views/setup/ConnectorConnectionsPage.tsx 的 startConnect(replacing: !state.client 默认沿用)、submitClient(沿用时 client 为 undefined、不带请求体)、confirmRevoke(断开后重新 GET)、表单里的重定向 URI 区块。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/setup/ConnectorConnectionsPage.test.tsx,应看到 21 条全过。
  • 需要你做的:无,席位落地。

新增语言键(connectorConnections 下,十种语言):redirectUriLabel、redirectUriCopy、redirectUriCopied、redirectUriHint、redirectUriMissing、clientKeepHint、clientKeep、clientReplace。改写文案的已有键:clientIntro、clientResend。

What changes

  • ConnectorConnectionsPage.tsx:
    • The installer-client form shows state.redirectUri exactly as GET /automation/connectors/:name/connection answered it, read-only, with a Copy button (navigator.clipboard.writeText; a denied clipboard leaves the selectable text). When the read answers none, the form says the deployment has no redirect URI and to set OS_BASE_URL / OS_AUTH_URL. No URI is built from the page's origin or parsed from prose.
    • A connection whose status carries client opens Reconnect in keep mode: no client fields, and Continue begins with no body, so the server reuses the kept client (ask 2 of connector connection state: answer the derived OAuth redirect URI so the Console can show it before an installer registers a client #29463). "Replace the stored client" switches to the fields (client ID prefilled, secret empty) and sends { clientId, clientSecret? }; "Keep the stored client" switches back.
    • Disconnect re-reads the status after the revoke, because the revoke answer carries no redirectUri. The read answer is applied (the revoke answer only if that read fails), so the next Connect still shows the URI.
  • @object-ui/i18n: eight new keys and two reworded values in all ten locales. clientIntro now points at the URI below instead of the documentation; clientResend is now the replace hint. No key is removed.
  • Changeset .changeset/28869-connect-redirect-uri-keep-client.md (@object-ui/app-shell, @object-ui/i18n: patch).

Tests

ConnectorConnectionsPage.test.tsx, 21 cases (10 in an installer-supplied OAuth client). New or changed:

  • Keep default: the stored client is shown, there are no client fields, and the begin POST has no body.
  • Replace path: the id is prefilled, the secret is empty, and the new { clientId, clientSecret } is sent; the secret is absent from the DOM afterwards.
  • The URI is shown exactly as answered (deliberately on another origin than the page), and Copy writes that value.
  • An absent URI shows the "has no redirect URI" line, no URI element and no Copy button, and the form text contains neither /oauth/callback nor the page origin.
  • A re-read after disconnect: one more GET after the DELETE, then the next Connect shows the URI.
  • The existing disconnect case now queues the post-revoke status read.

Acceptance notes

  • The redirect URI comes only from the status read; the begin answer and the refusal prose are not read for it.
  • Live run used a local stub for the vendor's authorize/token endpoints on app-crm, not a real vendor.

Verification

Page suite and ablations (head b223134; re-run on merge c67b39c)
  • Page suite: 21 passed on b223134. With packages/i18n/ on merge head c67b39c: Test Files 96 passed, Tests 1464 passed, 13 skipped (os-verify-lock VERDICT 0).
  • Ablations, each via scripts/ablation-replace.mjs, restored blob == HEAD with git diff HEAD empty:
    • B1, keep default off (replacing: true): 2 failed / 19 passed (keep pin, replace pin).
    • B2, replace path sends nothing when a client is stored: 1 failed / 20 passed (replace pin).
    • B3, URI guessed from window.location.origin when absent: 1 failed / 20 passed (absent-URI pin).
    • B4, no re-read after disconnect (const read = out): 1 failed / 20 passed (re-read pin).
  • ui vitest aliases @object-ui/* to source, so no rebuild leg was owed.
Packages and gates
  • turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/i18n on b223134: 66 tasks successful, VERDICT 0.
  • pnpm --dir ui exec vitest run scripts/ on c67b39c: Test Files 170 passed, 2 skipped; Tests 4977 passed.
  • dispatch-gates --commands on c67b39c: 35 families, all run, each exit 0. dispatch-gates --ran reconciles 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN.
  • Main re-merged at 9b3449b: six commits, no file shared with this branch, and the derivation gives the same 35 commands. On 9b3449b the page and packages/i18n/ suites pass again (96 files, 1464 passed, 13 skipped), and the @object-ui/app-shell type-check is clean after pnpm install picked up main's new pinyin-pro dependency.
  • pnpm check:changeset-fixed (roster under .changeset/): green.
Live run (app-crm, Vite source Console, head b223134)

objectstack dev --seed-admin --fresh on app-crm with OS_BASE_URL at the Console origin, the Vite Console proxying to it, a local vendor stub, and an installer connector saved through PUT /meta/connector/vendor_docs.

  1. The status read before any connect answered redirectUri: http://localhost:5233/api/v1/automation/connectors/oauth/callback with status not_connected. The form showed that value, and Copy put it on the clipboard.
  2. Connect with a client sent body keys clientId and clientSecret.
  3. Reconnect opened in keep mode with no inputs. Continue sent a POST with no body. The stub saw authorize with the stored client id and a token request authenticated as that client, and the reconnect landed with a new connectedAt.
  4. Replace showed the id prefilled and the secret empty.
  5. After Disconnect, Connect still showed the URI. The typed secret never appeared in the page HTML.

Generated by Claude Code

…lient on reconnect

The installer-client Connect form shows the redirect URI the connection read
answers (redirectUri), with a copy button, or says the deployment has none.
A reconnect of a connection that keeps an installer client keeps it by
default and begins with no body; replacing it sends the new client. A
disconnect re-reads the status so the next Connect still shows the URI.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jQhfprmgNtfhzNURJ6
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling labels Nov 3, 2026

Copy link
Copy Markdown
Collaborator

Contract-review-claim: session_01QsR2jV5sBSzCYNEDgpYLJw · head 9b3449b · 2026-11-03T04:12Z


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 9b3449be3017a187b5dfcf019191512196e210dc
Local-runs: none
Scope: every diff path — .changeset/28869-connect-redirect-uri-keep-client.md, ui/packages/app-shell/src/views/setup/ConnectorConnectionsPage.tsx, ui/packages/app-shell/src/views/setup/ConnectorConnectionsPage.test.tsx, ui/packages/i18n/src/locales/{ar,de,en,es,fr,ja,ko,pt,ru,zh}.ts — read against main: ui/packages/app-shell/src/views/setup/connectorConnections.ts (beginConnection, L196–202), packages/spec/src/contracts/automation-service.ts (redirectUri?: string, L1200), packages/services/service-automation/src/plugin.ts (derivedConnectorOAuthRedirectUri / connectorOAuthRedirectUri L3858–3880, installerClientOfBegin L4052–4090, the connection read's redirectUri), packages/services/service-automation/src/connector-connection-door.test.ts L443, and the card #28869 with its amendment, the PR 1 ACCEPT record, and os-dev report 6595178395.

Read-only; nothing built or run. Card #28869 PR 2 (the Console face of server #29463 / PR #29701, spec #29618).

① Derived judgments

  • The widening is eight connectorConnections i18n keys, present in all ten locale packs (redirectUriLabel, redirectUriCopy, redirectUriCopied, redirectUriHint, redirectUriMissing, clientKeepHint, clientKeep, clientReplace; each + line counted ten times in the diff). clientIntro and clientResend are reworded in all ten; no key is removed (every - locale line is one of those two). TranslationKeys = typeof en widens with them.
  • No other published surface moves. ClientDraft is a module-private interface of the page (replacing, redirectUri?, copied added); beginConnection on main already sends a body only when a client is given, so the keep-mode begin (connect(name, before, undefined)) is a no-body POST with no helper change. ConnectorConnectionsPage's props are untouched.
  • The server side the page relies on is landed, not assumed. installerClientOfBegin reuses keptInstallerClient when request.client is undefined (the no-body begin), replaces it when a client is named, and refuses with the registration step when none is kept; the read carries redirectUri from the one derivation and leaves it out when there is no base, and the begin then refuses 503. redirectUriMissing's "a connect is refused … Set OS_BASE_URL (or OS_AUTH_URL)" matches that refusal's own wording, and the env names are the server's.
  • The body's claims check out against the diff: URI only from state.redirectUri at dialog open; replacing: !state.client so an instance with nothing stored shows the fields; submitClient builds a client only when replacing; confirmRevoke re-reads the status and applies it when the read is ok; the secret is cleared before the begin is awaited and never prefilled.

② Semver level

  • @object-ui/app-shell patch and @object-ui/i18n patch, Clause-②: yes (bare = widening: the key set widens, the page's behaviour on a kept client changes from always-resend to keep-by-default, which the server now serves) and Metadata-protocol: none → patch is the level the protocol axis gives. No ADR-0087 marker is needed: no export or spec surface retires.

③ Boundary flags

  • Not Tier H. CI on 9b3449be was in progress when this was written (21 success, 11 skipped, 21 in progress, 0 failure). The seat's ACCEPT waits on green, as its note says.
  • Fixes #28869 closes the card on merge; PR 1 (feat(console): ask for the installer's OAuth client on Connect for a clientSource installer connector #29496) is merged, so this is the closing PR. Check Changeset re-runs if the body is edited.
  • Report 6595178395 writes "security_findings": "withheld"; read here as "none to report" since the body states the secret is write-only and the record above confirms it, but the seat should spell it out if the recogniser expects a literal.
  • Test-side: ConnectorConnectionsPage.test.tsx's hubspot disconnect test now queues a post-revoke GET, which follows from the re-read; the vi.stubGlobal('navigator', …) in the Copy test is scoped to that test.

④ Design judgment

  • The redirect URI is read from the status answer and never built from window.location.origin or parsed from a refusal; the missing case is said in words, with the server's own configuration names, and the page lets the begin be refused rather than guessing. That is the correct reading of ADR-0143 D5.
  • Keep-by-default on a reconnect is the right default: the Console can never read a secret back, so "resend" was a path that forced the administrator to re-type a secret the server already holds. Replace stays one click away and prefills only the id.
  • Re-reading the status after a revoke is the honest way to keep redirectUri on screen; it is one extra GET on an administrator action.
  • Narrowing face: nothing is taken away; the stored-client resend path is still reachable via Replace.

No must-fix. No should-fix.

Implemented-by: claude/project-thread-80b6qg
Reviewed-by: session_01QsR2jV5sBSzCYNEDgpYLJw

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m

席位核对 · ACCEPT · 2026-11-03T04:21Z

Head-sha: 9b3449be3017a187b5dfcf019191512196e210dc

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 3, 2026 04:22
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 3, 2026 04:22
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit 946a173 Nov 3, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-80b6qg branch November 3, 2026 04:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling

Projects

None yet

2 participants