mirror of
https://github.com/svemagie/indiekit-endpoint-activitypub.git
synced 2026-04-02 15:44:58 +02:00
fix(oauth): echo state parameter back in authorization redirect
OAuth 2.0 requires the server to echo the state parameter in the callback redirect. Mastodon clients (e.g. murmel.social) send a state value and fail with 'missing parameters' if it is absent. Thread state through: GET query → session store → hidden form field → POST body → callback redirect (approve and deny paths).
This commit is contained in:
@@ -206,6 +206,7 @@ router.get("/oauth/authorize", async (req, res, next) => {
|
||||
code_challenge,
|
||||
code_challenge_method,
|
||||
force_login,
|
||||
state,
|
||||
} = req.query;
|
||||
|
||||
// Restore OAuth params from session after login redirect.
|
||||
@@ -221,6 +222,7 @@ router.get("/oauth/authorize", async (req, res, next) => {
|
||||
scope = p.scope;
|
||||
code_challenge = p.code_challenge;
|
||||
code_challenge_method = p.code_challenge_method;
|
||||
state = p.state;
|
||||
}
|
||||
|
||||
if (response_type !== "code") {
|
||||
@@ -262,7 +264,7 @@ router.get("/oauth/authorize", async (req, res, next) => {
|
||||
// login redirect chain due to a re-encoding bug in indieauth.js.
|
||||
req.session.pendingOAuth = {
|
||||
client_id, redirect_uri, response_type, scope,
|
||||
code_challenge, code_challenge_method,
|
||||
code_challenge, code_challenge_method, state,
|
||||
};
|
||||
// Redirect to Indiekit's login page with a simple return path.
|
||||
return res.redirect("/session/login?redirect=/oauth/authorize");
|
||||
@@ -300,6 +302,7 @@ router.get("/oauth/authorize", async (req, res, next) => {
|
||||
<input type="hidden" name="scope" value="${escapeHtml(requestedScopes.join(" "))}">
|
||||
<input type="hidden" name="code_challenge" value="${escapeHtml(code_challenge || "")}">
|
||||
<input type="hidden" name="code_challenge_method" value="${escapeHtml(code_challenge_method || "")}">
|
||||
<input type="hidden" name="state" value="${escapeHtml(state || "")}">
|
||||
<input type="hidden" name="response_type" value="code">
|
||||
<div class="actions">
|
||||
<button type="submit" name="decision" value="approve" class="approve">Authorize</button>
|
||||
@@ -323,6 +326,7 @@ router.post("/oauth/authorize", async (req, res, next) => {
|
||||
scope,
|
||||
code_challenge,
|
||||
code_challenge_method,
|
||||
state,
|
||||
decision,
|
||||
} = req.body;
|
||||
|
||||
@@ -365,6 +369,7 @@ router.post("/oauth/authorize", async (req, res, next) => {
|
||||
"error_description",
|
||||
"The resource owner denied the request",
|
||||
);
|
||||
if (state) url.searchParams.set("state", state);
|
||||
return redirectToUri(res, redirect_uri, url.toString());
|
||||
}
|
||||
return res.status(403).json({
|
||||
@@ -413,9 +418,10 @@ router.post("/oauth/authorize", async (req, res, next) => {
|
||||
</html>`);
|
||||
}
|
||||
|
||||
// Redirect with code
|
||||
// Redirect with code (and state, which must be echoed back per OAuth 2.0 spec)
|
||||
const url = new URL(redirect_uri);
|
||||
url.searchParams.set("code", code);
|
||||
if (state) url.searchParams.set("state", state);
|
||||
redirectToUri(res, redirect_uri, url.toString());
|
||||
} catch (error) {
|
||||
next(error);
|
||||
|
||||
Reference in New Issue
Block a user