Skip to content

Sign-in screens, TLS certificate flow and client tokens - #23

Draft
Luxxy-GF wants to merge 23 commits into
lxc:mainfrom
Luxxy-GF:incus-auth
Draft

Luxxy-GF wants to merge 23 commits into
lxc:mainfrom
Luxxy-GF:incus-auth

Conversation

@Luxxy-GF

@Luxxy-GF Luxxy-GF commented Nov 5, 2026 •

Copy link
Copy Markdown

Closes #1

Summary

Signed-out users are sent to a sign-in screen that offers the methods the server advertises, plus "Sign in with Incus CLI" (incus webui) on Linux and macOS.

  • Honors user.ui.auth.offer_cli and auto_init_oidc (with the user.ui.sso_only alias). OIDC hands over to /oidc/login.
  • TLS client certificates: generate a certificate and key in the browser (.crt, .key, and a .pfx that macOS Keychain imports), enroll it with a trust token or manually, or use one already in the browser.
  • Client tokens: sign in with a certificate-signed JWT through /tls/login. When a token session ends, a dialog says whether it expired and offers a new token or another method.
  • Sign-out lives in a sidebar user menu and explains when a browser certificate or incus webui keeps the session trusted.
  • node-forge (BSD-3-Clause) is loaded only on the certificate generation screen.

Notes for review

Testing

  • npm run typecheck, npm run lint, npm run test:dev (58/58), and npm run test:incus (292/292) pass.
  • Not yet run against a live Incus server. The sign-in, certificate, and token flows need a manual pass.

Luxxy-GF and others added 21 commits November 5, 2026 16:28
Serve the Vite app (/ui) and the Incus API from one origin, modeled on the
Hye Ararat devProxy.ts but running on Node with native PFX client
certificate support and no extra dependencies.

It mirrors the original DEV_PROXY_* settings (API target, upstream client
PFX and passphrase, listener mode, cross-origin isolation) and adds
DEV_PROXY_PORT, DEV_PROXY_HOST and DEV_PROXY_APP_TARGET. HTTP bodies are
streamed, same-host redirects are rewritten onto the incoming origin, and
WebSocket upgrades (Vite HMR and /1.0/events) are piped over raw sockets.

Signed-off-by: Luxxy-GF <68338577+Luxxy-GF@users.noreply.github.com>
Events layer: one shared, session-scoped connection to /1.0/events with
lifecycle and operation events on a core socket and logging on its own
socket, so a restricted session keeps core delivery. Reconnects use
bounded backoff with jitter and report interruption and recovery, without
implying that consumers have caught up. Payloads stay in memory.

Project switching: a header project selector backed by a shared selection
context. It defaults to All Projects, which is distinct from the "default"
project, and builds project/all-projects query parameters per endpoint.

Data layer core: one in-memory observation store per deployment and
session, holding resource existence, field groups and collection
membership with completeness, independently of the request that revealed
them. Freshness follows event coverage: an interruption makes facts need
validation and a restored connection alone does not make them fresh.
Requirements raised in the same tick are planned together, equivalent
requests are shared, and events received while a request is in flight are
replayed on top of its response so older reads cannot undo them.

Projects and instances resource definitions expose consumer hooks with
typed field selection (useProjects, useProject, useInstances,
useInstance) plus imperative APIs on the same machinery. Lifecycle events
change the store directly: creation, deletion, rename and instance state
changes need no refetch, and updates revalidate only the affected
resource. Instance queries resolve the project from the shared selection
unless one is given, filter by status on the server or evaluate it locally
from complete broader results, and stay reactive as instances enter or
leave a result.

Also adds pop-up notifications for operations and resource changes, an
events debug panel and an instance list on the instances page.

Refs: lxc#6, lxc#10, lxc#11, lxc#13
Signed-off-by: Luxxy-GF <68338577+Luxxy-GF@users.noreply.github.com>
Shorten multi-line doc comments to one line and drop long design notes.
No code changes.

Signed-off-by: Luxxy-GF <68338577+Luxxy-GF@users.noreply.github.com>
Persistence: observations are written back to one IndexedDB database per
deployment and session through a coalescing, bounded queue that never
delays consumers. Resource definitions supply explicit allowlists; a
group is persisted whole or not at all, so config with unclassified keys
(e.g. cloud-init data) and sampled runtime state stay in memory. Restored
facts are usable but never fresh, never overwrite newer in-memory
observations, and a collection is restored as complete only if every
member record was stored. Storage failures leave the in-memory layer
untouched.

Tabs of the same session share observations, deletions and invalidations
over a BroadcastChannel. Merges compare observation times rather than
arrival order, same-time conflicts require validation, nothing is echoed
back, and remote facts are fresh only under continuous local coverage.

Commands: the transport now supports methods, bodies, If-Match, ETags and
async (202) responses, and maps 412 to a conflict. Instances and projects
expose edit/save based on the writable baseline and its ETag, plus
instance state actions and delete, and project create, delete and
rename. Affected data is invalidated centrally; a connection failure
after sending is reported as an unknown outcome and never replayed.

Operations are registered in a shared registry before the handle is
returned, merge events that arrive early, follow events or long-poll
/wait without coverage, distinguish lost monitoring from failure, support
wait and cancel, and revalidate affected data on completion.

Capabilities (lxc#7) are read from api_extensions: without server filtering
queries filter locally, without all-projects support the result is an
explicit unsupported error, and without recursion=2 runtime state uses
per-instance /state requests.

Instances gain config, devices, expanded config and sampled runtime
state. Profile changes invalidate the expanded config of dependent
instances, including through default-project profile inheritance.
Project used_by establishes instance existence and relationships.

Logout wipes every Incus UI database and in-memory state in all tabs,
guarded by a generation outside IndexedDB, and reports blocked deletion.
It runs when the session stops being trusted.

Also fixes isFetching consulting requests of other resource definitions.

Refs: lxc#1, lxc#6, lxc#7, lxc#13
Signed-off-by: Luxxy-GF <68338577+Luxxy-GF@users.noreply.github.com>
Consumers now recompute only when a store change touches a key they
read: the store records record, collection, relation and type reads
during each computation, and request, error and coverage changes still
reach every consumer. Results are compared and shared structurally
instead of serialised, so unchanged rows keep their identity and are not
re-rendered. On 1,000 instances with 11 watched lists, applying 300
lifecycle events drops from about 3.4s to 1.2s.

Prefetching activates a requirement ahead of its consumer and holds it
for a while; a consumer that mounts meanwhile shares the request. The
project selector prefetches a project's instance list on hover or focus.

Request planning fixes from review:
- Individual GETs keep the needs they were planned for, so resolving an
  undecided member reports fetching and records failures with backoff
  instead of retrying immediately. Equal requests already in flight adopt
  the new needs. Applies to instances and projects.
- Explicit refreshes reach the planners, which revalidate even when the
  store looks fresh.
- Events journaled during a request replay with the validation they had
  on arrival, so a reconnect cannot make pre-gap facts fresh.
- Without event coverage, watched facts are re-checked periodically so
  expired ones are reported stale and revalidated.

Loading states: collectionView and resourceView decide between skeleton,
data, empty, error and absent, preferring any known data over a
skeleton. Reusable table and list skeletons fade in after a short delay
so quick loads never flash; the instance list and project selector use
them.

Also caches persistence policy lookups, clears retry timers on dispose,
and falls back to getRandomValues for tab ids where crypto.randomUUID is
unavailable (plain HTTP).

Signed-off-by: Luxxy-GF <68338577+Luxxy-GF@users.noreply.github.com>
sonner is not recommended for shadcn's Base UI components. Event toasts
now use a shared toast manager rendered through a Base UI toast viewport.

Signed-off-by: Luxxy-GF <68338577+Luxxy-GF@users.noreply.github.com>
The proxy reads the Incus CLI configuration and proxies to its default
remote, or to the one named by --remote or INCUS_REMOTE. TLS remotes use
the CLI client certificate and pinned server certificate, local remotes
use the unix socket. npm run dev now starts the proxy, which spawns Vite.

Signed-off-by: Luxxy-GF <68338577+Luxxy-GF@users.noreply.github.com>
Signed-off-by: Luxxy-GF <68338577+Luxxy-GF@users.noreply.github.com>
Devtools now only render in development builds.

Signed-off-by: Luxxy-GF <68338577+Luxxy-GF@users.noreply.github.com>
Signed-off-by: Luxxy-GF <68338577+Luxxy-GF@users.noreply.github.com>
Signed-off-by: Joseph Maldjian <joseph.maldjian@hyecompany.com>
Signed-off-by: Joseph Maldjian <joseph.maldjian@hyecompany.com>
Signed-off-by: Joseph Maldjian <joseph.maldjian@hyecompany.com>
Signed-off-by: Joseph Maldjian <joseph.maldjian@hyecompany.com>
Extend the Incus client with any HTTP method, JSON bodies and extra
headers, a single retry on 305 (incus webui OIDC refresh), async
envelopes, and listeners for 401/403 so the session is re-checked when a
request is refused.

Read the server's auth methods and user.ui.* settings from GET /1.0,
which Incus exposes before authentication, and derive the sign-in screen
options (offer_cli, auto_init_oidc and the legacy sso_only alias). Add
certificate enrollment with a trust token, small persisted sign-in hints
(no credentials), and OS/browser detection.

Refs: lxc#1, lxc#8, lxc#9
Signed-off-by: Luxxy-GF <68338577+Luxxy-GF@users.noreply.github.com>
Redirect signed-out users to a sign-in screen that offers the methods the
server advertises, plus "Sign in with Incus CLI" (incus webui) on Linux
and macOS. It honours user.ui.auth.offer_cli and auto_init_oidc (with the
user.ui.sso_only alias) and skips itself when only one method remains.
OIDC hands over to /oidc/login.

TLS client certificates: generate a certificate and key in the browser
(.crt, .key, and a 3DES/SHA-1 MAC .pfx that macOS Keychain imports),
enroll it with a trust token or manually, or use one already in the
browser. Then sign in through the CLI (incus remote add --tls-cert ...,
incus webui) or follow import steps for the detected OS and browser,
resuming after a browser restart.

Client tokens: paste a certificate-signed JWT to sign in through
/tls/login; /tls/logout signs out. When a token session ends, a dialog
explains whether it expired and offers a new token or another method.

Sign-out lives in a sidebar user menu and explains when a browser
certificate or incus webui keeps the session trusted. node-forge is used
under its BSD-3-Clause option and is loaded only on the generate screen.

Refs: lxc#1, lxc#3, lxc#4
Signed-off-by: Luxxy-GF <68338577+Luxxy-GF@users.noreply.github.com>
The generated route tree was committed without the /auth/cli and
/auth/tls/* routes, which broke type checking.

Signed-off-by: Luxxy-GF <68338577+Luxxy-GF@users.noreply.github.com>
Keep only icons that carry meaning (loading, copy, back, disclosure, the
sidebar user menu). Option lists, notices and the certificate downloads
are now text-only.

Signed-off-by: Luxxy-GF <68338577+Luxxy-GF@users.noreply.github.com>
@Luxxy-GF
Luxxy-GF requested a review from Hye-Dev as a code owner November 5, 2026 16:37
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown

✅ REUSE Specification compliance check passed

Differential licenses

The following non-Apache-2.0 licenses occur in files covered by this check:

  • MIT: src/components/ui/toast.tsx

License texts already present before this PR: MIT.

@Hye-Dev
Hye-Dev marked this pull request as draft November 5, 2026 19:40
@Hye-Dev

Hye-Dev commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator

Draft until #22 is finalized. This PR will change depending on what happens there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Authentication

2 participants