From 2a4ac75c77708a8d96273fc29f19a569165271c4 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Sat, 21 Mar 2026 09:42:31 +0100 Subject: [PATCH] fix: use HTML+JS redirect for native app OAuth callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- lib/mastodon/routes/oauth.js | 36 ++++++++++++++++++++++++++++++++++-- package.json | 2 +- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/lib/mastodon/routes/oauth.js b/lib/mastodon/routes/oauth.js index 1d9a4ff..9bffd79 100644 --- a/lib/mastodon/routes/oauth.js +++ b/lib/mastodon/routes/oauth.js @@ -310,7 +310,7 @@ router.post("/oauth/authorize", async (req, res, next) => { "error_description", "The resource owner denied the request", ); - return res.redirect(url.toString()); + return redirectToUri(res, redirect_uri, url.toString()); } return res.status(403).json({ error: "access_denied", @@ -362,7 +362,7 @@ router.post("/oauth/authorize", async (req, res, next) => { // Redirect with code const url = new URL(redirect_uri); url.searchParams.set("code", code); - res.redirect(url.toString()); + redirectToUri(res, redirect_uri, url.toString()); } catch (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(` + + + + + Redirecting… + + +

Redirecting to application…

+ + +`); +} + export default router; diff --git a/package.json b/package.json index 1387b0d..a913e4e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "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.", "keywords": [ "indiekit",