Add secure credential storage, sessions, and a launch screen

Login now persists properly rather than through the JSON file the test
harness was using.

  dr-plat            SecretStore trait plus a Secret Service backend.
                     Verified against the live GNOME Keyring: store,
                     retrieve, delete, confirm-gone all round-trip.
  Session/SessionStore   splits credentials from settings — the app
                     password goes to the keyring (FR-NC-2), while
                     server, login, chosen root and format selection are
                     ordinary config. A test asserts the credential never
                     appears in the config file.
  LaunchModel        the launch-screen state machine, testable without a
                     display server: sign in, approve in browser, choose
                     folder, tick formats, sign out.
  launch.slint       the screen itself, in its own file.

Absence of a secrets daemon is an explicit degraded mode, not a silent
fallback to plaintext — the screen says sign-in will not persist rather
than letting the user find out next launch. Android's Keystore backend
fails loudly for the same reason: a no-op store would look like it
worked and then lose the credential.

Two bugs caught by tests rather than by running it:

  - fail() after busy() signed the user out, because busy() had already
    discarded the session. A failed *scan* would have logged you out.
    Busy now carries the session.
  - normalise_server upgrades http:// to https:// rather than accepting
    it. NFR-SEC-3 requires TLS, and silently sending a credential in the
    clear is not a decision to make on the user's behalf.

launch.slint is not yet wired into app.slint. Calling slint_build::compile
twice replaces the generated module rather than adding to it, which broke
the other in-flight work on dr-ui; I reverted that immediately. Wiring it
needs an import inside app.slint, which is that work's file to change.

419 tests passing across ten crates.
This commit is contained in:
2026-08-09 15:20:39 +02:00
parent c8bb08e661
commit 09e3043f4c
33 changed files with 3506 additions and 155 deletions
+23 -2
View File
@@ -12,8 +12,8 @@
//! it. This is a diagnostic, not the export path (FR-EXP-*).
use dr_gpu::{AdjustPass, Demosaicer, GpuContext};
use dr_pipeline::ops::{colour, exposure, tone, white_balance};
use dr_pipeline::EditGraph;
use dr_pipeline::ops::{colour, colour_mixer, contrast, exposure, tone, white_balance};
use dr_pipeline::{EditGraph, ParamId};
fn main() {
env_logger::init();
@@ -65,6 +65,27 @@ fn main() {
graph.set_param(colour::BRILLIANCE_ID, colour::BRILLIANCE, 40.0);
graph.set_param(white_balance::ID, white_balance::TEMPERATURE, 15.0);
}
// Contrast alone, so its effect can be judged without anything else
// moving.
"contrast" => {
graph.set_param(contrast::ID, contrast::CONTRAST, 60.0);
}
"flat" => {
graph.set_param(contrast::ID, contrast::CONTRAST, -60.0);
}
// The mixer, pushed hard on the two things this scene actually has:
// green vegetation and grey-blue rock.
"mixer" => {
graph.set_param(colour_mixer::ID, ParamId("green_sat"), 80.0);
graph.set_param(colour_mixer::ID, ParamId("green_hue"), -40.0);
graph.set_param(colour_mixer::ID, ParamId("chartreuse_sat"), 60.0);
graph.set_param(colour_mixer::ID, ParamId("azure_lum"), -50.0);
}
// One band only, to check the weighting really is selective rather
// than affecting the whole image.
"mixer_one" => {
graph.set_param(colour_mixer::ID, ParamId("green_sat"), 100.0);
}
_ => {}
}