Repository navigation
fix(console): a mobile hand-off sign-up's verification mail lands on "go back to the app", and the refusal time survives /verify-email-prompt - #30243
Draft
objectstack-fleet[bot] wants to merge 1 commit into
Conversation
…ack to the app", and the refusal time survives /verify-email-prompt The sign-up and resend mails sent a mobile sign-in hand-off redirect through withConsoleBaseRootRelative, so the link landed on /_console/api/v1/auth/one-time-token/handoff (page not found). A hand-off redirect now returns to /verify-email?handoff=verified, which says the address is confirmed and to go back to the app; every other redirect keeps its callbackURL byte for byte. handoff_refused_at is now carried on the three hops through /verify-email-prompt: /login's EMAIL_NOT_VERIFIED branch, /register's verification-required branch, and "Back to login". Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzgzSz4DH8R8v1yq6bm
Contributor
Author
|
domain:ui#4 · os-support-ai · session_01WGufzinedUGSXteN45poyo 维护者速读
席位意见:ACCEPT。实现与裁决 A 一致,改动面小,安全判据与旧代码逐字等价。 席位核对ACCEPT → PR #30243 @
|
This branch has not been deployed
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.
Requested by Yibo Wang · project thread
Fixes #30157
维护者速读
/_console/api/v1/auth/one-time-token/handoff,而是落到 Console 的「邮箱已确认,请回到 App 登录」页(/verify-email?handoff=verified),在打开邮件的浏览器里不发登录令牌;同时handoff_refused_at在经过/verify-email-prompt的三跳上不再丢失。ui/apps/console/src/pages/auth/handoffRedirect.ts(新的verificationCallbackURL与规则 6)、VerifyEmailPage.tsx(无 token 的handoff=verified落地分支)、RegisterPage.tsx/VerifyEmailPromptPage.tsx的两处 callbackURL。pnpm --dir ui exec vitest run apps/console/src/pages/auth/__tests__/handoffVerificationMail-30157.test.tsx,预期 1 个文件、11 条全部通过。Clause-②: no
Metadata-protocol: none
What changes
ui/apps/console/src/pages/auth/handoffRedirect.ts: a new rule 6 in the header ("not from the verification mail") andverificationCallbackURL(redirect): a hand-offredirect(the existinghandoffNavigationTargetmatcher, unchanged) returns to/verify-email?handoff=verifiedinside the Console mount; any other safe in-appredirectis resolved withwithConsoleBaseRootRelativeexactly as before; an unsafe or missing one gives nocallbackURL, as before.withConsoleBaseRootRelativeitself is untouched.RegisterPage.tsx(the sign-up mail) andVerifyEmailPromptPage.tsx(the "Resend" mail) build theircallbackURLthrough it.VerifyEmailPage.tsx: with notokenand?handoff=verified, the server has already verified the address (better-auth 302s to the callback afterGET /verify-email), so the page spends no token of its own and shows "Email verified" with "Go back to the app you were signing in to, and sign in there", and no Console sign-in link. better-auth appendserror=CODEto the callback for a bad or expired token; the page then shows its existing failure state. Without the mark, a token-less visit still reports the missing token.handoffMarkQuery(redirect)is appended onLoginPage'sEMAIL_NOT_VERIFIEDnavigation,RegisterPage'sonVerificationRequirednavigation andVerifyEmailPromptPage.handleBackToLogin. It is empty unless the redirect names the hand-off and the page carries a mark, so every other link is unchanged.@object-ui/i18n:auth.verifyEmail.handoffSuccessDescriptionin all ten locales.@object-ui/consoleand@object-ui/i18n, patch.No server file moves: no auth route, no token issuance, no change to the hand-off endpoint.
The landing, and what it does for a person who opens the mail elsewhere
Three landings were defensible, with different results for the person; the project's user chose this one on a decision card (2026-11-05 03:06Z, option 「回 App 提示页」, "back-to-the-app page"):
/verify-email?handoff=verified: "Email verified. Go back to the app you were signing in to, and sign in there." Whatever browser or device opens the mail (a desktop browser, Safari while the app waits in anASWebAuthenticationSession, a browser signed in as someone else), the person sees that page and no sign-in token is handed out there. Back in the app's waiting window, which still shows/verify-email-prompt, "Back to login" (now carrying the route's mark) and one sign-in finish the hand-off. This keeps the route's own rule that every hand-off follows a full sign-in in the system auth session./logincarrying the hand-off, so the mail browser signs in and is forwarded to the route. Smoother on Android, where Custom Tabs share Chrome's cookies and the scheme opens the app by intent; on iOS it relies on the app taking a cold scheme open, and on another device it ends at a scheme nothing opens. (This is what a signed-out mail browser got before this PR, by way of the SPA's sign-in guard.)/loginwith no hand-off, which signs the person into the web Console instead of the app.Tests
New pins: handoffVerificationMail-30157.test.tsx (11 tests)
The real
AuthProviderover a mock client, the Console mounted as the shipped embeddable build (base href /_console/,BASE_URL./);callbackURLis read off the client call that goes on the wire.callbackURLis/_console/verify-email?handoff=verified; the resent mail's too. CONTROL: an invitation redirect keeps/_console/accept-invitation/inv_1on both mails.&handoff_refused_at=1793620800000on the page and a hand-off redirect:/loginEMAIL_NOT_VERIFIED,/registerverification required, "Back to login" each carry it. CONTROL: beside an invitation redirect the three navigations are byte for byte today's; beside a hand-off redirect with no mark on the page they are today's too.?handoff=verifiedshows the confirmed state, no "Go to sign in" link, and no/verify-emailfetch;&error=INVALID_TOKENshows the failure state; CONTROL: no token and no mark still reports the missing token.Run:
pnpm --dir ui exec vitest run --maxWorkers=2 apps/console/src/pages/auth/__tests__/handoffVerificationMail-30157.test.tsx→ Test Files 1 passed, Tests 11 passed.Ablation
The fix committed first (
9d2f99aa), then the five Console source files checked out fromorigin/maininside a script with a restore trap; landing proved bygrep -c(verificationCallbackURL0 hits inhandoffRedirect.ts,handoffMarkQuery0 hits inVerifyEmailPromptPage.tsx). Result: Tests 6 failed, 5 passed: the two mail pins, the three hop pins and the landing pin went red; the controls stayed green (as they should: they assert today's behaviour). Restore bygit checkout HEAD --;git diff HEADempty and each file's hash equals its HEAD blob. No build in the loop: the Console tests import these files from source.Verification
Browser walk on a dev boot (shots in the project's ui-orders/30157-shots)
examples/app-crm,pnpm exec objectstack dev --fresh,OS_TRUSTED_APP_SCHEMES=ai.objectos.mobile,OS_AUTH_REQUIRE_EMAIL_VERIFICATION=true, audience postureopen(settingsauth), log mail provider (the mail read fromsys_identity_email). Headless Chromium: window A opens the hand-off route (302 to/_console/login?redirect=…&handoff_refused_at=…), follows "Sign up", registers; window B (a separate browser context) opens the mail's link; A then presses "Back to login" and signs in.Before (Console built from
origin/maindfafae813):callbackURL=/_console/api/v1/auth/one-time-token/handoff?redirect_uri=….302from/verify-email, then the Console's "Page not found" (before-signedin-3-mail-lands.png).os servecannot turn onautoSignInAfterVerification): the SPA's sign-in guard sends it to/_console/login?redirect=/api/v1/auth/one-time-token/handoff…, so the mail browser is offered the hand-off by accident (before-3-mail-lands.png)./verify-email-promptand "Back to login" carry nohandoff_refused_at(before-walk.json, steps 3 and 6).After (this branch's Console):
callbackURL=/_console/verify-email?handoff=verified.after-3-mail-lands.png,after-signedin-3-mail-lands.png).handoff_refused_at(after-walk.json, steps 3 and 6); signing in on A forwards to the route, which answers302 ai.objectos.mobile://auth/callback?token=…(step 7).Gates (local, head 9d2f99a, one locked run, 40 commands, every one exit 0)
pnpm --dir ui exec vitest run apps/console/src/pages/auth/(Test Files 28 passed, Tests 228 passed);pnpm --dir ui exec vitest run packages/i18n/(95 files, 1443 passed, 13 skipped);pnpm --filter @object-ui/console run type-check;pnpm --filter @object-ui/i18n run type-check;eslinton the six changed Console files.node scripts/check-sdui-manifest.mjsand its--self-test: exit 0.node scripts/pm/dispatch-gates.mjs --commandsderives for this diff: exit 0 each (list in the project's ui-orders/30157-gates-ran.list).--ranreconciliation: "35 derived famil(ies) accounted for, 35 run, 0 NOT-MEASURED".Acceptance notes
autoSignInAfterVerificationis not reachable on a stockos serve; on a host that turns it on, the mail browser gets a session for the new account and still lands on the same page, which offers no Console link.