OAuth handle verification re-throws cancellation on a timed-out .well-known/atproto-did probe (and resolution hangs ~100s) for DNS-only handles #42
Labels
No labels
breaking-change
bug
documentation
duplicate
enhancement
good first issue
help wanted
performance
question
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/ATProto.NET#42
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
When a handle's domain serves no (or an unreachable)
/.well-known/atproto-did, OAuth login can hang ~100 s and then throw out ofCompleteAuthorizationAsync, even though the identity was already resolvable via DNS. Two related behaviors:1.
CompleteAuthorizationAsyncre-throws cancellations during handle verification.The verification catch is
catch (Exception ex) when (ex is not OperationCanceledException). So it swallows e.g.HttpRequestException(connection refused) and leavesIsHandleVerified = false— good — but aTaskCanceledException/OperationCanceledExceptionfrom a timed-out well-known probe is not caught and propagates out ofCompleteAuthorizationAsync. A slow/black-holing handle domain thus turns a "handle just couldn't be verified" case into a hard exception that aborts the whole login — even though the DID from the token exchange (the authoritativesub) is already in hand.2. Handle resolution waits on the hung HTTP probe even after DNS already succeeded.
StartAuthorizationAsyncfires thehttps://<handle-domain>/.well-known/atproto-didprobe alongside the DNS/DoH method. DoH resolves the DID in ~150 ms, but the call still waits on the well-known probe until the defaultHttpClient.Timeout(100 s) — so resolution takes ~100 s for a perfectly valid DNS-only handle whose domain doesn't serve the well-known.Repro
Use a handle whose domain has no reachable
/.well-known/atproto-did(e.g. the apex points at a parking IP that black-holes the TCP/TLS connection) but does publish a_atproto.<domain>TXT record.StartAuthorizationAsynctakes ~100 s. If aConnectTimeoutis set on theHttpClient,CompleteAuthorizationAsyncthen throws:Suggested fix
OperationCanceledException/TaskCanceledExceptionfrom the well-known probe the same as other failures (setIsHandleVerified = falseinstead of re-throwing). The caller's ownCancellationTokencancellation can still be distinguished and propagated.HttpClient.Timeout.Workaround
Map the handle domain to a fast-refusing address for the resolver process, so the probe fails with
HttpRequestException(swallowed) rather than a timeout (re-thrown).Environment
ATProtoNet 0.4.0, net10.0.
🤖 On it — follow along in the action run; the reply will link the full session transcript.
Pushed. Final summary:
Fixed point 1 on branch
claude/issue-42.What was wrong
CompleteAuthorizationAsyncdistinguished verification failures by exception type:catch (Exception ex) when (ex is not OperationCanceledException). A refused connection (HttpRequestException) leftIsHandleVerified = false, but a timed-out probe (TaskCanceledException— parked handle domain, or any host once aConnectTimeoutis set on theHttpClient) escaped and failed the whole login, even though the authoritative DID (the token response'ssub) was already in hand.The still-live path for that today is the DID-document fetch at the top of step 8 (
plc.directory), which is unbounded byHandleResolutionTimeout; the well-known probe itself is already OCE-safe inside the discovery layer after #52.Changes (
src/ATProtoNet/Auth/OAuth/OAuthClient.cs)catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)→ rethrow; everything else (including probe timeouts) → log and leave the handle unverified.VerifyDidToAuthServerConsistencyAsyncgot the same distinction — caller cancellation surfaces as cancellation instead of being wrapped in averification_failedOAuthException. A probe that merely timed out still fails closed there, since that check is a real security boundary.Point 2 (the ~100 s hang) was already fixed
StartAuthorizationAsyncshort-circuits on first authoritative success viaRaceAuthoritativeLookupsAsync, and every round is bounded byHandleResolutionTimeout(5 s default,OAuthOptions.HandleResolutionTimeout) — that landed for #52, covered byHandleResolutionTests/HandleResolutionTimeoutTests. This commit covers the callback half: a hung probe there now ends in an unverified handle rather than an exception. Note the callback's DID-document fetch still inheritsHttpClient.Timeout; if you'd like that bounded too, it's a small follow-up, but it would silently downgrade verification on slow-but-working networks, so I left that call to you.Verified
tests/ATProtoNet.Tests/Auth/OAuth/OAuthCallbackHandleVerificationTests.cs— 5 tests driving the real flow end-to-end (discovery → PAR → token exchange → callback) against a stub deployment: DID-doc timeout completes unverified; both handle authorities timing out completes unverified; silent well-known + answering DNS still verifies (the reported DNS-only case); handle resolving to another DID stays unverified; caller cancellation propagates.OAuthClient.cstomainmakesComplete_DidDocumentFetchTimesOut_CompletesUnverifiedInsteadOfThrowingfail with the propagatedTaskCanceledException.dotnet test tests/ATProtoNet.Tests/, .NET SDK 10.0.302 installed in the container).CHANGELOG.mdupdated under[Unreleased] → Fixed.⏱ 7m 0s · 37 turns · tokens: 82.9k in / 26.2k out (+2587.0k cache-read)
📜 Full session transcript
→ Opened PR: #56