mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-19 17:25:19 +03:00
fix(auth): a host may receive a code at its own name, and a refusal is not a redirect
Two changes to who may start a flow here, and where a refusal is delivered. A host reached at its own hostname sits on a different registrable domain from this issuer, deliberately: that is what stops a cookie set there from ever reaching this one. The default rule allows a redirect back to whatever hostname the request arrived on, so it refused exactly the case the separation created. Which is a real problem rather than a theoretical one, because a session cookie without a Domain attribute is host-only, so a browser arriving at one of those hostnames for the first time carries no cookie whether or not it is signed in, and sending it here to sign in again changes nothing. So a client id that is a single hostname under that zone, whose redirect_uri is https and that same hostname at one reserved path, is allowed. Making the client id the hostname is the load-bearing part: a token's audience is its client id, so the session that comes back is bound to the host it will live on and is not a credential anywhere else. Separately, and worth its own paragraph: a refused client's redirect_uri was still used to report the refusal. The check that approves that URI is the one that just failed, so /authorize was an open redirector to anywhere at all -- no sign-in required, on the hostname people are asked to type a password into. It is now a page here. Before: GET /authorize?client_id=web&redirect_uri=https://somewhere.example/callback -> 302 https://somewhere.example/callback?error=unauthorized_client
This commit is contained in:
@@ -1702,6 +1702,16 @@ export function issuer<
|
||||
if (err instanceof UnknownStateError) {
|
||||
return auth.forward(c, await error(err, c.req.raw));
|
||||
}
|
||||
// A refused client does not get to choose where the refusal is delivered.
|
||||
// Everything below reports an error by redirecting to the `redirect_uri`
|
||||
// the caller supplied, which is correct once that URI has been approved
|
||||
// and is an open redirector before it has: the check that approves it is
|
||||
// the one that just failed, so honouring it here would turn every
|
||||
// refusal into a redirect to anywhere at all — no sign-in required, on
|
||||
// the hostname people are told to trust with a password.
|
||||
if (err instanceof UnauthorizedClientError) {
|
||||
return c.text(err.description || err.error, 400);
|
||||
}
|
||||
const authorization = await getAuthorization(c);
|
||||
// A device grant has no redirect to carry the error back on, so it is
|
||||
// said here instead. Without this the reporting path throws on a URL
|
||||
|
||||
Reference in New Issue
Block a user