Commit Graph
64 Commits
Author SHA1 Message Date
mrhid6 dde47de145 feat: auth provider REST API and public provider discovery 2026-08-03 10:51:35 +01:00
mrhid6 f1c3f67864 feat: per-provider SSO start and callback routes 2026-08-03 10:45:23 +01:00
mrhid6 e2b01b62a5 feat: add local_login_enabled setting with absent-means-on default 2026-08-03 10:36:48 +01:00
mrhid6 01e8b0ba44 feat: Better debugging for console
Chart Release / chart (push) Successful in 11s
Server Deploy / deploy (push) Successful in 1m25s
2026-07-31 16:31:19 +01:00
mrhid6 2aa4784518 feat: Better debugging for console
Chart Release / chart (push) Successful in 18s
Server Deploy / deploy (push) Successful in 59s
2026-07-31 16:13:51 +01:00
mrhid6 36995fa62b fix: Fixed install and update scripts
Chart Release / chart (push) Successful in 9s
Server Deploy / deploy (push) Successful in 1m20s
2026-07-31 12:10:32 +01:00
mrhid6 165114471f fix: Fixes to running on kubernetes
Chart Release / chart (push) Failing after 13s
Server Deploy / deploy (push) Successful in 6m35s
2026-07-31 10:34:10 +01:00
mrhid6 1fe608f531 fix: bound and complete console relay teardown, restore proxy_failed audit
- Arm the unclaimed-relay watchdog in NewSession rather than Serve, so an
  agent that never opens its ProxyStream is bounded to 10s and reports
  reason "agent_timeout", per the design spec's failure-mode table.
- Session.Close now also closes the accepted net.Conn (stored via setConn),
  so ConsoleProxy.Close() is an unconditional kill of the whole relay chain
  instead of only closing an already-idle listener.
- Emit console.proxy_failed and end the console session from a defer in
  consoleTunnel guarded on relay.Reason(), since guac's OnDisconnect never
  runs when the connect callback errors -- which is the path every relay
  failure this feature introduces takes. Update the two docsite
  troubleshooting rows to match what the audit event can now actually show.
2026-07-31 09:21:07 +01:00
mrhid6 05f10ed3c9 feat: record relay proxy_id and port in console audit events 2026-07-29 13:10:58 +01:00
mrhid6 c0bec3737b feat: route every console session through the agent relay 2026-07-29 13:07:30 +01:00
mrhid6 bc6c7cdb5a feat: Updated docker file
Server Deploy / deploy (push) Successful in 2m35s
2026-07-28 16:04:51 +01:00
mrhid6 31e0000306 feat: default steps are read only
Server Deploy / deploy (push) Successful in 1m46s
2026-07-28 13:34:08 +01:00
mrhid6 dabe6fe3aa feat: Updated package path to match repo
Agent Release / build (push) Failing after 25s
Agent Release / msi (push) Skipped
Server Deploy / deploy (push) Successful in 2m47s
2026-07-28 10:01:40 +01:00
mrhid6andClaude Opus 5 c4e6ad5485 feat(license): metered licensing — catalogue, entitlements, and enforcement
Server Deploy / deploy (push) Successful in 5m22s
Implements spec 7 tasks 2-10 on top of the six-plan payload from task 1.

Admin: plans re-keyed on (deployment, tier); new catalogue collection holds
every Paddle price ID (one row per priceable component); new entitlements
collection holds desired beside granted. admin/internal/catalogue owns both
folds — entitlement to licence limits, and entitlement to Paddle line items —
so the base allowance is subtracted in exactly one place. licensing.Issue now
snapshots the instance's granted entitlement, never desired. Free is enforced
per account AND deployment. Staff endpoints for plans, catalogue and
entitlements; Free self-hosted can be claimed and renewed on its annual term;
the reaper stays cloud-only.

Server: enforces the monitor cap, audit-log retention (daily sweep, skips
Unlimited and lapsed instances), and gates the OIDC callback. Unset limits are
filled from the seed plan at the single decode site so old blobs never read as
zero.

Frontends: adminsite gains a catalogue price-ID editor, six-plan allowance
screen, and a catalogue-driven PlanConfigurator mounted on the staff instance
page. web shows monitors, audit retention and support level on the licence page.

Docs: CLAUDE.md, spec index and plan 5 preamble updated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 09:37:39 +01:00
mrhid6 dd306e4757 feat(license): the paste form is self-hosted only
Server Deploy / deploy (push) Successful in 2m14s
A cloud instance's licence is issued in HQ and written into the control
plane by admin/internal/inject, straight to the database. The customer has
nothing to paste, so /settings/license offered them a form that could only
ever fail — and on an expired cloud instance, failed at the exact moment
they were looking for a way out.

GET /license now reports `deployment`, and the page swaps the paste form
for a short panel saying where the licence comes from, with a link to the
portal when HQ_URL is set. That is the same treatment hq-managed members
already get in the members table: read-only here, and a pointer to where
it is actually managed.

POST /license refuses with 409 cloud_managed on a cloud deployment. Hiding
a form is a courtesy; this codebase's rule is that the API is the boundary,
and the endpoint was reachable regardless of what the page rendered. It
cannot break injection, which never goes through HTTP at all.

Verified: server builds and vets clean, web builds clean.
2026-07-26 20:42:53 +01:00
mrhid6andClaude Opus 5 a05a74cf4d feat(server): refuse local edits to hq-sourced users
The API is the boundary; hiding the control in web/ is a courtesy. A role
editable in two places is a role with two answers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 16:31:27 +01:00
mrhid6 e8cc9b4ecd fix: Fixed misleading error message when license on cloud 2026-07-26 15:16:02 +01:00
mrhid6 120c9c6735 feat(server): licence API, mutation gate and feature gates
RequireActiveLicense is mounted on the /api group so new routes are gated by
where they live. GET /api/servers/new is named explicitly: it mints a
pre-registration token, so it mutates despite the method.
2026-07-24 15:13:35 +01:00
mrhid6 c3e363eccc refactor(server): finish the Org to Instance rename
Private identifiers plan 0b's naming map missed, plus the OrgOIDC model type.
No wire format, database field or route changes.
2026-07-24 15:07:06 +01:00
mrhid6 4f041d2f4b refactor(server): rename Org to Instance
Adds migration 0004_org_to_instance, the ScopedCollections list, the
AssertNoScopedCollectionMissed boot check, and moves EnsureAuthIndexes into
its own file.

Two ordering constraints the rename exposed, both now enforced and commented:

- 0004 must run BEFORE EnsureAuthIndexes. The index builder creates
  instances.slug, which would create an empty instances collection and make
  0004 refuse to rename orgs onto an existing target.
- Migrations 0001 to 0003 run BEFORE 0004 and still read and write org_id, so
  they use a private legacyOrg struct rather than shared/models.
2026-07-24 13:58:41 +01:00
mrhid6 1a6cf03c03 feat: Removed comments
Server Deploy / deploy (push) Failing after 1m59s
2026-07-24 09:51:30 +01:00
mrhid6 9ffae221ac fix: Fixed server install scripts
Server Deploy / deploy (push) Successful in 59s
2026-07-22 12:59:05 +01:00
mrhid6 77a92787fb fix: repair migration collection names and cross-cutting scoping gaps
Findings from the final whole-branch review.

- scopedCollections named "audit" and "channels", but the code writes to
  audit_logs and notification_channels. On upgrade from single-tenant,
  legacy audit events and channels would never get org_id, becoming
  invisible to org-filtered reads while channels silently stopped firing
  — and the detection loop counted the wrong names, so the 0001 marker
  could be written having migrated nothing. Names fixed, plus migration
  0003 so an incorrectly-migrated instance converges with a fresh one.
- EnsureAuthIndexes failure is now fatal. GetUserByEmail is unscoped and
  the OIDC cross-org guard compares against whichever duplicate Mongo
  returns first, so users.email uniqueness is a security invariant, and a
  legacy collection with duplicate emails is the realistic upgrade case.
- Evict the per-org OIDC provider cache on save; rotating away from a
  compromised IdP previously had no effect until restart.
- Build the oauth2 config per request instead of mutating a shared cached
  pointer outside the mutex, which raced on RedirectURL between
  concurrent logins for the same org.
- Stamp org_id on console_sessions, incidents and monitor_rollups, the
  last collections with no tenant column. 0003 derives their org from the
  owning server/monitor rather than defaulting, so one org's console
  history and incident timeline cannot merge into another's.
- Seed default steps when an org is created, not only at boot.
- Reject an empty session OrgID at the middleware.
- Derive the app root label from APP_ROOT_LABEL instead of hardcoding
  "vantage", which silently disabled the host guard off that domain.
- Stop caching negative slug lookups, so a new org's subdomain resolves
  immediately.
2026-07-22 10:44:17 +01:00
mrhid6 aa31cd8a10 fix: validate roles, guard last owner, scope bootstrap status
Security review of e70b2f0. The UI gating was correctly backed by
RequireRole everywhere; these are the missing validation gaps behind it.

- UpdateUserRole and createOrgUser accepted any role string verbatim, so
  an admin could self-promote to owner, create an owner outright, or set
  a junk role that silently stripped a user's access. Roles are now
  whitelisted, only an owner may grant or remove the owner role, and an
  actor cannot change their own.
- Neither demote nor delete guarded the last owner, so an org could reach
  zero owners. Both now refuse when no owner would remain, returning 409.
  Self-delete rejected.
- CountUsers counted across all orgs, so a locked-out org could never
  re-bootstrap once another tenant existed, and the unauthenticated
  bootstrap-status endpoint reported instance-wide state. It now answers
  per-org on an org host, falling back to global only on the apex.
- HandleMe repeats the middleware's host/org check; it sits outside the
  middleware so it can still return its own 401.
- Post-bootstrap now sends the new owner to their org host's login page.
  The session cookie is deliberately scoped to the exact host, so the old
  redirect landed them unauthenticated.
- AuthProvider renders an error state instead of mounting the shell with
  a null user when /auth/me fails for a reason other than 401.
- api.ts unwraps {"error": ...} so these messages render as text.
2026-07-22 10:26:21 +01:00
mrhid6 e70b2f0e67 feat(web): login, first-run setup, org settings; org-aware AuthProvider
- Route group (app) holds AuthProvider + Sidebar, so /login and /setup
  render without app chrome and never mount the provider.
- AuthProvider drops the removed auth_enabled flag and exposes
  {user, org, isAdmin}.
- New login page (password + SSO), first-run setup page, and org settings
  page with a members table and the OIDC provider form.
- Settings page and the Organization nav entry are gated on role, since
  /api/settings now 403s for members.
- GET /api/org/oidc gains client_secret_set so the UI can show whether a
  secret is stored; the secret itself is still never serialized, and an
  empty submitted value still means "keep the stored one".
- Fix logout: the sidebar linked to /auth/logout with a GET, but the route
  is POST-only, so logout was 404ing.
2026-07-22 10:17:12 +01:00
mrhid6 4f512d01f1 fix(server): harden per-org settings migration and sweeps
Review follow-ups on e5363a6:

- MigrateSettingsOrg no longer guesses via the "default" slug. One org
  means stamp that org; zero orgs means synthesise Default; more than
  one means leave it alone and log, since guessing would hand one org
  another's SMTP config and ESO token.
- EnsureSettingsIndexes failure is now fatal. Without the unique index
  on org_id, GetSettings returns an arbitrary duplicate; without the one
  on the token hash, ResolveSecretsReadToken picks an arbitrary org.
- Name the token-hash index explicitly so it stops colliding with the
  legacy name DropOne targets, and exclude the empty string from the
  partial filter.
- Log retention: distinguish a missing run doc from a Mongo error, so a
  transient failure skips the directory rather than purging it at the
  30-day default.
- Offline sweep: fresh context per org, log-and-continue on a per-org
  error, plus a final pass for servers whose org no longer exists.
- ESO handler 401s on an empty token-derived org rather than querying
  org_id "".
2026-07-22 09:41:51 +01:00
mrhid6 e5363a64ee fix(server): per-org settings and ESO read token
The settings collection was a single global document, so every org
shared one SMTP config, alert config, retention policy and ESO read
token. GetSecretGroupDecryptedAny then flattened every org's secrets
for a group into one map, meaning any tenant's token read every
tenant's secrets.

- settings gains org_id; GetSettings/SaveSettings/RotateSecretsReadToken/
  GetWorkflowLogRetentionDays all take orgID
- VerifySecretsReadToken replaced by ResolveSecretsReadToken, which
  resolves the org from the presented token's hash; the ESO endpoint
  derives its org from the token rather than a session, since it is
  called machine-to-machine
- GetSecretGroupDecryptedAny deleted in favour of the org-scoped variant
- settings and token-rotation routes now require owner/admin
- offline sweep and log retention resolve org per server / per run
- migration 0002 stamps the legacy settings doc with the default org

Note: /api/settings now 403s for members; the web settings page needs a
matching role check.
2026-07-22 09:35:47 +01:00
mrhid6 dff3668a25 fix(server): validate cross-org resource ownership
Review of the org-scoping pass found that org_id on a query filter
protects the row you look up, but does nothing when a handler accepts a
foreign resource ID as data and a downstream unscoped query consumes it.

- AssignKey: verify key and server both belong to the org
- BuildAuthorizedKeys: resolve server first, scope assignments and keys
  to that server's org (was honouring foreign assignment rows)
- Workflows: validate TargetServerIDs on create/update and re-check at
  trigger time
- Monitor incidents/uptime handlers: gate on org-scoped GetMonitor
- GetChannels: take orgID; validate channel_ids on monitor create/update
- Secret and default-step unique indexes: scope to org_id so a second
  org no longer hits E11000
- DeleteServer/DeleteMonitor: scope cascading deletes
2026-07-22 09:28:43 +01:00
mrhid6 850aa0ed05 feat(server): org-scope service layer + handlers + org admin API
Threads org_id through every admin-facing service function (servers, keys,
assignments, secrets, workflows/steps/runs, monitors, channels, audit),
adds RequireRole middleware, and wires /api/org user + OIDC management
routes. Agent/scheduler paths keep unique-key signatures and resolve org
from the loaded record; internal-only helpers (getServerByID,
getRunByID, getMonitorByID) preserve those call sites.
2026-07-21 16:56:39 +01:00
mrhid6 d0ed9885e7 feat(auth): per-org OIDC resolver replaces global provider 2026-07-21 16:41:50 +01:00
mrhid6 8f3a27100f feat(server): multi-channel monitor notifications
Server Deploy / deploy (push) Successful in 1m16s
2026-07-21 14:22:55 +01:00
mrhid6 e413009faa feat(server): server-run monitor scheduler + REST API
Server Deploy / deploy (push) Successful in 58s
2026-07-21 14:13:26 +01:00
mrhid6 93423e32e6 feat(server): step usage counts endpoint 2026-07-21 11:31:05 +01:00
mrhid6 7c4a676742 fix: inline step secrets + inline input/output display + import body limit 2026-07-21 10:40:45 +01:00
mrhid6 15c9da1b01 feat(server): default steps seed-on-boot + admin re-sync 2026-07-21 10:25:41 +01:00
mrhid6 baa7bb239d feat(server): step import/export/parse endpoints 2026-07-21 10:22:35 +01:00
mrhid6 63dadf6239 feat(api): server-run log fetch and SSE stream endpoints 2026-07-20 15:18:35 +01:00
mrhid6 85e1baf59a feat(server): workflow log-writer registry, retention setting, sweeper 2026-07-20 15:18:35 +01:00
mrhid6 f141767fc2 feat(workflows): inject step inputs into env; update returns workflow 2026-07-20 14:37:36 +01:00
mrhid6 ff3a94b888 fix(api): audit workflow run cancellation 2026-07-20 11:42:31 +01:00
mrhid6 631894084a feat(api): workflow, step, and run REST endpoints 2026-07-20 11:40:36 +01:00
mrhid6 d31486ae1b fix: Fixed agent windows version
Server Deploy / deploy (push) Successful in 1m28s
Agent Release / build (push) Successful in 10m34s
Agent Release / msi (push) Successful in 57s
2026-07-17 15:38:00 +01:00
mrhid6 2b4611f7ac feat: added windows install script to ui
Server Deploy / deploy (push) Successful in 1m20s
2026-07-17 14:40:00 +01:00
mrhid6 1989d6cd98 fix: Fixed console width
Server Deploy / deploy (push) Successful in 1m17s
2026-07-17 13:46:12 +01:00
mrhid6 55bae898f3 fix compile error
Server Deploy / deploy (push) Successful in 1m34s
2026-07-17 12:29:04 +01:00
mrhid6 a9d602d021 feat: harden console sessions + complete protocol support
Server Deploy / deploy (push) Failing after 1m9s
Agent Release / build (push) Successful in 1m52s
Agent Release / msi (push) Failing after 52s
- single-use session tokens (atomic ConsumeSessionToken) + user-bound tunnel (actor must match session opener)
- wire VNC end-to-end (stash/consume password, connect+tunnel, frontend password field)
- passphrase-protected SSH keys: passphrase_enc on Key model, capture on upload, decrypt + pass to guacd
2026-07-17 12:19:07 +01:00
mrhid6 1ad5b5d6db fix: send ssh username to guacd (default root) for console SSH 2026-07-17 11:54:33 +01:00
mrhid6 c26f120e42 feat: dynamic windows install.ps1 endpoint 2026-07-17 11:39:54 +01:00
mrhid6 332c7760ca fix: keep RDP creds out of tunnel URL/logs via single-use encrypted stash 2026-07-17 11:28:00 +01:00
mrhid6 138f708a87 feat: console connect + guacd websocket tunnel endpoints 2026-07-17 11:24:24 +01:00