Changeset bf72108 in Klonkt


Ignore:
Timestamp:
07/19/2026 04:04:25 AM (7 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
4407c67
Parents:
dd568e7
git-author:
Robin <roboburr@…> (07/19/2026 04:04:05 AM)
git-committer:
Robin <roboburr@…> (07/19/2026 04:04:25 AM)
Message:

Fix: OAuth consent hands off to native apps via an interstitial

After Allow/Deny the server 302-redirected to the client's redirect_uri. For a
native custom scheme (com.klonkt.shaer:/oauth) that 302 is silently dropped by
mobile browsers (no user gesture to launch an external app), so Allow/Deny
appeared to do nothing. finishRedirect() now keeps the 302 for http/https
clients but, for a non-http redirect_uri, serves a tiny interstitial that
meta/JS auto-forwards AND shows an 'Open the app' tap link — a tap is the user
gesture Android needs, and iOS's ASWebAuthenticationSession intercepts either
navigation. Used for both allow (code) and deny/error. Verified: native →
interstitial HTML with the code + tap link; https → still 302; deny → error in
the interstitial. 80 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@…>

Files:
4 edited

Legend:

Unmodified
Added
Removed
  • CHANGELOG.de.md

    rdd568e7 rbf72108  
    2828
    2929### Behoben
     30- **OAuth-Zustimmung übergibt jetzt zuverlässig an native Apps.** Nach
     31  Allow/Deny war die Weiterleitung an ein natives Custom-Scheme (z. B.
     32  `com.klonkt.shaer:/oauth`) ein einfacher 302, den mobile Browser stillschweigend
     33  verwerfen. Der Zustimmungsschritt liefert nun eine kleine Zwischenseite für
     34  Nicht-http-Redirect-URIs, die automatisch weiterleitet und einen "App öffnen"-
     35  Tipp-Link bietet (ein Tipp startet die App auf Android zuverlässig; iOS'
     36  Web-Auth-Session fängt sie ohnehin ab). Web-Clients (http/https) bekommen
     37  weiterhin einen 302.
    3038- **Besucher können auf die eigenen Kommentare des Seiteninhabers antworten.**
    3139  Der Knopf "über das Fediverse antworten" erschien nur bei Kommentaren anderer;
  • CHANGELOG.md

    rdd568e7 rbf72108  
    2626
    2727### Fixed
     28- **OAuth consent now hands off reliably to native apps.** After Allow/Deny, a
     29  redirect to a native custom scheme (e.g. `com.klonkt.shaer:/oauth`) was a plain
     30  302, which mobile browsers silently drop. The consent step now serves a tiny
     31  interstitial for non-http redirect URIs that auto-forwards and offers an "Open
     32  the app" tap link (a tap reliably launches the app on Android; iOS's web-auth
     33  session intercepts either way). Web (http/https) clients still get a 302.
    2834- **Visitors can reply to the site owner's own comments.** The "reply via the
    2935  fediverse" button only appeared on comments from others; the site's own
  • CHANGELOG.nl.md

    rdd568e7 rbf72108  
    2727
    2828### Opgelost
     29- **OAuth-toestemming geeft nu betrouwbaar over aan native apps.** Na Allow/Deny
     30  was de redirect naar een native custom-scheme (bijv. `com.klonkt.shaer:/oauth`)
     31  een gewone 302, en die negeren mobiele browsers stilzwijgend. De toestemmings-
     32  stap serveert nu een klein tussenscherm voor niet-http redirect-URI's dat
     33  automatisch doorstuurt én een "Open de app"-tikknop biedt (een tik opent de app
     34  betrouwbaar op Android; iOS' web-auth-sessie vangt 'm sowieso op). Web-clients
     35  (http/https) krijgen nog steeds een 302.
    2936- **Bezoekers kunnen reageren op de eigen reacties van de site-eigenaar.** De
    3037  knop "reageer via de fediverse" verscheen alleen bij reacties van anderen; bij
  • src/routes/oauth.js

    rdd568e7 rbf72108  
    4545}
    4646
     47// Hand control back to the client at redirect_uri + params. For a web client
     48// (http/https) a plain 302 is right. For a NATIVE custom scheme
     49// (com.klonkt.shaer:/oauth) a 302 is unreliable: mobile browsers routinely drop
     50// a server redirect to a custom scheme (no user gesture). So we serve a tiny
     51// interstitial that both auto-forwards AND offers a tap link — a tap is a user
     52// gesture that launches the app on Android, and iOS's ASWebAuthenticationSession
     53// intercepts either navigation. Same page for allow and deny (neutral copy).
     54function finishRedirect(res, redirectUri, params) {
     55  const target = redirectWith(redirectUri, params);
     56  if (/^https?:\/\//i.test(redirectUri)) return res.redirect(target);
     57  const attr = target.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
     58  return res.type('html').send(`<!doctype html>
     59<html lang="en"><head><meta charset="utf-8">
     60<meta name="viewport" content="width=device-width,initial-scale=1">
     61<meta http-equiv="refresh" content="0;url=${attr}">
     62<title>Return to the app</title>
     63<style>body{font-family:system-ui,-apple-system,sans-serif;background:#111;color:#eee;margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;text-align:center}
     64.box{padding:1.5rem}p{color:#aaa;line-height:1.5}a.btn{display:inline-block;margin-top:1.2rem;padding:.85rem 1.7rem;border-radius:12px;background:#5A32E6;color:#fff;text-decoration:none;font-weight:700}</style>
     65</head><body><div class="box">
     66<p>Almost done. If the app doesn't open by itself:</p>
     67<a class="btn" href="${attr}">Open the app</a>
     68</div>
     69<script>location.replace(${JSON.stringify(target)});</script>
     70</body></html>`);
     71}
     72
    4773// Bounce back to the client with an OAuth error (RFC 6749 §4.1.2.1) when we have
    4874// a validated redirect_uri; otherwise render a plain error (open-redirect guard).
    4975function authError(res, redirectUri, state, error, desc) {
    50   if (redirectUri) return res.redirect(redirectWith(redirectUri, { error, error_description: desc, state }));
     76  if (redirectUri) return finishRedirect(res, redirectUri, { error, error_description: desc, state });
    5177  return res.status(400).json({ error, error_description: desc });
    5278}
     
    111137  });
    112138  if (out.error) return authError(res, redirect_uri, state, out.error, out.error_description);
    113   return res.redirect(redirectWith(redirect_uri, { code: out.code, state }));
     139  return finishRedirect(res, redirect_uri, { code: out.code, state });
    114140});
    115141
Note: See TracChangeset for help on using the changeset viewer.