fix: use HTML+JS redirect for native app OAuth callbacks
Android Chrome Custom Tabs block 302 redirects to custom URI schemes (fedilab://, moshidon-android-auth://) for security. The server sends the redirect correctly but the WebView silently ignores it — "nothing happens" when the user taps Authorize. Fix: detect non-HTTP redirect URIs and render an HTML page with both a JavaScript window.location redirect and a meta refresh fallback. Client-side navigation to custom schemes is allowed by WebViews. HTTP(S) redirect URIs (Phanpy, Elk) still use standard 302.
This commit is contained in:
@@ -310,7 +310,7 @@ router.post("/oauth/authorize", async (req, res, next) => {
|
|||||||
"error_description",
|
"error_description",
|
||||||
"The resource owner denied the request",
|
"The resource owner denied the request",
|
||||||
);
|
);
|
||||||
return res.redirect(url.toString());
|
return redirectToUri(res, redirect_uri, url.toString());
|
||||||
}
|
}
|
||||||
return res.status(403).json({
|
return res.status(403).json({
|
||||||
error: "access_denied",
|
error: "access_denied",
|
||||||
@@ -362,7 +362,7 @@ router.post("/oauth/authorize", async (req, res, next) => {
|
|||||||
// Redirect with code
|
// Redirect with code
|
||||||
const url = new URL(redirect_uri);
|
const url = new URL(redirect_uri);
|
||||||
url.searchParams.set("code", code);
|
url.searchParams.set("code", code);
|
||||||
res.redirect(url.toString());
|
redirectToUri(res, redirect_uri, url.toString());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@@ -600,4 +600,36 @@ function extractClientCredentials(req) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to a URI, handling custom schemes for native apps.
|
||||||
|
*
|
||||||
|
* HTTP(S) redirect URIs use a standard 302 redirect (web clients).
|
||||||
|
* Custom scheme URIs (fedilab://, moshidon-android-auth://) use an
|
||||||
|
* HTML page with JavaScript + meta refresh. Android Chrome Custom Tabs
|
||||||
|
* block 302 redirects to non-HTTP schemes but allow client-side navigation.
|
||||||
|
*
|
||||||
|
* @param {object} res - Express response
|
||||||
|
* @param {string} originalUri - The registered redirect_uri (to detect scheme)
|
||||||
|
* @param {string} fullUrl - The complete redirect URL with query params
|
||||||
|
*/
|
||||||
|
function redirectToUri(res, originalUri, fullUrl) {
|
||||||
|
if (originalUri.startsWith("http://") || originalUri.startsWith("https://")) {
|
||||||
|
return res.redirect(fullUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Native app — HTML page with JS redirect + meta refresh fallback
|
||||||
|
res.type("html").send(`<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="refresh" content="0;url=${fullUrl}">
|
||||||
|
<title>Redirecting…</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Redirecting to application…</p>
|
||||||
|
<script>window.location.href = ${JSON.stringify(fullUrl)};</script>
|
||||||
|
</body>
|
||||||
|
</html>`);
|
||||||
|
}
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@rmdes/indiekit-endpoint-activitypub",
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
||||||
"version": "3.6.1",
|
"version": "3.6.2",
|
||||||
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
|
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"indiekit",
|
"indiekit",
|
||||||
|
|||||||
Reference in New Issue
Block a user