Skip to content

Sign-in: Ring and Bitkit QRs side by side, profile prefill, clear quota error - #111

Merged
BitcoinErrorLog merged 8 commits into
release/shop-v0.6.8from
cursor/grant-profile-signin-1f48
Sep 24, 2026
Merged

BitcoinErrorLog merged 8 commits into
release/shop-v0.6.8from
cursor/grant-profile-signin-1f48

Conversation

@BitcoinErrorLog

Copy link
Copy Markdown
Owner

#48: new Bitkit identities on the Shop (profile save, both QRs, profile prefill)

Base: release/shop-v0.6.8 @ 5e7fc0e64 (tag shop-v0.6.25). Issue: BitcoinErrorLog/pubky-marketplace#48 (comment)

Root cause of "Could not save profile" (not a Shop or SDK defect)

The production homeserver refuses every pubky.app write for accounts created through Homegate's IP signup, which is how Bitkit creates an identity (PubkyRepo.createIdentity → POST https://homegate.pubky.app/ip_verification → signUp). Homegate gives those accounts allowed_write_paths = ["/pub/paykit/", "/pub/bitkit.to/"] and a 2 MB storage quota (pubky/pubky-stack .github/workflows/homegate-cicd.yml:110-116). The homeserver's WritePathLayer then answers 403 Write to this path is not allowed for /pub/pubky.app/* and /priv/pubky.app/*, whatever the session's capabilities.

Evidence, production, throwaway Homegate IP-signup identity 5dyqcwcn…:

  • shop.pubky.app v0.6.25 in Chromium: grant sign-in succeeds (POST /auth/grant/session 200), PUT /pub/pubky.app/profile.json → 403 Write to this path is not allowed, toast "Could not save profile". Pav's failure reproduced.
  • A root cookie session (/:rw) for the same account gets the same 403 on /pub/pubky.app/profile.json, /pub/pubky.app/marketplace/v1/… and /priv/pubky.app/…, while /pub/paykit/ and /pub/bitkit.to/ succeed.
  • The Shop's grant session (startGrantAuthFlow(CAPABILITIES, signin, cid shop.pubky.app), SDK 0.11) for the seller test seat n3pfudgx… writes, reads back and deletes /pub/pubky.app/proofs/…. The grant session storage API and the capability path are correct; only the account allow-list differs.

The Shop cannot lift that restriction. This PR makes the Shop say what happened and fixes the two UX problems. The homeserver-side fix is an operator change in pubky/pubky-stack (see "Needs an owner decision").

Changes

  1. Both QRs on /sign-in. Ring and Bitkit QRs render side by side, each labelled ("Pubky Ring" / "Bitkit", "Scan with Bitkit 2.5 or newer."); mobile shows both Authorize buttons. The Bitkit grant flow gets its own controller slot, so it no longer cancels the Ring single-approval ceremony. A completed sign-in, local-state cleanup and cross-tab sign-out cancel both. A Bitkit approval that settles after another sign-in won is signed out, not initialized. The Ring QR URL and its dual POST are unchanged. Browsers without grant delegation keep the Ring-only layout.
  2. Accurate save error. isWritePathNotAllowedError matches the homeserver's allow-list 403 body; Create profile shows "Your homeserver account doesn't allow saving a Pubky profile yet. Signing in again won't change this." A capability 403 keeps "Could not save profile".
  3. Prefill, no invented name. Create profile reads the account's Pubky App profile.json, then Bitkit's profile (Paykit rc55 /pub/bitkit.to/bitkit/wallet/profile.json, then the Bitkit 2.4 /pub/bitkit.to/profile.json), and prefills name, bio and safe links without overwriting typing. With no profile the name is empty. An existing Pubky App profile.json still skips onboarding (unchanged userIsSignedUp). generateRandomUsername had no other caller and is removed.

§6d contract table

# Input or state Deployed client / current behaviour New behaviour UI copy or state Test name
S1 /sign-in, no grant delegation Ring QR only unchanged Ring QR only bitkit qr hidden without delegation
S2 /sign-in, grant delegation available Ring QR; Bitkit QR behind "Signing in with Bitkit? Use Bitkit instead" Ring and Bitkit QRs side by side, labelled; no switch link "Authorize with Pubky Ring or Bitkit to sign in." shows the Ring and Bitkit QRs side by side, each labelled, with no link to click
S3 Ring QR URL pubkyauth://signin?caps&relay&secret unchanged unchanged Chromium ring-with-both-qrs-proof.mjs check 2; existing R1.1 tests
S4 Bitkit flow starts while the Ring ceremony is live (and the reverse) Bitkit start cancels the Ring ceremony (single slot) both stay live both QRs scannable starting the Bitkit QR keeps the Ring ceremony live, starting the Ring QR keeps the Bitkit flow live
S5 Any sign-in completes, cleanup, cross-tab sign-out cancels the one live flow cancels both, bumps the flow generation progress view a completed sign-in cancels both QRs
S6 Bitkit approval settles after another sign-in or a sign-out n/a (one flow) grant session signed out, rejected as canceled; signed-in identity untouched none a Bitkit approval that settles after another sign-in won is signed out, not returned; positive a Bitkit approval with no competing sign-in is returned
S7 Hook releases its Bitkit handle cancels the single slot cancels only the Bitkit slot Ring QR stays releasing the Bitkit handle cancels only the Bitkit flow
S8 Mobile /sign-in "Authorize with Pubky Ring" + text link both Authorize buttons "Authorize with Pubky Ring", "Authorize with Bitkit" offers both authorize buttons on mobile
S9 Copy on the Bitkit QR n/a copies the Bitkit flow URL "Authentication link copied" copying the Bitkit QR copies the Bitkit flow URL, not the Ring one
E1 Homeserver 403 Write to this path is not allowed on profile save "Could not save profile" account-restriction copy "Your homeserver account doesn't allow saving a Pubky profile yet. Signing in again won't change this." the account allow-list 403 maps to an auth error the profile form can name, names the homeserver account restriction when the profile write is refused by path
E2 Homeserver capability 403 "Could not save profile" unchanged "Could not save profile" a capability 403 is not reported as an account restriction, keeps the generic copy for other homeserver 403s
E3 401 / session expired "Session expired. Please sign in." unchanged unchanged existing useProfileForm / error tests
P1 Pubky App profile.json exists at sign-in onboarding skipped (userIsSignedUp) unchanged; if Create profile is reached, it prefills from it first — prefers an existing Pubky App profile
P2 No Pubky App profile, Bitkit rc55 profile random name ("Dusty-Dolphin-Shield") name, bio, safe links prefilled prefilled form falls back to the Bitkit profile, newest layout first, prefills name, bio and links from an existing profile; Chromium check 6
P3 Only the Bitkit 2.4 layout random name prefilled prefilled form reads the Bitkit 2.4 layout when only it exists
P4 No profile anywhere random name empty name, Finish disabled until typed placeholder "Enter your name" does not invent a name when the account has no profile; Chromium check 6 (no-profile run); VRT onboarding-profile-*
P5 Malformed or wrong-typed profile JSON, javascript: links n/a fields ignored, unsafe links dropped, values bounded to spec limits — ignores fields of the wrong type instead of failing, takes the name, bio and safe links from a Bitkit profile, bounds every field to the Pubky App limits
P6 Profile read fails n/a next source; never rejects; log carries path and error code only form as P4 never rejects: a failed read moves on to the next source
P7 User types before the prefill arrives n/a typed value kept — never overwrites a name typed before the profile arrives
P8 Edit profile n/a no prefill read — edit mode never reads a prefill

Identity predicates: S6 compares nothing new; the signed-in identity is still decided by completeAuthenticatedSession → persistIdentityUnderLock (shouldAbortIdentityPersist against currentUserPubky and the persisted blob). Residual (P3 cleanup): if a Ring token was already redeemed when a Bitkit completion cancels the Ring flow, a later Ring completion for a different pubky is refused by that guard; for the same pubky it replaces the grant session and the grant record stays in IndexedDB until the next sign-out's clearAll.

The Bitkit profile fixture is authored from pubky/paykit-rs v0.1.0-rc55 PaykitProfile serde and bitkit-android PubkyProfileData; no live Bitkit-written profile has been captured yet. Parsing is tolerant (.passthrough(), every field optional).

Proof

  • Pre-push gate: PREPUSH OK 22e5d08862bf345050d660cb834f5289c91e4312 403 (prettier and eslint on 21/19 changed files, typecheck, vitest related 708 files / 10,814 tests, Linux VRT 62 specs: 98 + 26 files passed).
  • Real Chromium on head 22e5d088, local build of this branch against the production homeserver, relay, Nexus and marketplace service, throwaway Homegate IP-signup identity: both QRs visible without a click; Bitkit grant approval signs in; Create profile shows an empty name (no profile) or the Bitkit name and bio (after writing a Bitkit rc55 profile); save shows the account-restriction copy on the homeserver's 403.
  • Real Chromium, same build, seller test seat: Ring approval with both QRs live → one POST /session 200, one marketplace POST /v1/auth/sessions 201, cookie export, no grant exchange; signed out after.
  • Mutation calibration: each new controller, seed and hook gate fails on a deliberate regression; the VRT baseline fails on a one-byte change.

Needs an owner decision (outside this repo)

Bitkit-created identities stay unable to save a Shop profile, publish listings, or write the purchase-bootstrap proof document until pubky/pubky-stack adds /pub/pubky.app/ to the production ip_verification.signup_quota.allowed_write_paths and existing Homegate IP accounts get the same quota update on the homeserver.

The Bitkit grant flow gets its own controller slot, so starting it no longer
cancels the Ring single-approval ceremony and vice versa. A completed sign-in,
local-state cleanup and cross-tab sign-out cancel both. A Bitkit approval that
settles after another sign-in won is signed out instead of initialized.
Both QRs are labelled and scannable at once; the Bitkit QR is no longer behind
a text link. Mobile shows both Authorize buttons. Browsers without grant
delegation keep the Ring-only layout.
pubky-homeserver answers 403 "Write to this path is not allowed" when the
account's allowed_write_paths quota excludes a path, whatever the session's
capabilities. Homegate IP signups (Bitkit's path) allow only /pub/paykit/ and
/pub/bitkit.to/. isWritePathNotAllowedError tells it apart from a capability
403.
…invent a name (#48)

Create profile reads the account's Pubky App profile.json, then Bitkit's
profile (Paykit rc55 /pub/bitkit.to/bitkit/wallet/profile.json, then the
Bitkit 2.4 /pub/bitkit.to/profile.json), and prefills name, bio and safe links
without overwriting typing. With no profile the name stays empty.
generateRandomUsername had no other caller and is removed.

Saving on an account whose homeserver refuses Pubky App writes shows that
reason instead of "Could not save profile".
…th directions

Every sign-in QR records the flow generation it started under. A completion
whose generation moved on lost to another sign-in or a sign-out: it is signed
out and never initialized, in the Ring dual-POST window, in the Bitkit wrapper,
and as the first synchronous step of completeAuthenticatedSession, which closes
the window where both approvals are already in hand. A losing Ring completion
drops the marketplace bearer it minted; a losing Bitkit approval mints none and
leaves the winner's bearer alone. The winner's session and grant record are
untouched. The sign-in hook no longer toasts a failure for the loser.
@BitcoinErrorLog
BitcoinErrorLog merged commit a26333e into release/shop-v0.6.8 Sep 24, 2026
13 checks passed
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