feat: support Pubky signup - #724
Conversation
Greptile SummaryThis PR adds Pubky Ring signup URL parsing, wallet-derived identity registration, authorization approval, session activation, and resumable profile setup through the existing scanner and approval UI.
Confidence Score: 3/5The PR should not merge until Ring signup can recover from intermediate failures and users can leave pending profile setup without being immediately redirected back. The new flow can strand a remotely registered identity when approval or sign-in fails, and its navigation observer creates a repeatable Create Profile trap while setup remains pending. Files Needing Attention: Bitkit/Managers/PubkyProfileManager.swift, Bitkit/MainNavView.swift
|
| Filename | Overview |
|---|---|
| Bitkit/Managers/PubkyProfileManager.swift | Adds Ring signup and pending-profile state, but the signup sequence is not recoverable after an intermediate failure. |
| Bitkit/MainNavView.swift | Adds automatic profile-setup resumption, but route-driven reevaluation prevents users from leaving the setup screen. |
| Bitkit/Models/PubkyAuthRequest.swift | Adds strict parsing and validation for Ring signup parameters and reconstructs the corresponding authorization URL. |
| Bitkit/ViewModels/AppViewModel.swift | Routes Ring signup requests through the existing scanner while preserving restricted payment-flow state. |
| Bitkit/Views/Sheets/PubkyAuthApproval/PubkyAuthApprovalSheet.swift | Extends the approval sheet to execute signup and transition into profile setup, with partial-failure recovery delegated to the manager. |
| Bitkit/Services/PubkyService.swift | Adds low-level registration and Ring authorization operations used by the new signup sequence. |
Sequence Diagram
sequenceDiagram
participant R as Pubky Ring request
participant B as Bitkit scanner
participant H as Homeserver
participant A as Auth relay
participant P as Profile setup
R->>B: pubkyring://signup
B->>B: Parse and show approval
B->>H: Register wallet-derived identity
H-->>B: Registration complete
B->>A: Approve authorization
A-->>B: Authorization complete
B->>H: Sign in
H-->>B: Active session
B->>P: Navigate to Create Profile
Reviews (1): Last reviewed commit: "feat: support Pubky Ring signup" | Re-trigger Greptile
|
Regtest device QA, home Scan, QR from staging.pubky.app. Staging e2e doesn’t finish — is that expected? No spinner after scan. Scanner stays up after already-signed-in / invalid auth. bitkit_logs_2026-09-03_09-45-08-ios.zip ScreenRecording_09-03-2026_11-38-24_ios-compressed.mp4 |
|
@piotr-iohk Thanks for the device QA and logs. You found a real signup interop bug: Bitkit treated every The ordinary sign-in QR rejection is separate. These PRs use Paykit rc50’s app-scoped grant auth model, while staging Pubky App is still generating the older auth request format. Pubky App needs to update its sign-in flow to the new grant model for ordinary sign-in to work with Bitkit. Could you please recheck the staging signup path on this head? |
91555ae to
436ee06
Compare
|
Retested the rebased head on a physical iPhone 13 using the regtest build and a signup QR from staging.pubky.app. Signup now completes end to end: Bitkit shows progress while processing the request, creates the Pubky identity, opens profile setup, and the staging website continues successfully. Ordinary sign-in still fails because staging currently generates the older non-grant authorization request. As clarified, that is outside the scope of this signup PR. The original signup interoperability and missing-progress issues are resolved for me. bitkit_logs_2026-09-03_14-36-23-ios.zip ScreenRecording_09-03-2026.16-31-36_1-ios.MP4 |
436ee06 to
4eedebb
Compare
692eccc to
0ae29a6
Compare
0cdee24 to
2777d79
Compare
0ae29a6 to
5c008e2
Compare
5c008e2 to
6f836b7
Compare
|
Restacked onto the updated #697 head ( |
ovitrif
left a comment
There was a problem hiding this comment.
The write-side ordering from the last round is fixed at this head, thank you — setProfileSetupPending(true) now runs only after activateRegisteredIdentity returns, and the activation-failure path clears it again. What I found this time is the read side of that same flag: createIdentity trusts the UserDefaults-restored isProfileSetupPending without an active session, so one failed session restore after signup permanently dead-ends Create Profile. Details inline, along with two smaller points on test coverage and a duplicated loading view.
| existingImageUrl: String? = nil, | ||
| avatarImage: UIImage? = nil | ||
| ) async throws { | ||
| if isProfileSetupPending { |
There was a problem hiding this comment.
The write-time ordering in approveSignupAuth is fixed at this head, thank you — setProfileSetupPending(true) now runs after activateRegisteredIdentity. What I still see is the read side. isProfileSetupPending is restored from UserDefaults in init, and this branch trusts it without checking that a session actually came back. When initialize() lands on .restorationFailed it calls clearAuthenticatedState(), so publicKey is nil while the pending flag is still true; .profile then routes to PubkyChoiceView, whose Create Profile button navigates to .createProfile, and createIdentity re-enters this branch and throws PubkyServiceError.sessionNotActive. Nothing in the app clears the flag in that state, so a user who signed up but never finished the profile step and then has one failed session restore can never create a Pubky identity again. Could we require an active session for the pending branch — for example if isProfileSetupPending, let publicKey — and otherwise call setProfileSetupPending(false) and fall through to the normal deriveKeys()/sign-up path below?
| setProfileSetupPending(false) | ||
| } | ||
|
|
||
| func approveSignupAuth(request: PubkyAuthRequest) async throws { |
There was a problem hiding this comment.
approveSignupAuth is the new production entry point for both signup flows, and its whole contract is an ordering rule: the pending marker at setProfileSetupPending(true) must only be committed after activateRegisteredIdentity returns, and must stay clear when it throws. That rule is exactly what the last two commits on this branch fix, but no test in BitkitTests exercises it — the new tests only cover URL parsing and scan-scope rejection. This file already has the pattern for it: completeAuthenticationForTesting backs testCompleteAuthenticationRevokesSessionWhenActivationThrows, which is the same shape of test for the older auth flow. Could we add an equivalent #if DEBUG seam that injects the register, Ring-approve and activate closures, and cover both the success case and the activation-throws case asserting isProfileSetupPending stays false?
|
|
||
| HStack(spacing: 12) { | ||
| ActivityIndicator(size: 20) | ||
| BodyMText(t("profile__deriving_keys"), textColor: .white64) |
There was a problem hiding this comment.
AGENTS.md says "If identical or near-identical UI exists elsewhere in the codebase, extract it into a shared component rather than duplicating it." Here the new signup overlay pairs ActivityIndicator with BodyMText(t("profile__deriving_keys"), textColor: .white64), which is the same pair CreateProfileView.loadingView already renders — grepping profile__deriving_keys returns only these two call sites. Two copies of the same deriving-keys spinner will drift apart in size, spacing and copy the next time either screen is touched. Could we pull the indicator and caption into one small shared view in Components/ and use it from both the overlay and CreateProfileView?
Description
pubkyring://signupand auth-bearingpubkyauth://signuprequests, plus directpubkyauth://direct_signupand parameter-only legacypubkyauth://signup, through the normal scanner and deep-link flow.This PR is stacked on #697 and uses its Paykit rc50 authorization model. Ordinary Pubky App sign-in must use that grant-auth model; compatibility with the older sign-in request is intentionally outside this signup PR.
Linked Issues/Tasks
Depends on #697.
Screenshot / Video
Not included; this reuses the existing scanner, authorization approval sheet, loading treatment, and profile setup UI.
QA Notes
Validation: