feat(auth): restore grant auth for the Ring deeplink (#130) - #296
Merged
Merged
Conversation
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
This was referenced Sep 11, 2026
jvsena42
marked this pull request as ready for review
September 11, 2026 00:48
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the hold from #128. Both platform clients bind the FFI's plain
start_auth_flow/await_auth_approvalagain, so Loopky mintspubkyauth://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
react-native-pubkyon pubky 0.104f2798a— "feat: use grant auth pubky bindings (#360)"A fourth gate, recorded only in the
signIn/signUpKDoc rather than in the issue, has cleared too. The grant flow'sPOST /auth/grant/sessionwas refused in 2026-08 with403 Forbidden - Writing to directories other than '/pub/'. Both homeservers route it today:(The 403 was never
export_grant_session_secret— that does no I/O in pubky 0.10.)What changed
UniffiPubkyClient/IosPubkyClient→ the grant variants, passingLOOPKY_CLIENT_ID. Note the file is no longerAndroidPubkyClient.ktas 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 howsharedis built, and Swift export targets the bridge traps by name #273) means this is shared with the CLI/desktop row.PubkyClient.startAuthFlowKDoc rewritten; thesignIn/signUpKDoc corrected, since it claimed "both bind to cookie, likestartAuthFlow".asSignupUrl→signup_grant. A one-word change:cid/cpklive in the query, so the host rewrite carries them over untouched.grant_secretalias inSessionPayloadParseris left alone, exactly as Restore grant auth once a Pubky Ring release ships on pubky 0.10 #130 asked.requireSessionSecretShapefixed — 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 ispubky-grant-credential-v1:<hs>:<secret>:<jws>, four parts where the guard demanded two, so every deeplink session would have been refused asbad_input.signIn/signUpstay on cookie: their blocker is gone, but nobody has driven them down the grant path. Mixing is safe —restore_sessionsniffs the token kind, sosignOut,revalidateSessionandput_with_sessiontake 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.signin_grant, in-app, cold Ringsignin_grant, in-app, warm Ringsignincookie URL from the FFIThe 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) andemulator-5556(Pixel_9, both Ring profiles set up).Ring's own parser is fine (v1.19's
inputParser.tshandlessignin_grant/signup_grant) andlibpubkycore.soloads. The failure is inside Ring's nativeparseDeepLink— 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 detektAllgreen;:androidApp:assembleDebugandlintSwiftgreen. Journey 01 recorded as blocked injourneys/RESULTS.mdwith the measurements above.Update — verified end to end with Bitkit
Bitkit (a synonymdev/bitkit-android#1224 build) approves this PR's
signin_grantwhere Ring v1.19 can't. On the emulator:grant_secretalias Restore grant auth once a Pubky Ring release ships on pubky 0.10 #130 said to keep:{capabilities, grant_secret, pubky}.403 Write to this path is not allowed. That's the identity's per-userallowed_write_pathsquota on staging, not the grant: a grant with the sameclient_idand 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