Skip to main content

push_for_unresolved_hint

Function push_for_unresolved_hint 

Source
fn push_for_unresolved_hint(
    store: &dyn ParStore,
    decisions: &dyn DecisionStore,
) -> Result<(String, Duration), ParError>
Expand description

What login_hint naming nobody gets instead of push_authorization_request’s ordinary decision record: the identical success this endpoint hands back for a real account, and nothing an unauthenticated caller — or this store — can act on.

No account state, ever — that is the whole fix, and the whole reason this stores nothing at all. An earlier version of this function put a placeholder DecisionRecord through DecisionStore::put, keyed on the raw login_hint string, so a nonexistent hint would cost the same pending_per_account slot a real account’s push would. That reopened the hole a different way: resolve folds every spelling of a real account — a handle, its DID, one with a redundant :port — into the one DID that names it, so a real account’s pending bucket is one bucket however it is addressed, while every distinct spelling of a nonexistent one is its own bucket forever. Push eight variously-spelled requests for one candidate and a ninth: a real account’s ninth answers differently from its first eight, and a made-up one’s ninth never does — the aliasing itself is the oracle, no matter how the two buckets’ refusals are worded. Worse, since nothing bounds how many distinct invented strings exist, that placeholder was also a new denial-of-service surface past the account-existence one: a handful of addresses at the per-address rate limit could keep pending_total permanently full with entries no real account ever asked for, since before this a stranger could not occupy a slot without naming one.

The fix an account-keyed bound cannot have is not to bound unresolved hints at all — there is no account here for a bound to be of. A fresh request_uri is minted through ParStore::mint and handed back with the same expires_in a real push gets, and nothing is stored under it anywhere: not in ParStore, so GET /oauth/authorize answers crate::oauth::authorize::AuthorizeError::UnknownRequestUri for it, as it does for a real one this server has swept; not in DecisionStore, so bot.did.getAuthorization answers not-found for the same reason it always would — no credential can authenticate as a string that resolved to no account.

That leaves one thing this must still answer identically to a real push: DecisionStore::check_room, the server’s own bound, which is a fact about this deployment’s load and not about login_hint at all — true for every caller alike, so answering it truthfully leaks nothing. Skipping this check would make a full store itself an oracle (real accounts start answering 403, unresolved hints never would), so the close reads it — without writing anything, whichever way it answers.