Tighten redirect_uri shape rules (default port, custom scheme)
Two corrections to `validate_redirect_uris` to match the atproto
OAuth profile precisely
(<https://atproto.com/specs/oauth#authorization-request-fields>):
#12 Custom-scheme native redirects. Spec: *"The URI scheme must be
followed by a single colon (`:`) then a single forward slash (`/`)
and then a URI path component."* Previously only the scheme was
checked (reverse-domain match), so
`com.example.app://host/callback` — which introduces an authority
component — was silently accepted. Now rejected via a new
`custom_scheme_has_single_slash` helper that inspects the raw URI
string (the `url` crate is too permissive on non-special schemes to
rely on its parsed fields here).
#13 Default-port suppression. Spec: *"The URL may include a port
number, but not if it is the default port number."* A redirect URI
declared as `https://example.com:443/cb` was treated as equal to
`https://example.com/cb` because `Url::origin()` normalises default
ports. Now rejected via a new `https_has_explicit_default_port`
helper that looks at the raw string before url-crate normalisation.
Applies to both web and native HTTPS redirects.
New fixtures and integration tests cover each rejection path. Unit
tests pin the two helpers against IPv6 literals, user-info authority
prefixes, and several edge shapes. All 333 tests pass; all 16
real-world atproto OAuth clients still pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>