Commit Graph

59 Commits

Author SHA1 Message Date
Vantz Stockwell
46e2cb6e2f debug: add SFTP message logging to diagnose empty directory listing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 02:01:33 -04:00
Vantz Stockwell
9e30e5915f fix: SFTP sidebar empty on load — list sent before WebSocket open
The list('/') call fired immediately after connect(), but the
WebSocket was still in CONNECTING state so send() silently dropped
the message. Now buffers the initial list request and sends it
in the onopen callback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 01:56:59 -04:00
Vantz Stockwell
aa457b54d4 fix: infinite remount loop — use stable key for session components
When replaceSession changed the session ID from pending-XXX to a
real UUID, Vue's :key="session.id" treated it as a new element,
destroyed and recreated TerminalInstance, which called connectToHost
again, got another UUID, replaced again — infinite loop.

Added a stable `key` field to sessions that never changes after
creation, used as the Vue :key instead of the mutable `id`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 01:51:31 -04:00
Vantz Stockwell
4ccf138744 fix: seed script checks for any existing user, not just admin@wraith.local
Previously the seed only checked for admin@wraith.local by email,
so it would create a duplicate if the admin had changed their email.
Now skips seeding entirely if any user exists.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 01:48:19 -04:00
Vantz Stockwell
2216ee79aa fix: close WebSocket and auto-remove session on SSH connect failure
When SSH connection fails, close the WebSocket immediately and
auto-remove the pending session after 3 seconds so the user sees
the error message before the panel clears. Prevents stuck sessions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 01:32:48 -04:00
Vantz Stockwell
6262ab6e7e debug: verify ssh2 key parsing and log derived public key
Uses ssh2 utils.parseKey() to check if the key decrypts and
parses correctly, logs the key type and public key fingerprint.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 01:13:51 -04:00
Vantz Stockwell
36c8527c28 debug: add SSH auth diagnostic logging
Logs key format, length, auth method selection, and ssh2 debug
output for auth/key events to diagnose why key auth is rejected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 01:08:54 -04:00
Vantz Stockwell
11e1705110 fix: detect orphaned SSH key references and missing auth methods
When a credential's sshKeyId points to a deleted/missing SSH key row,
the connection attempt silently had zero auth methods. Now throws a
clear error explaining the SSH key is missing. Also catches the case
where a credential has neither password nor SSH key configured.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 01:04:41 -04:00
Vantz Stockwell
f06dcbaa6b chore: expose postgres on port 4211 for external access
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 00:55:35 -04:00
Vantz Stockwell
639ac329a8 fix: TDZ crash on SSH connect failure — sessionId referenced before assignment
When SSH timed out, the onClose callback referenced `const sessionId`
before connect() resolved, causing a Temporal Dead Zone ReferenceError
that killed the process. Changed to `let` with try/catch so connection
failures send an error message to the client instead of crashing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 00:50:35 -04:00
Vantz Stockwell
8d95fc5652 fix: add crash handlers to catch process-killing exceptions
Process-level uncaughtException/unhandledRejection handlers plus
try/catch around upgrade and connection handlers. This will log
whatever is crashing the server on browser WebSocket connections
before the process dies, instead of silently restarting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 00:36:44 -04:00
Vantz Stockwell
8207b78b36 fix: hijack upgrade event before WsAdapter can consume the socket
WsAdapter registered its upgrade handler first and destroyed sockets
for non-matching paths. Now we remove all existing upgrade listeners,
install ours first, and forward non-terminal/sftp upgrades to the
original WsAdapter handlers for RDP.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 23:28:54 -04:00
Vantz Stockwell
55b944bfc5 fix: restore WsAdapter for RdpModule gateway, manual handler coexists
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 23:21:48 -04:00
Vantz Stockwell
734ab5633e fix: bypass NestJS WsAdapter — manual WebSocket upgrade handling
The NestJS WsAdapter silently swallowed WebSocket connections through
NPM despite 101 responses in the access log. Replaced with manual
ws.Server instances using noServer mode and explicit HTTP upgrade
event handling. Gateways are now plain @Injectable services, not
@WebSocketGateway decorators.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 23:12:01 -04:00
Vantz Stockwell
10f3537b01 fix: move WebSocket paths under /api/ prefix to work through NPM proxy
NPM forwards /api/* correctly but silently drops WebSocket upgrades on
/ws/* despite toggle being enabled and custom nginx config. Moving
gateways to /api/ws/terminal and /api/ws/sftp so they ride the same
proxy rules that already work for REST endpoints.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 15:13:45 -04:00
Vantz Stockwell
9dc5938fa6 debug: add HTTP upgrade event listener to diagnose WebSocket routing
handleConnection never fires despite browser getting open event.
Adding server-level upgrade listener to see if upgrades reach NestJS.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 14:34:19 -04:00
Vantz Stockwell
77a76262f5 debug: add verbose WebSocket logging to terminal gateway
Need to see if handleConnection is even being called.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 14:27:43 -04:00
Vantz Stockwell
c4d7ad1833 fix: WebSocket auth always fails — client object has no URL property
With the NestJS ws adapter, the JWT token URL is on the HTTP upgrade
request (second arg to handleConnection), not on the WebSocket client
object. client.url was undefined, new URL(undefined) threw, catch
returned null, and every connection got 4001 Unauthorized.

Fix: Pass the IncomingMessage req to validateClient and prefer req.url.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 11:16:03 -04:00
Vantz Stockwell
d74bb28960 fix: terminal never appears — pending session removed before WS connects
Root cause: TerminalInstance.onMounted() called sessions.removeSession()
on the pending session, dropping sessions.length to 0. SessionContainer's
v-if="hasSessions" went false, unmounting the entire terminal UI before
the WebSocket could establish and add the real session.

Fix: Added replaceSession() to session store. TerminalInstance no longer
removes the pending session — instead passes its ID to connectToHost(),
which swaps it in-place when the backend responds with the real session ID.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 11:08:34 -04:00
Vantz Stockwell
6759327ee3 fix: null reference crash in saveHostInline after edit
Save editingHost.id to local var before nulling the ref.
Prevents TypeError on Array.find during post-save refresh.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 11:01:38 -04:00
Vantz Stockwell
340a862826 fix: wire up Edit Host modal with pre-populated fields
- Edit now populates name, hostname, port, protocol, group, credential, tags
- Modal title shows "Edit Host" vs "New Host" appropriately
- Save calls updateHost for existing hosts, createHost for new
- Added group dropdown selector to host modal
- Button shows "Update" when editing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 10:49:22 -04:00
Vantz Stockwell
1f32ce4620 fix: credential picker in host modal, fix xterm dimensions crash
- Added credential dropdown to New Host modal (loads from vault API)
- Fixed xterm.js "Cannot read dimensions" crash by guarding fitAddon.fit()
  with requestAnimationFrame and container dimension checks
- Added WebGL context loss handler
- credentialId now passed when creating hosts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 10:31:39 -04:00
Vantz Stockwell
7e347ce378 fix: add tags input to host modal, pass groupId when creating from group
- Tags field added to New Host modal (comma separated)
- Clicking + on a group now passes groupId to the new host
- Shows "Adding to: GroupName" in modal when creating from a group
- Refreshes tree after host creation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 10:30:29 -04:00
Vantz Stockwell
ae97bd5a4a feat: convert Settings to right sidebar panel, remove light mode
- Settings now opens as a slide-over sidebar from nav bar (no page nav)
- Removed App Theme toggle (dark mode only — no working light mode)
- Terminal theme picker, font size, scrollback all in sidebar
- Removed standalone /settings page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 10:25:24 -04:00
Vantz Stockwell
74d3c0bd9a feat: add delete button for groups in sidebar
Shows × on hover next to the + button. Confirms before deleting.
Hosts in deleted groups become ungrouped.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 10:23:02 -04:00
Vantz Stockwell
3b14a7c1d1 feat: Termius-inspired UI — right sidebar, host counts, terminal themes
Left sidebar:
- Groups now show recursive host count badges
- Hosts in tree show up to 3 tags inline

Right sidebar (Host Details panel):
- Click any host card to open details panel on the right
- Shows address, port, protocol, group, credential, tags, color, notes
- Connect, Edit, Delete action buttons at bottom
- Selected card gets ring highlight

Terminal themes (10 prebuilt):
- Wraith (default), Dracula, Nord, Solarized Dark, Monokai, One Dark,
  Gruvbox Dark, Tokyo Night, Catppuccin Mocha, Cyberpunk
- Visual theme picker in Settings with color preview + sample text
- Persisted to /api/settings and localStorage for immediate use
- useTerminal reads theme on terminal creation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 10:19:57 -04:00
Vantz Stockwell
b88878b171 fix: use Nuxt prefixed component names for connections/ subdirectory
Root cause of hosts not displaying: Nuxt auto-imports components in
subdirectories with the directory name as prefix. HostCard → ConnectionsHostCard,
HostTree → ConnectionsHostTree, QuickConnect → ConnectionsQuickConnect.
Unprefixed names silently resolve to nothing — no errors, just invisible components.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 10:11:37 -04:00
Vantz Stockwell
e2c04186d1 fix: inline vault modals to fix rendering issue
Same root cause as host/group dialogs — components in subdirectories
with Teleport don't render at runtime in Nuxt SPA builds. Inlined
CredentialForm and KeyImportDialog directly into their parent pages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 09:30:13 -04:00
Vantz Stockwell
826ca1c0c7 feat: replace WRAITH text with ghost logo in nav bar and login
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 09:16:21 -04:00
Vantz Stockwell
f778213c32 fix: inline modals in index.vue, proper DTO for profile update
Dialogs: bypassed component-based dialogs entirely — inlined modals
directly in index.vue with inline style fallbacks for z-index/colors.
If button clicks work, we see the modal. Period.

Profile 500: created UpdateProfileDto with class-validator decorators
so ValidationPipe processes it correctly. Added error logging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 09:09:05 -04:00
Vantz Stockwell
19e765058d fix: remove extra </div> from Teleport removal in HostEditDialog
Mismatched div count was silently breaking the component in
production builds. 18 opens, 19 closes → now 18/18.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 08:52:18 -04:00
Vantz Stockwell
04d619eb2d fix: replace PrimeVue dialogs with plain Tailwind modals
PrimeVue Dialog wasn't rendering regardless of theme config.
Rewrote both Host and Group edit dialogs using Teleport + Tailwind,
matching the rest of the app's styling pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 08:45:12 -04:00
Vantz Stockwell
edea719d17 fix: remove pre-filled email from login form
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 08:43:12 -04:00
Vantz Stockwell
6931e93838 fix: configure PrimeVue Aura dark theme so dialogs render
theme: 'none' meant Dialog/InputText/Select/Button had zero CSS.
Configured Aura preset with sky primary + dark mode selector.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 08:41:31 -04:00
Vantz Stockwell
13111ae007 feat: nav bar (Home link), profile management, TOTP 2FA
- Add Home/Profile links to nav bar alongside Vault/Settings/Logout
- Profile page: change email, display name, password
- TOTP 2FA: setup with QR code, verify, disable with password
- Login flow: two-step TOTP challenge when 2FA is enabled
- Backend: new endpoints PUT /profile, POST /totp/setup|verify|disable
- Migration: add totp_secret and totp_enabled columns to users

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 08:36:03 -04:00
Vantz Stockwell
f1e3892572 fix: correct dist path (dist/src/main.js), static path, explicit schema for migrate 2026-03-13 08:23:52 -04:00
Vantz Stockwell
bced9728d3 fix: add initial Prisma migration so migrate deploy creates tables 2026-03-13 08:22:27 -04:00
Vantz Stockwell
5c82bbbb79 fix: auto-run Prisma migrate + seed on container startup 2026-03-13 08:19:46 -04:00
Vantz Stockwell
adb18cbd75 fix: expose app on port 4210 2026-03-13 08:17:52 -04:00
Vantz Stockwell
3a55287f54 fix: Dockerfile — use npm install instead of npm ci for lock file compat 2026-03-13 08:12:57 -04:00
Vantz Stockwell
7dff07e3ac feat: Wraith v0.1.0 — SSH + SFTP + RDP in a browser
All modules registered in app.module.ts. 8/8 tests passing. TypeScript clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 17:33:59 -04:00
Vantz Stockwell
b774541a13 feat: Phase 4 — settings, theming, polish
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 17:33:12 -04:00
Vantz Stockwell
8546824b97 feat: quick connect, search, recent connections
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 17:32:18 -04:00
Vantz Stockwell
19183ee546 feat: vault management UI — SSH key import + credential CRUD
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 17:30:59 -04:00
Vantz Stockwell
e2e03be2dd feat: RDP frontend — Guacamole client with custom JSON WebSocket tunnel
- useRdp.ts: JsonWsTunnel class extends Guacamole.Tunnel to bridge
  guacamole-common-js (expects raw protocol) with our JSON gateway
  (consistent with SSH/SFTP message envelope pattern). Parses
  length-prefixed Guacamole instructions, dispatches to Guacamole.Client.
  Handles mouse/keyboard input, clipboard send, and session lifecycle.
- RdpCanvas.vue: full-size container that mounts the Guacamole display
  canvas. Calls useRdp().connectRdp() on mount, cleans up on unmount.
  Exposes sendClipboard() and disconnect() for toolbar integration.
- RdpToolbar.vue: auto-hiding floating toolbar (3s idle timeout) with
  clipboard paste dialog, fullscreen toggle (HTML5 Fullscreen API),
  settings panel stub, and disconnect button.
- SessionContainer.vue: renders RdpCanvas + RdpToolbar when
  session.protocol === 'rdp', replacing the Phase 3 placeholder.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 17:27:19 -04:00
Vantz Stockwell
5d75869bb4 feat: RDP backend — Guacamole TCP tunnel to guacd over WebSocket
- guacamole.service.ts: raw TCP client to guacd on GUACD_HOST:GUACD_PORT.
  Performs SELECT rdp → CONNECT handshake with full RDP parameter set.
  Provides encode/decode helpers for length-prefixed Guacamole wire format.
- rdp.gateway.ts: WebSocket gateway at /ws/rdp. JWT auth via WsAuthGuard.
  Handles connect (host lookup, credential decrypt, guacd tunnel open) and
  guac (bidirectional instruction forwarding). Updates lastConnectedAt and
  creates ConnectionLog on connect (same pattern as SSH gateway).
- rdp.module.ts: imports VaultModule, ConnectionsModule, AuthModule.
- app.module.ts: registers RdpModule.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 17:27:09 -04:00
Vantz Stockwell
c8868258d5 feat: Phase 2 — SSH terminal + SFTP sidebar in browser
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 17:21:11 -04:00
Vantz Stockwell
56be3fc102 feat: SFTP gateway — file operations over WebSocket
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 17:17:18 -04:00
Vantz Stockwell
60d7b6b024 feat: SSH terminal gateway — ssh2 proxy over WebSocket
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 17:17:12 -04:00
Vantz Stockwell
fd916fa4ef fix: add @types/jest, fix WsAuthGuard TS error 2026-03-12 17:13:50 -04:00