Repository navigation
feat: add desktop distribution for Windows, macOS and Linux - #84
Merged
Merged
Conversation
Tauri 2 shell around the existing React + Vite frontend — the hosted Vercel deployment stays canonical and unchanged for browsers. Desktop shell (src-tauri): - product name Dev Ledger, identifier site.devledger.desktop - resizable native window (1320x860, min 960x600), brand icon set - least-privilege capabilities: opener (https/mailto only), http scoped to devledger.site, deep-link + single-instance; no fs/shell/clipboard - strict CSP matching the web posture; Rust on_navigation restricts the webview to the app origin; external links go to the system browser - OS keychain via the keyring crate holds the bearer token (Windows Credential Manager / macOS Keychain / Secret Service) Desktop auth — system-browser OAuth, never embedded: - GET /api/auth/login?desktop_flow=<uuid>&desktop_challenge=<sha256> runs the existing GitHub OAuth + App install chain in the browser - callback/setup redirect to dev-ledger://auth?flow&code carrying a single-use hashed code (2-min TTL) bound to a PKCE-style challenge — an intercepted deep link is unexchangeable - POST /api/user?action=desktop&op=exchange mints an opaque dlt_ bearer token bound to a dedicated server-side session (function count stays at 12 via the /api/feedback rewrite precedent) - token rows are hash-only; logout/delete/kill-all revoke through the same auth_sessions machinery; migration 014 adds both tables with RLS-deny and service_role-only access - frontend transport shim sends Authorization: Bearer + the hosted Origin via tauri-plugin-http — no CORS loosening, no cookies Distribution readiness (prepared, nothing submitted): - CI matrix: windows x64 (nsis+msi), macOS arm64+x64 (app+dmg), linux x64 (deb+AppImage); v* tags produce a DRAFT release + SHA256SUMS - distribution/winget manifests, homebrew cask, flatpak manifest + AppStream metainfo + desktop entry - npm run desktop:version keeps package.json the single version source - icons generated from the brand mark (scripts/gen-icon.mjs) Docs: docs/desktop.md covers architecture, auth, builds, signing, notarization, updater activation and all three submission paths. Tests: +21 desktop-auth tests (handoff, PKCE, single-use, revocation, bearer), +17 distribution-invariant tests (identity, versions, HTTPS URLs, least-privilege, no secrets). 725/725 pass, typecheck clean. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
installMode 'perUser' is v1 syntax — v2 uses 'currentUser'. The macOS bundle section no longer accepts 'category' (top-level bundle.category remains valid). Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- tauri::Builder has no on_navigation in Tauri 2 — it lives on WebviewWindowBuilder, so the main window is now constructed in setup() with identical properties and the origin guard attached. - tauri-plugin-deep-link 2.6.2 fails to compile on Linux against rust-ini 0.21.3 (ParseOption gained required fields); pinned to =2.6.1 per plugins-workspace#3710 until a fixed release ships. - Commit Cargo.lock for reproducible desktop builds; cargo check and cargo fmt --check both clean locally now. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
cargo check on Windows masked this; Linux/macOS CI caught it. Verified every other builder method used is ungated. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…I gates
Release-asset contract (verified CI bug — manifest URLs would 404):
- scripts/release-assets.mjs is now the single source of truth mapping
Tauri's "Dev Ledger_*" outputs to canonical release names
(Dev-Ledger_*; dev-ledger_ for the .deb). The CI collect step calls it;
tests import it so filename drift is a test failure.
- Manifest URLs/hashes/dates are now {{TAG}}/{{VERSION}}/{{DATE}}/
{{SHA256*}} placeholders — nothing points at the already-published
v1.3.0 release. ReleaseDate's bogus 2025-01-01 is gone; the version
script stamps the real date during release prep.
Flatpak: the .deb was incorrectly declared extra-data (that type is for
non-redistributable third-party binaries fetched at install time). It is
now a normal pinned `type: file` source unpacked at build time — the
right model for our own AGPL artifact. flathub.json restricts builds to
x86_64 since only an amd64 deb exists.
Keep me signed in: the checkbox is now honored — pendingRemember rides
with pendingFlow/pendingVerifier; exchange stores the token via
storeToken(token, {persistent}). Unchecked = process memory only plus an
explicit keychain_delete so no stale credential resurrects; checked =
OS keychain restore on launch. Server-side expiry/revocation unchanged.
CI hardening: a `verify` job (npm ci --ignore-scripts, build, typecheck,
test, audit) now gates the desktop matrix, and draft-release needs both.
Every checkout sets persist-credentials: false. Release tags also run
the security workflow's full-history gitleaks scan.
External links: SyncMonitor's window.open now routes through the shared
openExternalUrl helper (plugin-opener on desktop, window.open on web);
an invariant test scans src/ for bypasses.
Docs: canonical asset table, v1.3.0-must-not-be-reused release flow,
migration 014 as an explicit production prerequisite, honest
keep-signed-in semantics.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The transport shim classified both /api/* and /rly/* as native hosted routes, so a /rly/* call would carry Authorization: Bearer <desktop token>. /rly/* is the hosted analytics relay — vercel.json rewrites it to eu.i.posthog.com — so a Dev Ledger credential on that path would be handed to a third party. Desktop analytics is disabled, so no normal traffic hits it today; this closes the credential-boundary footgun. - isApiPath now admits only /api/*; /rly/*, external URLs and bundled assets fall through to window.fetch verbatim (no headers added). - apiHeaders() owns the bearer + hosted Origin for the api branch and strips caller-injected Authorization. - test/desktop-transport.test.mjs runs the real shim: proves /api/user and /api/sync qualify, /rly/e/ and /rly/i/v0/e/ do not, external URLs do not, non-api paths pass through untouched, and pins invariants so /rly/ can never be re-admitted to the authenticated transport. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
isApiPath used a raw startsWith('/api/') check, so traversal inputs
like /api/../rly/e/ or /api/%2e%2e/rly/e/ were classified as API paths
while resolving to /rly/e/ on the hosted origin — a desktop bearer
would have been attached to a request whose final path left /api/*.
resolveApiTarget() replaces classification: root-relative inputs only,
resolved through new URL(raw, API_BASE) so the WHATWG parser normalizes
literal, percent-encoded and backslash dot segments, then require the
resolved origin to equal API_BASE and the normalized pathname to begin
/api/. The returned canonical URL is the exact string handed to
tauriFetch — validation and dispatch share one value and cannot drift.
Tests exercise the real shim: literal and percent-encoded traversal
rejection, protocol-relative/absolute/external ineligibility, pass-
through integrity for non-API paths, and source invariants pinning the
canonicalized check and the single tauriFetch call site.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a production-grade Tauri 2 desktop distribution of Dev Ledger for Windows, macOS and Linux — the same React + Vite frontend inside a least-privilege native shell. The hosted Vercel app stays canonical and is completely unchanged for browsers.
src-tauri/—site.devledger.desktop, resizable native window, strict CSP, Rust-side navigation guard, brand icon set, no fs/shell/clipboard permissions.dev-ledger://deep link carries a single-use hashed code bound to a PKCE-style challenge; exchange mints an opaquedlt_bearer token stored hashed server-side and kept in the OS keychain (never localStorage). Revocation rides the existingauth_sessionsmachinery. Serverless function count stays at 12 via the/api/feedbackrewrite precedent (/api/auth/desktop→/api/user?action=desktop).src/desktop/— fetch transport shim (tauri-plugin-http →https://devledger.sitewith Bearer + Origin, no CORS loosening), external-link interception, keyboard shortcuts, deep-link handling.014_desktop_auth.sql—desktop_auth_codes+desktop_tokens, hash-only, RLS-deny + service_role-only.desktop.ymlmatrix: windows x64 (nsis+msi), macOS arm64+x64 (app+dmg), linux x64 (deb+AppImage);v*tags → draft release +SHA256SUMS.txt(nothing auto-publishes).distribution/— WinGet manifest set (NoamanAli.DevLedger, nullsoft/NSIS), Homebrew Caskdev-ledger.rb, Flatpak manifest + AppStream + desktop entry (GNOME runtime for WebKitGTK).npm run desktop:version— package.json stays the single version source across Tauri config, Cargo, and all manifests.docs/desktop.md— architecture, auth, builds, signing/notarization, updater activation, and all three submission paths.Security/privacy implications
core:default,deep-link,opener(https/mailto),http(devledger.site only). No filesystem, shell execution, clipboard, or OS info.desktop-distribution.test.mjsscans the desktop surface + dist bundle).backup-scope.mjs— credentials are never backed up.Test plan
npm test— 725/725 pass (+38 new desktop tests)npm run typecheck— cleannpm run build— cleannpm audit --audit-level=moderate— 0 vulnerabilitiesNothing merges, publishes, or submits to WinGet/Homebrew/Flathub.
Generated with Devin