feat(base): SimpleLogin's sign-in comes back by the app's scheme or the loopback (ADR 0020, D9 corrected)

simpleLogin.begin takes the way back (SimpleLoginReturn: loopback,
scheme) rather than the platform, so macOS can come back through its
scheme as iOS and Android do; the markers become learn-loopback,
learn-scheme, todo-loopback and todo-scheme. Not breaking: the
endpoint has never been released.

D9 corrected by the developer after research: Android, iOS and macOS
come back through the app's scheme, Linux and Windows through the
loopback. Verified links protected nothing here: a code caught on its
way is worthless without the attempt's ID, and a hostile app beginning
an attempt of its own can ask for a way back that other platforms need
open. The ADR notes the residual risk and the check of prompt=consent
still to do. assetlinks.json stays served.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-23 23:35:46 +02:00
commit 7ea9f16904
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -6,16 +6,16 @@
`/simplelogin-callback` on the register's web server (halkyon_base's
`addSimpleLoginCallbackRoute`, Caddy forwarding the path), ahead of the sign-in, so
development signs in through learn.halkyon.eu's page, which its SimpleLogin app
registers too. The state is `<app>-<platform>.<rest>`: the marker one of the module's
fixed table (Learn and Todo, the desktop's loopback on ports 43823 and 43824, or the
app's scheme), the rest 1 to 512 characters of base64url. A 303 carries the code or
registers too. The state is `<app>-<way back>.<rest>`: the marker one of the module's
fixed table (Learn and Todo, the loopback on ports 43823 and 43824, or the app's
scheme; named `<app>-<platform>` until D9's correction), the rest 1 to 512 characters
of base64url. A 303 carries the code or
SimpleLogin's error (`deny`) with the state and nothing else; whatever is malformed,
repeated or not a GET is refused with a page that repeats nothing. No caching, no
referrer, nothing logged. Android's `assetlinks.json` for Halkyon Learn signed with
the release key (D9) is answered by `deploy/Caddyfile` on learn.halkyon.eu alone,
held to `tool/release/android-certificate.sha256` by a test. The app's intent filter
comes with the sign-in; iOS's associated domains wait for an Apple team ID, so iPhones
come back through the forwarding page and the scheme until then.
the release key is answered by `deploy/Caddyfile` on learn.halkyon.eu alone, held to
`tool/release/android-certificate.sha256` by a test: built for D9's verified links,
which its correction dropped, and kept, as it costs nothing.
- **Built (23 Oct 2026), step 2:** the sign-in on the server's side (1 to 5), on
halkyon_oidc (ADR 0023) with its SimpleLogin preset: `simpleLogin.begin` and
`simpleLogin.complete` in halkyon_base, a member asking the register for both; the
@ -121,9 +121,20 @@ against app.simplelogin.io on 23 Oct:
GitHub ends, 22 Jan 2027, which was recommended): from then on every session of an
account without SimpleLogin ends, GitHub and email codes sign nobody in there, and the
instance offers SimpleLogin alone.
- **D9 Phones come back through verified links** (recommended): Android's App Links and
iOS's https callbacks with associated domains, for learn.halkyon.eu; the forwarding
page with the app's scheme for other instances, the loopback on desktops.
- **D9 Apps come back through their scheme or the loopback** (corrected 23 Oct, night,
by the developer, after research; decided first as verified links for phones on
learn.halkyon.eu, Android's App Links and iOS's https callbacks): Android, iOS and
macOS come back through the app's scheme, which the system's browser sheet catches
(Chrome's Auth Tab, since Chrome 132, even after the forwarding page's redirect;
`ASWebAuthenticationSession`), Linux and Windows through the loopback.
`simpleLogin.begin` takes the way back (`SimpleLoginReturn`: `loopback`, `scheme`),
not the platform. Verified links protected nothing here: a code caught on its way is
worthless without the attempt's ID (4), and a hostile app beginning an attempt of its
own can ask for the scheme or the loopback, which other platforms need open (an
Android app can listen on `127.0.0.1` too). They cost: debug builds and other
instances need the scheme anyway, Firefox has no Auth Tab, Samsung Internet stays on
the page, and flutter_web_auth_2 (v5.1.0) has open bugs with https callbacks (#183,
#215). The scheme is what AppAuth calls the most widely supported.
- **D10 Accounts without SimpleLogin are deleted 30 days after the cut-off**
(recommended, over keeping them unreachable): nobody can add SimpleLogin signed out;
the reminder names both days.
@ -200,12 +211,12 @@ against app.simplelogin.io on 23 Oct:
ID the app holds, a `nonce`, which app and platform, the caller if signed in; ten
minutes, taken once) and answers the authorization address. The app opens it in the
browser (`flutter_web_auth_2`, as GitHub's). SimpleLogin sends the browser to
`https://<register's host>/simplelogin-callback`, the one address registered. On a
phone with learn.halkyon.eu, the system hands that address straight to the app (D9).
Elsewhere the instance's page there forwards code and `state` to the app: the
desktop's loopback (`http://127.0.0.1:43823/simplelogin-callback`, each app its own
port) or the phone's scheme (`app.halkyon.learn://simplelogin-callback`). Its targets
are fixed per app and platform, chosen by a marker in `state`, so it sends nobody
`https://<register's host>/simplelogin-callback`, the one address registered. The
instance's page there forwards code and `state` to the app (D9): the loopback on
Linux and Windows (`http://127.0.0.1:43823/simplelogin-callback`, each app its own
port) or the app's scheme on Android, iOS and macOS
(`app.halkyon.learn://simplelogin-callback`). Its targets
are fixed per app and way back, chosen by a marker in `state`, so it sends nobody
elsewhere; SimpleLogin's errors (a denial) are forwarded alike. The app hands the code
with its attempt's ID to `simpleLogin.complete`.
4. **Checking.** The register exchanges the code and checks the ID token: RS256 against
@ -213,8 +224,8 @@ against app.simplelogin.io on 23 Oct:
`https://app.simplelogin.io`, the audience its client ID, not expired, and the `nonce`
the attempt's. So a code caught on its way to the app is worthless: it can only
complete the attempt it was asked for, whose ID never left the app. (A hostile app
starting an attempt of its own is what D9's verified links stop: SimpleLogin answers
without a consent page for an app already allowed.) It keeps the `sub` alone
on the device beginning an attempt of its own isn't stopped, verified links or not
(D9): SimpleLogin answers without a consent page for an app already allowed.) It keeps the `sub` alone
(`simplelogin_identity`: account ID, `sub`, each unique); the name serves only as the
handle's suggestion at sign-up (D5); address, name and avatar are never stored, and
SimpleLogin's userinfo is never asked.
@ -268,5 +279,9 @@ against app.simplelogin.io on 23 Oct:
- Everyone has a handle, which stays private until they go public.
- The keys' password is one more thing to keep, and the recovery phrase also mends a lost
SimpleLogin link.
- GitHub's sign-in on phones has the weakness D9 avoids (its callback is the scheme)
until it ends.
- A hostile app on a person's device can begin a sign-in of its own and, once the person
has allowed Halkyon at SimpleLogin, come back with a session without a consent page
(4, D9); what it gets is the account's server-side data, while private studysets,
tags and settings stay sealed under the keys (ADR 0019). Whether SimpleLogin honours
OpenID Connect's `prompt=consent`, which would show the consent page every time, is
still to be checked.