Skip to content

Add keyring unlock option to per-profile unlock modal#52

Merged
romejoe merged 3 commits intomasterfrom
22-43-per-profile-independent-unlock
Mar 28, 2026
Merged

Add keyring unlock option to per-profile unlock modal#52
romejoe merged 3 commits intomasterfrom
22-43-per-profile-independent-unlock

Conversation

@romejoe
Copy link
Copy Markdown
Owner

@romejoe romejoe commented Mar 28, 2026

Changes

  • Add toggle option to unlock profiles using keyring master password instead of profile password
  • Keyring toggle is only shown for profiles managed by keyring
  • Default to keyring mode if the pre-selected profile is keyring-managed
  • Update password label dynamically based on selected unlock method
  • Handle keyring password retrieval and profile password lookup during unlock
  • Improve error messages to distinguish between keyring and profile password errors
  • Automatically hide keyring toggle and reset when switching to non-keyring-managed profile

Summary by CodeRabbit

  • New Features

    • Optional keyring master password mode for unlocking profiles with a toggle to switch authentication modes
    • Password field label updates contextually between profile password and keyring master password
    • Toggle visibility updates based on selected profile; form preserves inputs during async unlock attempts
  • Bug Fixes

    • Improved validation and clearer error messages for missing passwords, incorrect credentials, corrupted data, and keyring-specific failures (including missing profile in keyring)

When a profile is managed by the keyring, the unlock modal now shows a
'Use keyring master password' toggle (defaulting to on). When enabled,
the user enters the keyring master password to unlock just that profile
instead of its individual password. The toggle hides automatically when
switching to a non-managed profile.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@romejoe romejoe linked an issue Mar 28, 2026 that may be closed by this pull request
5 tasks
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2c2c7fe3-26b8-49f6-a651-da85ab5002c5

📥 Commits

Reviewing files that changed from the base of the PR and between 565bbca and d94611a.

📒 Files selected for processing (1)
  • src/modals/unlock-modal.ts

📝 Walkthrough

Walkthrough

Adds an optional keyring-based unlock mode to the unlock modal: a toggle to enter a keyring master password, fetch the selected profile's stored password from KeyringService, and use it to unlock the profile; updates labels, validation, and error messages, and snapshots form state before async calls.

Changes

Cohort / File(s) Summary
Unlock modal (keyring support)
src/modals/unlock-modal.ts
Adds useKeyring state and passwordSetting label switching; renders a "Use keyring master password" toggle; snapshots profileId, submittedPassword, useKeyring before async calls; on submit, branches to call plugin.keyringService.getProfilePasswords(...) when useKeyring is enabled and then sessionService.unlockProfile(..., profilePassword); updates validation and error messages and handles missing profile password and keyring-specific errors.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Modal as UnlockModal
    participant Keyring as KeyringService
    participant Session as SessionService

    User->>Modal: Select profile, enter password, toggle useKeyring?
    alt useKeyring == true
        Modal->>Keyring: getProfilePasswords(masterPassword)
        alt keyring returns profile password
            Keyring-->>Modal: profilePassword
            Modal->>Session: unlockProfile(profileId, profilePassword)
            Session-->>Modal: unlock success
            Modal-->>User: Profile unlocked
        else keyring error / missing profile
            Keyring-->>Modal: error / no password
            Modal-->>User: Incorrect keyring master password / Profile not found in keyring
        end
    else useKeyring == false
        Modal->>Session: unlockProfile(profileId, enteredPassword)
        alt unlock success
            Session-->>Modal: success
            Modal-->>User: Profile unlocked
        else unlock failure
            Session-->>Modal: error
            Modal-->>User: Incorrect password or corrupted database.
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped to the modal with a curious click,
A toggle for keys made the workflow slick,
Master password hummed, secrets unrolled,
Profiles woke up as the story was told,
✨🔑

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add keyring unlock option to per-profile unlock modal' directly and clearly summarizes the main change—adding a keyring-based unlock feature to the modal, which is the primary objective of this changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 22-43-per-profile-independent-unlock

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/modals/unlock-modal.ts`:
- Around line 175-180: The catch-all error message in the unlock flow is
misleading when useKeyring is true because failures can be either opening the
keyring (master/keyring password) or decrypting the stored profile password;
update the logic in the UnlockModal/unlock flow to separate the two failure
cases: wrap the keyring-opening call (the branch that uses the keyring unlock)
in its own try/catch and call this.showError with "Incorrect keyring master
password or corrupted keyring." on failure, and then wrap the subsequent
retrieval/decryption of the profile password (the code that reads and decrypts
the stored profile password) in a separate try/catch that calls this.showError
with "Stored profile password is incorrect or corrupted." on failure; keep the
existing console.error calls for debugging and preserve the this.useKeyring flag
to choose messaging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ab5e3d1f-4a32-4266-b2e1-2c0dab7c0a35

📥 Commits

Reviewing files that changed from the base of the PR and between 884d348 and db3b5d3.

📒 Files selected for processing (1)
  • src/modals/unlock-modal.ts

…odal

Split the keyring unlock branch into two distinct try/catch blocks:
- Opening the keyring now reports "Incorrect keyring master password or
  corrupted keyring."
- Decrypting the stored profile password now reports "Stored profile
  password is incorrect or corrupted."

This addresses CodeRabbit feedback that the previous catch-all was
misleading when the keyring opened fine but the stored password was stale.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/modals/unlock-modal.ts (1)

149-177: ⚠️ Potential issue | 🟠 Major

Freeze the profile ID for the whole submit flow.

src/keyring-service.ts:33-52 returns a map keyed by the profileIds passed into getProfilePasswords(). Here we pass [this.selectedProfileId], await, and then read this.selectedProfileId again. If the user changes the dropdown mid-submit, the lookup can miss the originally requested entry and onDone can report the wrong profile. Snapshot the mutable form state before the first await.

🛠️ Suggested fix
-		const config = this.plugin.settings.profiles[this.selectedProfileId];
+		const profileId = this.selectedProfileId;
+		const submittedPassword = this.password;
+		const useKeyring = this.useKeyring;
+		const config = this.plugin.settings.profiles[profileId];
 		if (!config) {
 			this.showError("Profile not found.");
 			return;
 		}
@@
-			if (this.useKeyring) {
+			if (useKeyring) {
 				let passwords: Map<string, string>;
 				try {
 					passwords = await this.plugin.keyringService.getProfilePasswords(
 						this.plugin.settings.masterKeyringPath,
-						this.password,
-						[this.selectedProfileId],
+						submittedPassword,
+						[profileId],
 					);
 				} catch (e) {
@@
-				const profilePassword = passwords.get(this.selectedProfileId);
+				const profilePassword = passwords.get(profileId);
 				if (!profilePassword) {
 					this.showError("Profile not found in keyring.");
 					return;
 				}
 				try {
-					await this.plugin.sessionService.unlockProfile(this.selectedProfileId, config, profilePassword);
+					await this.plugin.sessionService.unlockProfile(profileId, config, profilePassword);
 				} catch (e) {
@@
 			} else {
-				await this.plugin.sessionService.unlockProfile(this.selectedProfileId, config, this.password);
+				await this.plugin.sessionService.unlockProfile(profileId, config, submittedPassword);
 			}
 			this.close();
-			this.onDone?.(this.selectedProfileId);
+			this.onDone?.(profileId);

Also applies to: 187-187

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modals/unlock-modal.ts` around lines 149 - 177, Snapshot the mutable
selectedProfileId into a local const before the first await in the submit flow
so the value used for getProfilePasswords, lookup in the returned Map, and the
subsequent unlockProfile call is stable even if UI state changes mid-submit;
specifically capture this.selectedProfileId into a local variable (e.g.,
selectedProfileId) before calling keyringService.getProfilePasswords and then
use that local variable for the Map.get(...) and for
plugin.sessionService.unlockProfile(...) (also apply the same snapshot for the
later occurrence around the other await at the end of the submit flow).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/modals/unlock-modal.ts`:
- Around line 61-64: When the unlock mode toggles (the useKeyring switch), stale
credentials must be cleared: update the toggle handler(s) that call
dd.setValue(...).onChange(...) and the methods that flip useKeyring (e.g.,
updateKeyringSection and any handlers referenced around the dd.setValue call and
the other related handlers) to reset this.password to an empty string and clear
the associated input element value (the password input DOM reference) whenever
useKeyring changes; ensure the UI input is also cleared so a previously typed
keyring master password cannot be reused as a profile password after flipping
modes.

---

Outside diff comments:
In `@src/modals/unlock-modal.ts`:
- Around line 149-177: Snapshot the mutable selectedProfileId into a local const
before the first await in the submit flow so the value used for
getProfilePasswords, lookup in the returned Map, and the subsequent
unlockProfile call is stable even if UI state changes mid-submit; specifically
capture this.selectedProfileId into a local variable (e.g., selectedProfileId)
before calling keyringService.getProfilePasswords and then use that local
variable for the Map.get(...) and for plugin.sessionService.unlockProfile(...)
(also apply the same snapshot for the later occurrence around the other await at
the end of the submit flow).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 61e55bf2-d332-452b-981f-11fd5aa84661

📥 Commits

Reviewing files that changed from the base of the PR and between db3b5d3 and 565bbca.

📒 Files selected for processing (1)
  • src/modals/unlock-modal.ts

- Capture selectedProfileId, password, and useKeyring into local consts
  before the first await in submit() to prevent stale reads if the user
  changes the dropdown or toggle while the async flow is in flight.
- Store a reference to the password input element and add clearPassword()
  to reset both this.password and the DOM value whenever the unlock mode
  toggles (via the toggle handler or updateKeyringSection), so a previously
  typed keyring master password cannot be reused as a profile password
  after switching modes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@romejoe romejoe merged commit d9825e6 into master Mar 28, 2026
5 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.

[4.3] Per-profile independent unlock

1 participant