Skip to content

fix(auth): sign in through the cookie flow so Pubky Ring can read the deeplink - #128

Merged
jvsena42 merged 2 commits into
mainfrom
fix/ring-cookie-auth-flow
Aug 24, 2026
Merged

fix(auth): sign in through the cookie flow so Pubky Ring can read the deeplink#128
jvsena42 merged 2 commits into
mainfrom
fix/ring-cookie-auth-flow

Conversation

@jvsena42

Copy link
Copy Markdown
Owner

The bug

Sign-in has been broken in prod since #125. Ring answers "Unrecognized format. Expected a recovery phrase, invite code, auth URL, or session request" and the flow ends there.

#125 didn't change our call, but it changed what the call does. The fork's start_auth_flow now delegates to start_grant_auth_flow, and pubky 0.10 renders a grant flow as a new intent:

before   pubkyauth://signin?caps=…&relay=…&secret=…
after    pubkyauth://signin_grant?caps=…&relay=…&secret=…&cid=…&cpk=…

No released Ring can read that. Every one bundles react-native-pubky@0.13.0 — pubky 0.9.x, whose generated binding has zero Grant/Cookie symbols — and its parser knows signin, signup, direct_signup, session and nothing else. An unknown intent falls through to InputAction.Unknown, which is that message.

Why not just wait for Ring

Ring is fixing its half on chore/pubky-0.10.0, but it's unmerged and currently unbuildable: it calls signUp(secret, homeserver, token, clientId) while its own lockfile still resolves 0.13.0, whose binding takes three arguments. npm's latest react-native-pubky is 0.13.0 — nothing carrying pubky 0.10 has been published. The chain is rn-pubky publishes → Ring merges → Ring releases, and users' installed Ring is behind all three.

The change

The fork still exports the pre-bump flow as start_cookie_auth_flow / await_cookie_auth_approval, already in the checked-in bindings on both platforms. It emits exactly the URL Loopky sent before #125, and returns the session_secret the rest of the client already expects.

Nothing else about #125 is touched — the TLS fixes it was actually for stay, and ClientId still travels on sign-in, sign-up and secret-key writes. Upstream deprecates the cookie flow, so this is a hold, not a destination; revisit when a Ring release ships on pubky 0.10. Both the interface KDoc and the commit say so.

Second commit fixes a bug that was live until the first one and would have been the next failure in the same sign-in: the grant flow returns its secret as grant_secret, and SessionPayloadParser only knew session_secret. Even a grant-capable Ring would have left us throwing session payload missing 'session_secret' one layer past anything the error names.

Verification

On the Pixel_9 emulator against Ring 1.17 — the released build, debug Loopky, account Cosmic-Crystal-Panda:

authUrl=pubkyauth://signin?caps=%2Fpub%2Floopky%2F%3Arw,…

Ring parsed it, listed /pub/loopky/ and /pub/pubky.app/ as READ,WRITE on its Authorize screen, and approved. Loopky logged complete: got session payload={capabilities, pubky, session_secret=…}, saved the session, wrote its loopky-user self-tag to the homeserver and loaded a 5-deck library — so the secret works for reads and writes, not just for signing in.

:shared:allTests, detektAll and lintSwift all green. iOS is the same two-line swap but is unverified, as ever.

🤖 Generated with Claude Code

jvsena42 and others added 2 commits August 24, 2026 08:17
Sign-in has been broken in prod since #125. Pubky Ring answers "Unrecognized
format. Expected a recovery phrase, invite code, auth URL, or session request"
and the flow ends there.

The bindings bump did not change our call, but it changed what the call does.
`start_auth_flow` in the fork now delegates to `start_grant_auth_flow`, and
pubky 0.10 renders a grant flow as a new intent:

    before   pubkyauth://signin?caps=…&relay=…&secret=…
    after    pubkyauth://signin_grant?caps=…&relay=…&secret=…&cid=…&cpk=…

No released Ring can read that. Every one of them bundles
react-native-pubky@0.13.0 — pubky 0.9.x, whose generated binding has zero
Grant/Cookie symbols — and its parser knows `signin`, `signup`,
`direct_signup` and `session`, nothing else. An unknown intent falls through
to `InputAction.Unknown`, which is the message above.

Ring is fixing its half on `chore/pubky-0.10.0`, but that branch is unmerged
and unbuildable: it calls `signUp(secret, homeserver, token, clientId)` while
its own lockfile still resolves 0.13.0, whose binding takes three arguments.
npm's latest react-native-pubky is 0.13.0. So this is not one Ring release
away, and Loopky cannot wait behind it.

The fork still exports the pre-bump flow as `start_cookie_auth_flow` /
`await_cookie_auth_approval`, already present in the checked-in bindings on
both platforms. It emits exactly the URL Loopky sent before #125, and its
session secret is the `session_secret` the rest of this client already
expects. Nothing else about #125 is touched — the TLS fixes it was actually
for are untouched, and `ClientId` still travels on sign-in, sign-up and
secret-key writes.

Upstream deprecates the cookie flow, so this is a hold, not a destination.
Revisit when a Ring release ships on pubky 0.10.

Verified on the Pixel_9 emulator against Ring 1.17 (the released build), debug
Loopky, account Cosmic-Crystal-Panda:

    authUrl=pubkyauth://signin?caps=%2Fpub%2Floopky%2F%3Arw,…

Ring parsed it, listed /pub/loopky/ and /pub/pubky.app/ as READ,WRITE on its
Authorize screen, and approved. Loopky then logged "complete: got session
payload={capabilities, pubky, session_secret=…}", saved the session, wrote its
loopky-user self-tag to the homeserver, and loaded a 5-deck library — so the
returned secret works for both reads and writes, not just for signing in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The FFI names the session secret after the flow that minted it —
`session_secret` from the cookie flow, `grant_secret` from the grant flow
pubky 0.10 added. The parser only knew the first, so a grant payload died on
`session payload missing 'session_secret'`.

That was live until the previous commit and would have been the *second*
failure in the same sign-in: even a grant-capable Ring approving the deeplink
would have left Loopky throwing here, one layer past anything the error names.
It is dormant now and stays dormant while we ask for the cookie flow, which is
the argument for the alias rather than against it — whoever switches back to
grant auth should not have to rediscover this.

The two values are interchangeable downstream: the FFI's `restore_session`
sniffs which kind of token it was handed, so `signOut`, `revalidateSession`
and the `*_with_session` writes all take either.

Tests cover both spellings and the genuinely-absent case, which had no
coverage at all before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jvsena42
jvsena42 merged commit a98f472 into main Aug 24, 2026
2 checks passed
@jvsena42
jvsena42 deleted the fix/ring-cookie-auth-flow branch August 24, 2026 11:20
@jvsena42

Copy link
Copy Markdown
Owner Author

Verified on a real phone, release build

Redmi Note 11 / Android 13 / MIUI, Ring 1.18 (newest release — still pins react-native-pubky@0.13.0, still no grant support: git merge-base --is-ancestor 4c9b1e1 1.18 is false). Release variant built with isDebuggable = true and debug-signed, local-only, not committed.

Signed in end to end: authUrl=pubkyauth://signin?… → Ring's Authorize sheet → complete: got session payload={capabilities, pubky, session_secret=…} → session saved → selfTag: loopky-user written → home showed a 4047-card deck with 29 due. @jvsena42 reproduced the same sign-in by hand afterwards.

A rollout note that matters more than the fix

The first attempt on that phone still showed the old error, and it was not this branch's fault. Loopky and Ring were looking at two different URLs:

Loopky logged:  pubkyauth://signin?caps=…&relay=…&secret=…
Ring logged:    pubkyauth://signin_grant?…&cid=loopky.app&cpk=…   ← Unrecognized format

dumpsys activity recents explained it — two tasks:

task base intent mCallingUid
Ring's (resumed) pubkyauth://signin_grant?… u0a351 — the previous install
the chooser pubkyauth://signin?… u0a352 — this build

Android stores the launching URL as the base intent of Ring's task with isPersistable=true. MIUI resumed that pre-existing task instead of delivering the new intent, so Ring cold-started and replayed a URL from an attempt made by the old, unfixed build.

So a user who hit this bug can see the same error once after updating, until Ring's task is gone — and the obvious reaction (reinstall and retry) never clears it. am force-stop doesn't clear it; swiping the card out of MIUI recents didn't either. What worked was re-launching Ring with NEW_TASK|CLEAR_TASK|CLEAR_TOP to replace the base intent. pm clear to.pubky.ring would also do it and must never be suggested — it wipes the user's keys.

Worth a line in the release notes: if sign-in still fails after updating, close Pubky Ring from recents and try again.

Unrelated MIUI detail for anyone testing on that device: Bitkit also registers pubkyauth://, so every launch hits the resolver dialog and picking Ring through it did not deliver the intent. Temporarily disabling Bitkit made the flow complete first try.

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