Skip to content

enable QtKeychain with Wasm - #515

Merged
nschimme merged 1 commit into
MUME:masterfrom
nschimme:wasm-qtkeychain
Apr 14, 2026
Merged

enable QtKeychain with Wasm#515
nschimme merged 1 commit into
MUME:masterfrom
nschimme:wasm-qtkeychain

Conversation

@nschimme

@nschimme nschimme commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

Summary by Sourcery

Enable QtKeychain-based password storage and account auto-login for the Wasm build and refactor account credential management into a dedicated dialog and configuration flow.

New Features:

  • Add QtKeychain-backed password storage and auto-login support for the Wasm platform using per-account keys.
  • Introduce a ManagePasswordDialog for editing account names and passwords, including optional password deletion.

Bug Fixes:

  • Replace blocking message boxes with non-blocking, auto-deleting dialogs to avoid lifetime issues on platforms like Wasm.

Enhancements:

  • Refine password-management flow with explicit signals for password saved/deleted and centralize enablement logic for the auto-login option.
  • Extend PasswordConfig to support password deletion and differentiated handling of platform-specific key names.
  • Gate auto-login availability on the presence of both an account name and a stored password.

Build:

  • Update embedded QtKeychain build to use a wasm-capable fork and adjust CMake configuration for EMSCRIPTEN, including static builds where required.
  • Enable WITH_QTKEYCHAIN for the Wasm Docker build and add pkg-config to the Emscripten build dependencies.

@sourcery-ai

sourcery-ai Bot commented Apr 14, 2026

Copy link
Copy Markdown

Reviewer's Guide

Enables QtKeychain-based password storage for the Wasm build and refactors the account/password UI and password management flow to use a new ManagePasswordDialog and async, non-blocking dialogs, while extending PasswordConfig to support per-account keys and deletion notifications and updating build/Docker config to fetch and build a QtKeychain fork with Wasm support.

Sequence diagram for non-Wasm password set and auto-login flow

sequenceDiagram
    actor User
    participant GeneralPage
    participant ManagePasswordDialog
    participant PasswordConfig
    participant WriteJob as QKeychain::WritePasswordJob

    User->>GeneralPage: clicks setPassword button
    GeneralPage->>GeneralPage: slot_setPasswordClicked()
    alt password already saved
        GeneralPage->>PasswordConfig: getPassword()
        PasswordConfig->>WriteJob: m_readJob.start()
        Note over PasswordConfig,WriteJob: Read job emits sig_incomingPassword when finished
    else no password saved
        GeneralPage->>ManagePasswordDialog: create dialog and open()
    end

    rect rgb(230,230,250)
        PasswordConfig-->>GeneralPage: sig_incomingPassword(password)
        GeneralPage->>ManagePasswordDialog: create dialog
        GeneralPage->>ManagePasswordDialog: setAccountName(accountName)
        GeneralPage->>ManagePasswordDialog: setPassword(password)
        ManagePasswordDialog->>ManagePasswordDialog: user edits account and password
        User-->>ManagePasswordDialog: clicks OK
        ManagePasswordDialog-->>GeneralPage: accepted()
        GeneralPage->>GeneralPage: update config.account.accountName
        GeneralPage->>PasswordConfig: setPassword(newPassword)
        PasswordConfig->>WriteJob: m_writeJob.start()
        WriteJob-->>PasswordConfig: finished()
        PasswordConfig-->>GeneralPage: sig_passwordSaved()
        GeneralPage->>GeneralPage: setConfig().account.accountPassword = true
        GeneralPage->>GeneralPage: updateAutoLoginEnabled()

        alt autoLogin not checked
            GeneralPage->>GeneralPage: show QMessageBox Question
            User-->>GeneralPage: chooses Yes
            GeneralPage->>GeneralPage: ui->autoLogin->setChecked(true)
            GeneralPage->>GeneralPage: setConfig().account.rememberLogin = true
        end
    end

    rect rgb(240,255,240)
        ManagePasswordDialog-->>GeneralPage: sig_deleteRequested()
        GeneralPage->>PasswordConfig: deletePassword()
        PasswordConfig->>WriteJob: m_deleteJob.start()
        WriteJob-->>PasswordConfig: finished()
        PasswordConfig-->>GeneralPage: sig_passwordDeleted()
        GeneralPage->>GeneralPage: clear accountPassword and rememberLogin
        GeneralPage->>GeneralPage: ui->autoLogin->setChecked(false)
        GeneralPage->>GeneralPage: updateAutoLoginEnabled()
    end
Loading

Sequence diagram for Wasm account management with QtKeychain

sequenceDiagram
    actor User
    participant GeneralPage
    participant ManagePasswordDialog
    participant PasswordConfig
    participant WriteJob as QKeychain::WritePasswordJob

    User->>GeneralPage: clicks setPassword button
    GeneralPage->>GeneralPage: slot_setPasswordClicked()
    Note over GeneralPage: CURRENT_PLATFORM == Wasm

    GeneralPage->>ManagePasswordDialog: create dialog
    GeneralPage->>ManagePasswordDialog: setAccountName(accountName)
    ManagePasswordDialog->>ManagePasswordDialog: user edits account name
    User-->>ManagePasswordDialog: clicks OK
    ManagePasswordDialog-->>GeneralPage: accepted()

    GeneralPage->>GeneralPage: update config.account.accountName
    GeneralPage->>PasswordConfig: setPassword(emptyString)
    PasswordConfig->>WriteJob: m_writeJob.setKey(accountName)
    PasswordConfig->>WriteJob: m_writeJob.setTextData(emptyString)
    WriteJob->>WriteJob: start()
    WriteJob-->>PasswordConfig: finished()

    alt success
        PasswordConfig-->>GeneralPage: sig_passwordSaved()
        GeneralPage->>GeneralPage: setConfig().account.accountPassword = true
        GeneralPage->>GeneralPage: updateAutoLoginEnabled()
    else error or user denied
        PasswordConfig-->>GeneralPage: sig_error(msg) or suppressed on AccessDeniedByUser
        GeneralPage->>GeneralPage: log warning and show nonblocking QMessageBox
    end
Loading

Class diagram for updated password management components

classDiagram
    class GeneralPage {
        +slot_loadConfig()
        +slot_setPasswordClicked()
        +slot_themeComboBoxChanged(index int)
        +slot_displayMumeClockStateChanged(state int)
        +slot_displayXPStatusStateChanged(state int)
        +updateAutoLoginEnabled()
        +sig_reloadConfig()
    }

    class PasswordConfig {
        +PasswordConfig(parent QObject)
        +~PasswordConfig()
        +setPassword(password QString)
        +getPassword()
        +deletePassword()
        +sig_error(msg QString)
        +sig_incomingPassword(password QString)
        +sig_passwordSaved()
        +sig_passwordDeleted()
        -m_readJob QKeychain::ReadPasswordJob
        -m_writeJob QKeychain::WritePasswordJob
        -m_deleteJob QKeychain::DeletePasswordJob
    }

    class ManagePasswordDialog {
        +ManagePasswordDialog(parent QWidget)
        +~ManagePasswordDialog()
        +setAccountName(name QString)
        +accountName() QString
        +setPassword(password QString)
        +password() QString
        +sig_deleteRequested()
        -ui Ui::ManagePasswordDialog*
    }

    class QKeychainReadPasswordJob {
        +error() int
        +errorString() QString
        +textData() QString
        +setKey(key QString)
        +start()
    }

    class QKeychainWritePasswordJob {
        +error() int
        +errorString() QString
        +setKey(key QString)
        +setTextData(password QString)
        +start()
    }

    class QKeychainDeletePasswordJob {
        +error() int
        +errorString() QString
        +setKey(key QString)
        +start()
    }

    GeneralPage --> PasswordConfig : uses
    GeneralPage --> ManagePasswordDialog : creates

    PasswordConfig --> QKeychainReadPasswordJob : wraps
    PasswordConfig --> QKeychainWritePasswordJob : wraps
    PasswordConfig --> QKeychainDeletePasswordJob : wraps
Loading

File-Level Changes

Change Details Files
Refactor general preferences page to use non-blocking dialogs and a new password management flow driven by PasswordConfig signals and a ManagePasswordDialog.
  • Replace synchronous QMessageBox::question/warning calls with heap-allocated message boxes using WA_DeleteOnClose and finished/open signal handling to avoid blocking and to perform config reset and auto-login enablement decisions asynchronously.
  • Remove direct account password text field handling and legacy show/request password button logic in favor of a new setPassword button that opens ManagePasswordDialog and reacts to PasswordConfig signals for incoming, saved, and deleted passwords.
  • Add updateAutoLoginEnabled helper and wire it to relevant signals so auto-login is only enabled when an account name and stored password exist, including handling NO_QTKEYCHAIN and cleanup when a password is deleted.
  • Add slot_setPasswordClicked that branches behavior between Wasm and non-Wasm: on Wasm, only account name is set and an empty password triggers QtKeychain; on other platforms, it either fetches an existing password or opens ManagePasswordDialog to create/update/delete passwords.
src/preferences/generalpage.cpp
src/preferences/generalpage.h
src/preferences/generalpage.ui
Extend PasswordConfig to support Wasm-specific keying, password deletion, and richer signaling while integrating with global configuration.
  • Include configuration headers and add a DeletePasswordJob member, configuring all QKeychain jobs with autoDelete(false) and a shared error-handling lambda that suppresses AccessDeniedByUser on Wasm but emits sig_error otherwise.
  • Change setPassword and getPassword to choose the key name based on platform, using the account name as the key on Wasm and a constant PASSWORD_KEY elsewhere, and add a new deletePassword method that issues a delete job for the same key.
  • Add new signals sig_passwordSaved and sig_passwordDeleted emitted from the write and delete job finished handlers, and mark PasswordConfig as NODISCARD_QOBJECT via macros for better usage guarantees.
src/configuration/PasswordConfig.cpp
src/configuration/PasswordConfig.h
Introduce ManagePasswordDialog as a reusable dialog for editing account name and password with platform-specific UI behavior.
  • Implement ManagePasswordDialog QDialog subclass and associated UI that exposes setters/getters for account name and password and a sig_deleteRequested signal to drive password deletion from the general preferences page.
  • On Wasm, hide password-related controls and the delete button and retitle the dialog to "Manage Account" so only account name is editable, while on other platforms, support show/hide password toggling and enabling/disabling the delete button based on password presence.
src/preferences/ManagePasswordDialog.cpp
src/preferences/ManagePasswordDialog.h
src/preferences/ManagePasswordDialog.ui
Enable building QtKeychain for the Wasm target and update build tooling accordingly.
  • Update external QtKeychain CMake logic to treat EMSCRIPTEN like desktop for bundling: adjust library path/name selection, pass Emscripten-specific CMAKE_ARGS including disabling shared libs and wiring toolchain/host Qt for cross-build, and switch to a forked QtKeychain repository/branch with Wasm support.
  • Expose QTKEYCHAIN_EXTRA_ARGS to drive shared vs static build based on EMSCRIPTEN and list the static library as a byproduct so CMake can track it.
  • Enable WITH_QTKEYCHAIN for the Wasm Docker build, add pkg-config to the image, and register ManagePasswordDialog sources/UIS with the main src CMakeLists for inclusion in the application build.
external/qtkeychain/CMakeLists.txt
Dockerfile
src/CMakeLists.txt

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In PasswordConfig::setPassword, getPassword, and deletePassword, the #else (MMAPPER_NO_QTKEYCHAIN) branches reference accountName, which is not a parameter or member and will fail to compile; either remove those std::ignore lines or add an explicit parameter if you intend to support that value there.
  • When using the account name as the QtKeychain key on Wasm, consider what happens if accountName is empty or later changed (e.g. user renames the account): you may want to guard against empty keys and/or add a migration/cleanup path for passwords stored under the previous name.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `PasswordConfig::setPassword`, `getPassword`, and `deletePassword`, the `#else` (MMAPPER_NO_QTKEYCHAIN) branches reference `accountName`, which is not a parameter or member and will fail to compile; either remove those `std::ignore` lines or add an explicit parameter if you intend to support that value there.
- When using the account name as the QtKeychain key on Wasm, consider what happens if `accountName` is empty or later changed (e.g. user renames the account): you may want to guard against empty keys and/or add a migration/cleanup path for passwords stored under the previous name.

## Individual Comments

### Comment 1
<location path="src/configuration/PasswordConfig.cpp" line_range="77" />
<code_context>
     m_writeJob.setTextData(password);
     m_writeJob.start();
 #else
+    std::ignore = accountName;
     std::ignore = password;
     emit sig_error("Password setting is not available.");
</code_context>
<issue_to_address>
**issue (bug_risk):** The non-QtKeychain branches reference `accountName`, which is not defined in this scope and will not compile.

In the `#else` branches of `setPassword`, `getPassword`, and `deletePassword`, `accountName` is assigned to `std::ignore` but is neither a parameter nor a local variable, so this will not compile when `MMAPPER_NO_QTKEYCHAIN` is defined. Either remove the `std::ignore = accountName;` lines if the functions are not meant to take `accountName`, or change the signatures/overloads so that a valid `accountName` is available here.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/configuration/PasswordConfig.cpp Outdated
m_writeJob.setTextData(password);
m_writeJob.start();
#else
std::ignore = accountName;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): The non-QtKeychain branches reference accountName, which is not defined in this scope and will not compile.

In the #else branches of setPassword, getPassword, and deletePassword, accountName is assigned to std::ignore but is neither a parameter nor a local variable, so this will not compile when MMAPPER_NO_QTKEYCHAIN is defined. Either remove the std::ignore = accountName; lines if the functions are not meant to take accountName, or change the signatures/overloads so that a valid accountName is available here.

@codecov

codecov Bot commented Apr 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 119 lines in your changes missing coverage. Please review.
✅ Project coverage is 25.21%. Comparing base (ab4bfdb) to head (908b29a).
⚠️ Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
src/preferences/generalpage.cpp 0.00% 76 Missing ⚠️
src/preferences/ManagePasswordDialog.cpp 0.00% 22 Missing ⚠️
src/configuration/PasswordConfig.cpp 0.00% 21 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #515      +/-   ##
==========================================
- Coverage   25.26%   25.21%   -0.06%     
==========================================
  Files         513      514       +1     
  Lines       42372    42458      +86     
  Branches     4581     4585       +4     
==========================================
  Hits        10704    10704              
- Misses      31668    31754      +86     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@nschimme
nschimme merged commit fa71c1b into MUME:master Apr 14, 2026
18 of 20 checks passed
@nschimme nschimme mentioned this pull request Apr 14, 2026
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