Skip to content

ledger rung 5 has no auth story: the shipped GUI installs no principal, and RemoteServer would clear one anyway #242

Description

@Yaraslaut

What I found

Rung 5 (examples/ledger) has no authentication or authorization story at all, and the consequence is that every mutating LedgerModel/BudgetModel/RuleModel action fails in the shipped desktop client, in both deployment modes.

Every mutating action in the rung opens with the same gate:

const auto* ctx = morph::session::current();
if (ctx == nullptr || ctx->principal.empty()) {
    throw EmptyPrincipalError{};
}

There are two independent ways rung 5 fails to supply that principal.

1. Local mode: nobody ever installs one

examples/ledger/gui/main.cpp builds an AppContext and four QML bridges, and never calls ctx.login(...). Nothing in examples/ledger/gui_lib/ calls Bridge::setDefaultSession either:

$ grep -rn "login\|setDefaultSession\|principal" examples/ledger/gui_lib/ examples/ledger/gui/main.cpp
examples/ledger/gui/main.cpp:65:        // Nothing is torn down and rebuilt around login: a login only installs

AppContext's default session is empty, so LocalBackend installs an empty session::Context and OpenAccount — the first thing a user does — throws EmptyPrincipalError.

Contrast bookmarks/kanban, whose gui/main.cpp files each mint a Local-mode token and log in explicitly.

2. Remote mode: RemoteServer clears the claim, and there is no server anyway

RemoteServer::dispatchExecute (include/morph/core/remote.hpp) does:

if (auto verified = _authorizer->authenticate(env.session)) {
    env.session.principal = std::move(*verified);
} else {
    env.session.principal.clear();
}

which is correct and deliberate (docs/spec/security.md: never pass an unverified claim through). But IAuthorizer::authenticate's default — and therefore AllowAllAuthorizer's — returns nullopt, so with the allow-all default any principal is cleared. Rung 5 ships no src/auth/, no AuthModel, no Login action and no token secret, so allow-all is the only authorizer it can install.

It also ships no src/server/, so there is no ladder_ledger_server for --server <url> to connect to.

Verification status

Inferred from reading the code, not reproduced by running the GUI. What I did measure, on fix/160-ledger-app-layer (branched from d6690c5): an App dispatching RunReportJob over a RemoteServer with the allow-all default failed with the model's own principal check, which is the same mechanism:

[ERROR] [ledger::App] RunReportJob dispatch failed for job 1: RunReportJob: only the report runner may run a report job

Switching that dispatch to a LocalBackend (which installs the session directly, with no authenticate step) made it pass. That is direct evidence for (2). For (1) I have only the greps above and the gate's source; I did not launch ladder_ledger_gui.

Note that examples/ledger/tests/test_multiclient.cpp uses BackendRig in Mode::Local, so rung 5's own suite has never exercised a RemoteServer carrying a principal — which is why this has stayed invisible.

What would change the verdict

Close this if either turns out to be wrong:

  • launching ladder_ledger_gui and successfully creating an account (would falsify (1));
  • a session path I missed that installs a principal for the rung's bridges.

Otherwise this closes when rung 5 gains a login + authorizer (and, if it is to have a Remote mode at all, a src/server/main.cpp), the way rungs 1-4 each did.

Not folded into #160

Found while implementing #160 (moving the report job out of LedgerModel into an App layer). Per AGENTS.md it is filed rather than fixed there: that PR works around (2) by dispatching over a LocalBackend and says so in examples/ledger/include/ledger/app/app.hpp, and does not touch (1) at all.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: ladderSubsystem: ladderbugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions