Repository navigation
fix(console-auth): a dead invitation link reads "no longer valid", and a refused invitee is told the invited address - #27655
Merged
Conversation
…d a refused invitee is told the invited address The invitation-preview door answers one uniform 404 for an unknown, expired, accepted, rejected or cancelled id (ADR-0138 D2). The client read that 404 as "no preview", so every login and register page kept offering the accept-and-register flow for a dead link, and the refusal at the end told the link holder to ask for an invitation. - getInvitationPreview resolves null only for the door's own not-found body; a 404 without it (a server without the route) rejects. - useInvitationArrival reports `gone`; all four pages draw InvitationGoneNotice in place of the card, the prompt and the form. - A SELF_REGISTRATION_CLOSED refusal on an invitation register page re-reads the preview and names the masked invited address. Claude-Session: https://claude.ai/code/session_01Utp6CaTkEVPn2dBYY2piPZ Co-authored-by: Claude <noreply@anthropic.com>
…-read on both hosts; changeset Claude-Session: https://claude.ai/code/session_01Utp6CaTkEVPn2dBYY2piPZ Co-authored-by: Claude <noreply@anthropic.com>
…; the not-found code constant sits above it Claude-Session: https://claude.ai/code/session_01Utp6CaTkEVPn2dBYY2piPZ Co-authored-by: Claude <noreply@anthropic.com>
Contributor
Author
|
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC 席位核对ACCEPT · head
Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC
Requested by Bill · project thread
Fixes #27509
Clause-②: no (
getInvitationPreview's exported signature is unchanged; it now resolvesnullonly for the preview door's own not-found body, which is what its documented contract already said; the new hook, notice and optional argument are on@object-ui/app-shell, a private package)Metadata-protocol: none
维护者速读
ui/,不改服务端、不加错误码、文案不说「canceled」。getInvitationPreview只把带RESOURCE_NOT_FOUND正文的 404 读作「失效」,没有该路由的旧服务端回的空正文 404 现在按读取失败处理,页面保留原有通用文案,不会把有效链接说成失效。回滚即 revert 本 PR。ui/packages/auth/src/createAuthClient.ts的getInvitationPreview(404 判别那几行);ui/packages/app-shell/src/console/auth/invitationArrival.tsx的useInvitationArrival与InvitationGoneNotice;两个 RegisterPage 里传给RegisterForm的onError与signUpRefusalMessages第二参数。pnpm --dir ui exec vitest run packages/app-shell/src/console/auth/__tests__/invitationGone-27509.test.tsx apps/console/src/pages/auth/__tests__/invitationGone-27509.test.tsx,预期 10 个用例全绿。What changes
The signed-out preview door answers one 404 body (
RESOURCE_NOT_FOUND, "Invitation not found. It may have expired, been used, or been withdrawn.") for an unknown, expired, accepted, rejected or cancelled id (ADR-0138 D2). The client read every 404 as "no preview", so all four pages kept drawing the accept-and-register flow for a dead link, and the sign-up ended in the genericSELF_REGISTRATION_CLOSEDtext. Per the triage answer on the card, the fix lives wholly inui/: no server file, no new error code, no ADR change, and the copy never says "canceled".@object-ui/authgetInvitationPreview. Resolvesnullonly for a 404 whose body carries the door'serror.codeRESOURCE_NOT_FOUND. A server without the route answers 404 from better-auth's router with an EMPTY body (read from better-call 1.4.4's router, which returns a null body with status 404 for an unmatched path, and from plugin-auth's${basePath}/*mount, which hands better-auth's 404 back verbatim when nothing downstream answers). That 404 now rejects like any other failed read, so a page never tells a valid invitation's holder their link is dead. The exported signature and the type's JSDoc contract are unchanged; the JSDoc now names that case.invitationArrival.tsx(app-shell).useInvitationArrival(redirect)returns{ preview, gone, recheck };goneis true only after the door's not-found answer for the current id, never for a failed read.useInvitationPreviewis now itspreviewand behaves as before.InvitationGoneNoticeis the one card all four pages draw for a gone invitation: "This invitation link is no longer valid", "It may have expired, been used, or been withdrawn. Ask the person who invited you to send a new invitation.", and a "Go to sign in" link to the plain/login(noredirect)./loginand/registerand app-shell'sDefaultLoginPageandDefaultRegisterPagedraw the notice in place of the invitation header, the "Create an account and join" prompt, and the form (and, on the console login, the dev-admin row).RegisterFormanonErrorthat, forSELF_REGISTRATION_CLOSEDon an invitation redirect, re-reads the preview. The door's 404 turns the page into the notice; an answer keeps the form.signUpRefusalMessages(t, invitation?)takes the invitation and mapsSELF_REGISTRATION_CLOSEDto "This invitation was sent to {{email}}. Register with that address to accept it." with the masked address, or, while no preview is known, "This invitation can only be accepted with the address it was sent to. Register with that address." The same one map, not a second one.auth.login.invitationGoneTitle,auth.login.invitationGoneDescription,auth.login.invitationGoneSignIn,auth.register.errors.invitationSentTo,auth.register.errors.invitationAddressOnly.AcceptInvitationPageis untouched: its signed-out branch only bounces to/login?redirect=…, and its signed-in branch already reads the invitation with the session and shows its own not-found text.Changeset:
@object-ui/auth,@object-ui/app-shell,@object-ui/console,@object-ui/i18n, all patch.Tests
New, one per host (the console's pages and app-shell's default pages), real
AuthProviderover a realcreateAuthClient, onlyfetchstubbed:ui/apps/console/src/pages/auth/__tests__/invitationGone-27509.test.tsxui/packages/app-shell/src/console/auth/__tests__/invitationGone-27509.test.tsxEach pins: the door's 404 on login shows the notice with no sign-up prompt, no email field and a
/loginlink; the door's 404 on register shows the notice and no form; a live invitation keeps the prompt and the form (control); a refused sign-up on a live invitation re-reads the preview (two reads, one sign-up) and the banner reads the masked-address text, not the generic one; a refused sign-up whose re-read answers 404 turns the page into the notice.Updated: the two
invitationPreview-26473page files replace their "404 keeps the generic copy" case (it pinned this defect) with "a server without the route (empty-body 404) keeps the generic copy and the form"; the door's 404 moved to the 27509 files.ui/packages/auth/src/__tests__/invitationPreview-26473.test.tsadds: an empty-body 404 rejects withstatus: 404; a 404 with another code rejects with that code.All runs below were re-taken on the merged head
4a728231(origin/main merged once before this PR), except the ablation, which ran before the merge on the same implementation.Targeted runs
Test Files 5 passed (5),Tests 33 passed (33).pnpm --dir ui exec vitest run apps/console/src/pages/auth/ packages/app-shell/src/console/auth/ packages/auth/ packages/i18n/ packages/app-shell/src/__tests__/product-neutral-renderer.ratchet.test.ts:Test Files 163 passed (163),Tests 1958 passed | 13 skipped (1971)(includes the i18n pack parity tests; 1957 of 1970 before the merge).type-checkfor@object-ui/auth,@object-ui/i18n,@object-ui/app-shell,@object-ui/console: eachVERDICT command-exit 0.Ablation (scripts/ablation-replace.mjs, anchor 1 to 0, each restored to the HEAD blob with git diff HEAD empty)
ui vitest resolves
@object-ui/*to source, so no rebuild sits between mutation and run.status === 404: 6 of 33 red (both client 404 cases, and the four "server without the route" page cases).goneforced false: 6 of 33 red (the door's-404 cases on both hosts' login and register, and both "gone meanwhile" cases).recheckmade a no-op: 4 of 33 red (both re-read cases on both hosts).signUpRefusalMessagesbypassed: 2 of 10 red (the masked-address banner on both hosts).Derived gates — 34 of 34 exit 0 on `4a728231`
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 34 commands (identical list before and after the merge); each ran from the worktree with its exit code captured before any pipe, and--ranreconciled: "34 derived famil(ies) accounted for — 34 run, 0 NOT-MEASURED". Among themnode scripts/check-sdui-manifest.mjs(and--self-test),pnpm --dir ui exec vitest run scripts/,pnpm check:nul-bytes,pnpm check:published-files,pnpm check:type-check-coverage,pnpm check:type-check-debt,node scripts/check-empty-changeset.mjs --base origin/mainandnode scripts/check-adr-0087-registration.mjs --base origin/main. Alsopnpm --dir ui run check:new-line-citations: 0 new citations.The type-check lanes, the artifact-roster families and the whole-tree families are CI's; the PR's check-runs are the reading for them.
Acceptance notes
SELF_REGISTRATION_CLOSEDonly. Under anemail_domainposture a mismatched address can be refused asEMAIL_DOMAIN_NOT_ALLOWED, which keeps its own text.Generated by Claude Code