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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>