Skip to content

Multi user platform#5

Merged
Lanlan13-14 merged 10 commits into
mainfrom
multi-user-platform
Jul 19, 2026
Merged

Multi user platform#5
Lanlan13-14 merged 10 commits into
mainfrom
multi-user-platform

Conversation

@Lanlan13-14

Copy link
Copy Markdown
Owner

No description provided.

See added 10 commits July 19, 2026 09:10
- session-store.js: SQLite-backed auth_sessions (SHA-256 SID, sliding idle
  TTL + absolute TTL, remember pair, throttled touch, revoke, gc)
- storage.js: users gain userId/role/status; passkeys & reset codes gain
  userId FK; idempotent identity migration; createUser/getUserById/
  listUsers/updateUserById; saveUsersStore preserves identity
- server.js: sessions survive process restart; WS upgrade binds
  req.authSession once (no Upgrade/connect double-auth race); 5-min WS
  session watchdog closes 4001 on revocation; per-user password reset;
  change-password revokes other sessions; /api/me/sessions list+revoke;
  /healthz exposes instanceId; structured auth error codes
- tests: session-store (11), identity-migration (8), session-restart
  integration (real server reboot ×2, cookie survives)
- authz.js: capability model (discover/view/use/observe/control/execute/
  fileRead/fileWrite/edit/share/delete/revealSecret/administer), tiers,
  effective permission, grants/revokes with audit, enumeration-safe 404
- resource-service.js: owner-aware connection/proxy/sshKey/jumpHost CRUD;
  server-side dependency resolution; secrets stay server-side for `use`
- sharing-service.js: put/list/delete shares with tier expansion
- user-service.js: create/suspend/delete, force-password-reset, last-admin
  protection, resource transfer on delete
- storage: resource_acl + audit_events tables; ownerUserId/visibility on
  connections/proxies/ssh_keys/jump_hosts; ownership migration to first admin
- server routes: connections/proxies/keys/jumpHosts/test/remote-execute/
  rdp credentials & proxy all go through requireUser + ACL; admin user
  management, sharing, bootstrap, audit endpoints
- tests: authz unit (10), user-isolation integration (7); full suite 151 pass
…nk (Stage 3 partial)

- workspace-service.js: (userId,clientId,workspaceId) persistence, optimistic
  concurrency, secret scrubbing, restore re-filters ACL and never auto-replays
- user-settings-service.js: 3-layer merge (system > user override > admin
  default); only allow-listed keys writable; system-only keys blocked for users;
  non-admin never receive other users' apiKey/env values
- notes-service.js: owner/ACL/revision notes with soft-delete, restore, search,
  groups, import/export, 404 enumeration safety, 409 on stale revision
- deeplink-service.js: ssh/telnet/jms parser with port-validation regex
  fallback, encrypted credential blob, user-bound one-time token
- server routes: /api/me/{bootstrap,settings,workspaces}, /api/notes*,
  /api/deeplinks/{prepare,:token,:token/test}, /open landing page; SSH WS
  connect accepts transientToken (atomic consume, never written to assets)
- UI: notes.js controller (edit/split/preview, autosave, conflict surfacing),
  app.html notes workspace + transient connection modal, style.css notes + 980px
  mobile collapse, app.js openConnectionModal/transient/test/save flow,
  terminal.js passes transientToken through connect, open.html/open.js
  hand-off page (fragment-only, login redirect, postMessage to app)
- tests: notes-workspace-unit (3), notes-deeplink integration (5) incl.
  scrubState secret strip, cross-user token rejection, restore inaccessible
  filter, settings key isolation; full suite 159 pass
…tage 4/5 transitional)

Stage 4 - monitor flicker (FREEZE plan §2.3):
- terminal.js renderStats split into ensureStatsSkeleton (build DOM + init
  charts once per open) + renderStats (incremental text-node + chart-data
  updates). No more innerHTML rebuild or Chart teardown on every stats tick.
- hideInfoModal destroys charts and clears the skeleton flag so the next
  open starts fresh (handles disk set / theme / instance change).
- IP copy buttons bound via data-copy-stat events instead of inline onclick.

Stage 5/6 - WTerm viewport (FREEZE plan §3.8 transitional):
- term.viewport public facade (atBottom / followEnabled / state / follow /
  lock / unlock) installed by patchWTermScrollBehavior. Business code can
  migrate off term._scrollToBottom / _isScrolledToBottom; when the @wterm/dom
  fork exposes real public viewport methods only this facade moves.
- Full fork of @wterm/dom with public viewport API + single render commit
  + program-scroll suppression remains a follow-up (requires vendored fork
  + CI); this commit ships the decoupling layer.

tests: monitor-viewport-contract (4) - asserts renderStats no longer
rebuilds innerHTML/initCharts per tick, skeleton owns the build, hideInfoModal
tears down, and the viewport facade exposes a stable API. Full suite 163 pass.
Go worker (zephyr-worker/, performance-critical data plane):
- main.go: WebSocket server, HMAC ticket store (one-shot, TTL), session
  manager with ring-buffer replay, subscriber fan-out, SSH dial w/ proxy+
  jump chain, PTY shell, resize, graceful teardown
- session.go: browser <-> SSH session loop; browser disconnect keeps the
  SSH session alive, reconnect resumes with replay
- upgrader.go: nhooyr.io/websocket Conn adapter
- main_test.go: ring buffer (5), ticket store, session manager, token,
  envelope JSON round-trip - all pass

Node bridge (worker-bridge.js, control plane only):
- issueForConnection / issueForTransient: ACL + secret resolution in Node,
  one-time ticket to Go worker, browser gets ticket+wsUrl (never secrets)
- listSessions / killSession with admin vs user filtering
- server routes: /api/worker/ticket, /api/worker/sessions, /api/worker/
  sessions/:id/kill

Bug fix (pre-existing, surfaced by deeplink test): ssh2 Client emits
'post-ready' error events (e.g. "Connection lost before handshake") after
connectSSHConnection resolves - unhandled 'error' crashed the server.
Added a permanent safety handler that logs and suppresses post-ready errors.

Test infra: TestServer now forwards server stderr to test output and kills
the child on process exit / fatal signals to prevent orphaned server.js
holding the port between runs.

tests: worker-bridge integration (4, mock Go worker), full suite 167 pass
…ge 8)

- ai-policy.js: AiPolicyService with disabled/admin_shared/self_managed/both
  modes, per-user policy override, provider ownership + model whitelist +
  quota, provider sanitization (strips apiKey for non-admin), env var isolation
- ai-agent-service.js: all routes requireUser; chat/tools/run/confirm pass
  ACL-aware ctx (user, authz, resourceService, aiPolicy); every tool that
  touches resources re-checks capability per-call:
    list_connections / list_zephyr_resources -> discover (ACL-filtered)
    open_connection -> use
    remote_execute -> execute (skips denied targets)
    remote_read_file -> fileRead
    remote_write_file -> fileWrite
    connection_create/update/delete -> edit/delete + confirmation
  pendingActions bound to immutable userId; non-admin status never returns
  apiKey (admin sees raw key for management); memory snippets per-user
- server.js: inject authz/resourceService/aiPolicyService/handleServiceError
  into registerAiRoutes deps
- tests: ai-policy integration (5) - provider key isolation, ACL-filtered
  list_connections, resource list filtering, userId-bound confirmation,
  policy mode validation; full suite 172 pass
- ai-agent-service.js: add note_list/note_search/note_get/note_create/
  note_update/note_delete tool definitions (gated by permissions.notes);
  executeAiTool cases route through notesService with ctx.user ACL.
  Search/list return summaries (200 chars) not full content; write ops go
  through confirmation; all activity logged with userId.
- server.js: inject notesService into registerAiRoutes deps.
- tests: ai-notes-tools integration (5) - create/search/get/isolation,
  cross-user B cannot read/update A's notes; full suite 177 pass
…ge 9)

- terminal.html: add "笔记" button to the terminal topbar
- terminal.js: button postMessages open-notes-for-connection to the app
  shell (terminal iframe doesn't own the notes UI/ACL)
- app.js: handle open-notes-for-connection -> switchView('notes') +
  notesController.filterByConnection(connectionId)
- notes.js: state.connectionFilter + loadList sends connectionId param;
  new filterByConnection() method
- Existing notes workspace UI (Stage 3) already covers the three-pane
  desktop layout, edit/split/preview, Markdown toolbar, autosave, mobile
  collapse at 980px; this commit adds the terminal-to-notes hand-off
  and "current connection" filtering required by §6.4 / §9.
Go worker:
- telnet.go: minimal IAC negotiation (IAC SB TTYPE + NAWS on window resize),
  TCP dial, bidirectional forwarding to the WebSocket, sanitize Telnet
  control bytes so the browser only receives printable data + CRLF
- session.go: protocol TELNET routes to serveTelnetWS instead of SSH
- No fake proxy/replay/tmux-emulation fallback (plan §25); Telnet stays a
  first-class transport

Node server:
- deeplink-service.js: remove telnetUnsupported marker (Telnet is live)
- server.js: /api/deeplinks/:token/test does TCP reachability probe for
  TELNET (minimal IAC DO TTYPE) instead of hard-rejecting; SSH WS connect
  points Telnet to the worker ticket path; testConnection branch handles
  TELNET
- public/app.js: remove transient Telnet block; default port 23 for TELNET
  in form defaults and test payload
- Tests: full suite 177 pass; Go worker unit tests pass
…I + migration & CI (Stage 5/6/14)

Stage 5/6 - WTerm fork (vendoring approach, no external repo needed):
- public/vendor/wterm-fork/: vendored @wterm/dom 0.1.9 sources (wterm.js,
  renderer.js, input.js, debug.js) + @wterm/core (wasm-bridge, transport,
  wasm-inline) organized under core/; all imports rewritten to relative
  paths so the fork loads in the browser without a bundler
- wterm.js: expose public viewport API - get viewport() (atBottom,
  maxScroll, scrollTop, rowHeight, rows, cols, scrollToBottom, follow,
  lock) + isAtBottom()/scrollToBottom()/followBottom()/lockBottom() aliases
- terminal.js: prefers /vendor/wterm-fork/index.js, falls back to stock
  @wterm/dom; patchWTermScrollBehavior has a fork path that bridges the
  fork's public viewport API to the legacy term.viewport facade without
  monkey-patching private methods
- tests: wterm-fork-contract (5) - public API surface, no bare @wterm/core
  specifier, fork-before-stock loader, fork path doesn't touch privates

Stage 14 - migration completeness + CI:
- .github/workflows/ci.yml: add zephyr-worker build + vet + test job
- tests/stage14-migration (7): bootstrap payload, auth/me identity,
  all v1 API surfaces respond (no 5xx), three-layer settings merge,
  workspace restore ACL filter + autoReplay=false, AI policy status,
  healthz instanceId

Full suite 189 pass
@Lanlan13-14
Lanlan13-14 merged commit 2f92de5 into main Jul 19, 2026
1 check failed
@Lanlan13-14
Lanlan13-14 deleted the multi-user-platform branch July 19, 2026 04:59
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