Skip to content

feat(auth): restore grant auth for the Ring deeplink (#130) - #296

Merged
jvsena42 merged 4 commits into
mainfrom
feat/restore-grant-auth
Sep 11, 2026
Merged

jvsena42 merged 4 commits into
mainfrom
feat/restore-grant-auth

Conversation

@jvsena42

@jvsena42 jvsena42 commented Sep 10, 2026

Copy link
Copy Markdown
Owner

Closes the hold from #128. Both platform clients bind the FFI's plain start_auth_flow/await_auth_approval again, so Loopky mints pubkyauth://signin_grant?…&cid=loopky.app&cpk=….

Opened as a draft: the change is complete and green, but it cannot be verified on a device right now — see the blocker below.

The gates #130 listed have all landed

Gate Status
react-native-pubky on pubky 0.10 0.14.0, published 2026-08-31
Ring merges the grant bindings 4f2798a — "feat: use grant auth pubky bindings (#360)"
Ring cuts a release containing it v1.19 (2026-09-04); lockfile pins 0.14.0

A fourth gate, recorded only in the signIn/signUp KDoc rather than in the issue, has cleared too. The grant flow's POST /auth/grant/session was refused in 2026-08 with 403 Forbidden - Writing to directories other than '/pub/'. Both homeservers route it today:

                              staging    production
POST /auth/grant/session        422         422     <- route exists, payload rejected
POST /auth/grant/signup         422         422
POST /auth/grant/nonexistent    405         405     <- control

(The 403 was never export_grant_session_secret — that does no I/O in pubky 0.10.)

What changed

  1. UniffiPubkyClient / IosPubkyClient → the grant variants, passing LOOPKY_CLIENT_ID. Note the file is no longer AndroidPubkyClient.kt as Restore grant auth once a Pubky Ring release ships on pubky 0.10 #130 says — the jvmShared refactor (KMP toolchain audit against current official guidance: AGP 10 removes how shared is built, and Swift export targets the bridge traps by name #273) means this is shared with the CLI/desktop row.
  2. PubkyClient.startAuthFlow KDoc rewritten; the signIn/signUp KDoc corrected, since it claimed "both bind to cookie, like startAuthFlow".
  3. asSignupUrlsignup_grant. A one-word change: cid/cpk live in the query, so the host rewrite carries them over untouched.
  4. The grant_secret alias in SessionPayloadParser is left alone, exactly as Restore grant auth once a Pubky Ring release ships on pubky 0.10 #130 asked.
  5. requireSessionSecretShape fixed — not in Restore grant auth once a Pubky Ring release ships on pubky 0.10 #130, and it would have shipped broken. A grant credential is pubky-grant-credential-v1:<hs>:<secret>:<jws>, four parts where the guard demanded two, so every deeplink session would have been refused as bad_input.

signIn/signUp stay on cookie: their blocker is gone, but nobody has driven them down the grant path. Mixing is safe — restore_session sniffs the token kind, so signOut, revalidateSession and put_with_session take either.

Blocker: Ring v1.19 rejects every auth deeplink

Loopky mints the grant URL correctly (confirmed in logcat on both emulators), but Ring v1.19 answers it with [InputRouter] Unknown input format.

Input to Ring v1.19 Result
Real signin_grant, in-app, cold Ring Unrecognized format
Real signin_grant, in-app, warm Ring Unrecognized format — not stale-task replay
Real signin cookie URL from the FFI Unrecognized format — the control

The cookie row is the point: the flow Loopky shipped before this change fails on v1.19 too, so v1.19 breaks sign-in either way and this PR is not the cause. Measured on emulator-5554 (Pixel_Tablet) and emulator-5556 (Pixel_9, both Ring profiles set up).

Ring's own parser is fine (v1.19's inputParser.ts handles signin_grant/signup_grant) and libpubkycore.so loads. The failure is inside Ring's native parseDeepLink — our pubky 0.10.0 FFI parses all four shapes (cookie/grant, encoded/decoded) without complaint.

This needs a Ring fix before it can merge undrafted. Worth filing against pubky-ring.

Verification

./gradlew :shared:jvmTest :cli:test detektAll green; :androidApp:assembleDebug and lintSwift green. Journey 01 recorded as blocked in journeys/RESULTS.md with the measurements above.

Update — verified end to end with Bitkit

Bitkit (a synonymdev/bitkit-android#1224 build) approves this PR's signin_grant where Ring v1.19 can't. On the emulator:

  • The session payload parses through the grant_secret alias Restore grant auth once a Pubky Ring release ships on pubky 0.10 #130 said to keep: {capabilities, grant_secret, pubky}.
  • The session revalidates and the deck list loads.
  • The first write, the self-tag, gets 403 Write to this path is not allowed. That's the identity's per-user allowed_write_paths quota on staging, not the grant: a grant with the same client_id and caps, for another staging account, writes the same path fine.

On the emulator the sign-in also needed #298 (stacked on this one). The relay's name intermittently fails to resolve there, and without the resume the flow dies before anyone can approve.

Ring v1.19 still rejects every auth deep link, so Ring users stay blocked until Ring fixes that. The measurements above still stand.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YCmMyNzH6r4opjw6X1MZrH

jvsena42 and others added 3 commits September 10, 2026 15:00
Undoes the hold from #128. Both platform clients now call the FFI's plain
`start_auth_flow`/`await_auth_approval`, which delegate to pubky 0.10's grant
flow, so Loopky mints `pubkyauth://signin_grant?…&cid=…&cpk=…`.

All three preconditions #130 listed have landed: react-native-pubky 0.14.0
carries pubky 0.10, Ring merged the grant bindings (4f2798a), and v1.19 ships
them. A fourth, recorded only in the `signIn`/`signUp` KDoc, has cleared too —
the grant flow's `POST /auth/grant/session` was refused 403 in 2026-08, and both
homeservers route it as of today (422 against 405 for an unrouted path).

`asSignupUrl` rewrites the intent host to `signup_grant`. That stayed a one-word
change because `cid`/`cpk` live in the query, which the host rewrite carries over
untouched — `SignupGrantParams` wants exactly those two plus the `hs`/`st` this
already appended.

`signIn`/`signUp` stay on the cookie variants: their blocker is gone but nobody
has driven them down the grant path, and mixing the two kinds is safe because
`restore_session` sniffs which token it was handed.

The `grant_secret` alias in SessionPayloadParser is left alone, as #130 asked —
it stops being dormant at exactly this commit and is now the field every
deeplink sign-in arrives on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YCmMyNzH6r4opjw6X1MZrH
`requireSessionSecretShape` matched `<pubkey>:<cookie>` and nothing else, so
every session minted by the deeplink flow — now a grant credential, which is
`pubky-grant-credential-v1:<homeserver>:<secret>:<jws>` — was refused as
`bad_input` before it reached the homeserver. `loopky login` would have kept
working while the session it exported could not be used.

Matched by prefix *family* rather than by `v1`, so a later token version is not
refused here by a check that never gets to try it. The prefix is not a bypass:
a grant token still has to have all four non-blank parts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YCmMyNzH6r4opjw6X1MZrH
Ring v1.19 answers every `pubkyauth://` deeplink with "Unrecognized format",
measured on two emulators with both Ring profiles set up. The control is the
cookie URL: the flow Loopky shipped *before* the grant switch fails on v1.19
too, so sign-in is broken there either way and this change is not the cause.

Ring's own parser handles `signin_grant` and its lockfile pins
react-native-pubky 0.14.0; the failure is inside its native `parseDeepLink`,
which errors where our pubky 0.10.0 FFI parses all four shapes cleanly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YCmMyNzH6r4opjw6X1MZrH
@jvsena42
jvsena42 enabled auto-merge September 11, 2026 00:48
@jvsena42
jvsena42 merged commit 030625b into main Sep 11, 2026
7 checks passed
@jvsena42
jvsena42 deleted the feat/restore-grant-auth branch September 11, 2026 00:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant