Skip to content

fix(relay): keep sign-in polling idempotent once the authority redeemed a transaction - #3248

Closed
braindeadz wants to merge 1 commit into
GCWing:mainfrom
braindeadz:fix/relay-idempotent-signin-poll
Closed

braindeadz wants to merge 1 commit into
GCWing:mainfrom
braindeadz:fix/relay-idempotent-signin-poll

Conversation

@braindeadz

Copy link
Copy Markdown

Summary

Makes the relay's sign-in poll idempotent: once the identity authority has answered a transaction, the relay replays that answer instead of asking again, because the authority's grant is single-use.

Fixes #3246

Type and Areas

Type: bug fix (regression fix)

Areas: server/relay (src/crates/services/relay-service/src/identity.rs)

Motivation / Impact

Measured on production (https://remote.openbitfun.com/v/1.0.2), same transaction, two consecutive polls with the same transactionId/transactionSecret:

poll #1 after the user finished in the browser -> 200 {"status":"authorized","tokens":{accessToken, refreshToken, ...}}
poll #2 (same transaction, same secret)        -> 200 {"status":"consumed"}   # no tokens, ever again

Both native clients poll on an interval (AuthorizationPoll.awaitAccessToken, every pollIntervalSeconds, plus one poll immediately after the browser handoff), and only understand authorized / expired / denied. So a poll whose response is lost, a request repeated after a timeout, or a process resumed after the handoff redeems the grant without the client ever seeing the tokens; from then on the sign-in can never complete and the app ends on a generic failure. That matches #3246 exactly: the browser reports "login complete", the phone reports "the relay returned an invalid account response".

Impact: a repeated poll now returns the payload the first poll received, for the length of the transaction window, so an already-installed APK completes a sign-in it would previously lose. pending answers are still forwarded upstream, and a poll presenting a different secret is refused rather than handed the stored payload.

Verification

Not compiled locally: I have no Rust toolchain on this machine, so cargo test -p openbitfun-relay-service identity was not run and this change relies on your CI. Please treat the patch as needing a compile pass before merge.

What was verified:

  • The production behaviour above was measured repeatedly against the live relay (email-code sign-in created end to end: auth/desktop/start -> auth/email/send -> auth/email/verify -> two poll calls -> login), which is what the patch addresses.
  • The added test poll_replays_a_completed_transaction_instead_of_redeeming_it_twice is written to fail on the pre-patch code: it asserts the authority is called once for a repeated poll, that both payloads are identical, and that a mismatching secret sees consumed instead of the issued token.
  • Request shapes used for the measurement are the ones the Android build sends (device_kind: "mobile", clientVersion, clientProtocol), and login answers 200 with them.

Reviewer Notes

  • The cache is process-local and bounded by POLL_REPLAY_SECS (900 s), trimmed on every access. A relay behind multiple instances would still need the authority itself to be idempotent; this patch removes the failure for a single relay (as deployed today).
  • Keying: the transaction id is the map key, but the stored secret must match before a payload is replayed, so possession of a transaction id alone never yields tokens.
  • std::sync::Mutex is deliberate: every guard is dropped before any await, so no lock is held across a suspension point.
  • If you would rather fix this in the identity authority (making the grant idempotent for a repeated poll) and keep the relay stateless, say so and I will withdraw this and open an issue against the authority instead.

Checklist

  • This PR is focused and does not include secrets, temporary prompts, generated scratch files, or unrelated artifacts.
  • Relevant verification is recorded above, or skipped checks are explained. (Skipped: local Rust compile/test, no toolchain available.)
  • User-facing strings, docs, and locales are updated where applicable. (No user-facing string changes: the relay keeps returning the authority's payload.)

…ed a transaction

The identity authority redeems a sign-in transaction exactly once: the first
poll that finds it completed returns the tokens, and every later poll for the
same transaction answers `consumed` without them. Both native clients poll on
an interval, so a response that is lost, a request repeated after a timeout, or
a process that resumes after the browser handoff redeems the grant without the
client ever seeing the tokens. That sign-in can then never complete: the app
polls until the window closes and reports a generic failure, which is what
GCWing#3246 reports (browser says "login complete", phone says the relay returned an
invalid account response).

Replay the terminal answer this relay has already received, keyed by
transaction id and validated against the same transaction secret, so a repeated
poll sees what the first poll saw. Pending answers are still asked upstream, and
entries expire with the transaction window. A poll presenting a different
secret never receives the stored payload.

Fixes GCWing#3246
@braindeadz

Copy link
Copy Markdown
Author

CI note: the two workflow runs on this branch are sitting in action_required, i.e. they are waiting for a maintainer to approve the run for a first-time contributor. Approving them is what gives this change its compile and test pass: I have no Rust toolchain on my side, so the patch is otherwise unbuilt.

If CI reports a compile error in the added test helper, I will fix it promptly; the production-facing part of the change is confined to poll_auth plus the replay helpers above it.

@wgqqqqq

wgqqqqq commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator

Superseded by #3253. The replacement keeps the idempotent polling fix, scopes replay by transaction_id and transaction_secret, adds regression coverage, and includes the Android/HarmonyOS secure-store recovery fixes.

@wgqqqqq wgqqqqq closed this Sep 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: 安卓客户端无法登录,提示:中继返回了无效的账号响应

2 participants