Skip to content

feat(auth): desktop platform sign-in, data binding and lock - #709

Open
robspan wants to merge 1 commit into
feat/desktop-auth-gatefrom
feat/desktop-platform-sign-in
Open

robspan wants to merge 1 commit into
feat/desktop-auth-gatefrom
feat/desktop-platform-sign-in

Conversation

@robspan

@robspan robspan commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Third of three PRs for the desktop platform sign-in (#295). Based on #708 (auth mode and gate), which is based on #707; until the stack merges, only the last commit is this PR.

What

A desktop with the admin config file now signs in through the system browser and keeps each account's data to that account. Without the file nothing of this runs: no guard is installed and every open behaves as on main.

  • Sign-in (RFC 8252, src/main/auth/platform-sign-in.ts, oidc-provider.ts, loopback-callback.ts): loopback redirect on 127.0.0.1, code flow with PKCE S256, state, nonce, acr_values, prompt=login, max_age. Both tokens are checked with platform-jwt, including the access claim (for example a group); no token is kept. A first sign-in that only set the authenticator app up asks for one more.
  • Binding (database-binding.ts): a database is bound to the first identity (issuer and subject) that opens it while signed in and refused for every other one. Server workspaces stay closed in platform mode.
  • Lock (session-lock.ts, DesktopAuth.lock): after idle time, the maximum session, screen lock or suspend, and by a toolbar button. The gate refuses at once, running jobs are cancelled, the database closes once no work runs, every copy of its key is dropped, and finished jobs are forgotten.
  • requirePassphrase: no new key gets an OS wrap, so only passphrase-protected databases open on shared computer accounts.
  • Guide: docs/guide/desktop-sign-in.md for users and administrators.

Existing files touched

File Lines Why
src/main/services/DatabaseManager.ts +51/−5 setOpenGuard: checkKey before a SQLite file is touched, checkSession before any session becomes current; with a guard an AppError keeps its message (without one every open failure is wrapped as before); lock() closes with closeForLock, false while work runs
src/main/storage/sqlite/SqliteStorageSession.ts +25 closeForLock(): the key goes first and new reads wait, so a writer or read pool respawned by a late call finds no key; close() is unchanged
src/main/database/DatabaseService.ts +9 discardKey()
src/main/database/db-key-store-instance.ts +14/−1 setOsKeyWrapAllowed(false) for requirePassphrase; existing wraps still unwrap, so the guard still recognises OS-unlockable keys
src/main/services/auth/{AuthService,auth-writes}.ts +13, +8 read and bind database_settings.platform_identity (INSERT OR IGNORE through the writer)
src/main/services/jobs/JobRunner.ts +3/−2 pruneTerminalJobs(keep) public: a lock forgets finished jobs, whose params name the account's files
src/renderer/src/components/AppToolbar.vue +2 the lock button
shared contract, preload, parity manifest, mocks +27/−6 auth:desktopSignIn, auth:desktopCancelSignIn, auth:desktopLock, desktop-only
docs/.vitepress/config.mts +2/−1 the guide page

Verification

Against main at 779a6d25, macOS arm64, Node 24.15.0, Electron 43, windows hidden:

  • Vitest (main and renderer), compared test by test with main: no test changes status; 136 new tests pass.
  • out/ hashed file by file: as in feat(auth): desktop auth mode, access gate and admin config file #708, and in out/main also the shared chunk with the auth write operations and one chunk whose require lines of Node built-ins move.
  • E2E: as feat(auth): desktop auth mode, access gate and admin config file #708, plus desktop-sign-in-keycloak.e2e.ts against a local Keycloak (opt-in via VARLENS_E2E_KEYCLOAK_URL): enrolment, sign-in, lock, a second account kept off the first one's database, an account outside the access group refused. It plays the browser through VARLENS_E2E_SIGN_IN_URL_FILE, a hook that works in development builds only.
  • make lint-check, format-check, typecheck, agent-check pass; make web-gate-static: 1131 passed, 1 expected failure, as on main.

Not covered yet (listed in the spec): the Windows ACL check has run against recorded descriptors only, not on a Windows host; settings shared by one computer account (recent databases, annotation author) are not tied to the identity yet.

Spec: .planning/specs/2026-10-24-desktop-platform-sign-in.md.

A desktop with the admin config file now signs in through the system
browser and keeps each account's data to that account (#295).

- Sign-in (RFC 8252): loopback redirect on 127.0.0.1, code flow with
  PKCE S256, state, nonce, acr_values, prompt=login and max_age. Both
  tokens are checked with platform-jwt, including the access claim (for
  example a group); no token is kept. A first sign-in that only set the
  authenticator app up asks for one more.
- Binding: DatabaseManager gains an open guard. A database is bound to the
  first identity (issuer and subject) that opens it while signed in and
  refused for every other one; server workspaces stay closed. Without the
  config file no guard is installed and opens behave as before.
- Lock: after idle time, the maximum session, screen lock or suspend, and
  by the toolbar button. The gate refuses at once, running jobs are
  cancelled, the database closes once no work runs and every copy of its
  key is dropped; finished jobs are forgotten.
- requirePassphrase: no new key gets an OS wrap, so only
  passphrase-protected databases open on shared computer accounts.
- Tests: an opt-in end-to-end test against a real Keycloak
  (VARLENS_E2E_KEYCLOAK_URL), which plays the browser through the
  development-build-only VARLENS_E2E_SIGN_IN_URL_FILE hook.
- docs/guide/desktop-sign-in.md describes the setup for users and
  administrators.

Spec: .planning/specs/2026-10-24-desktop-platform-sign-in.md

This branch has not been deployed

No deployments
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