Commit Graph
418 Commits
Author SHA1 Message Date
J3vb fcffdb8d6b fix: add per-user upload rate limit to prevent disk exhaustion (BUG-131)
Upload endpoint now enforces 10 uploads/min per user via the existing
RateLimiter. Previously only body size was capped (100 MiB) with no
per-user throttle, allowing authenticated users to exhaust disk with
repeated uploads.
2026-04-02 13:18:57 +02:00
J3vb ac86fc1ef5 fix: immediately disconnect banned user's WebSocket connection (BUG-113)
BroadcastMemberBan now calls DisconnectUser after broadcasting, which
sends an error message and kicks the client. Previously banned users
retained WS access until the periodic 30s session sweep or 10-message
recheck triggered.
2026-04-02 13:16:06 +02:00
J3vb 50258457e4 fix: per-username login lockout and per-user password confirmation lockout (BUG-110, BUG-111)
BUG-110: Login handler now tracks failures per-username alongside per-IP.
Distributed brute force from rotating IPs is blocked after 9 failures
for the same username within 15 minutes.

BUG-111: Password-change, TOTP enable/confirm/disable endpoints now have
per-user escalating lockout (3 failures / 15min window / 15min lock),
matching the existing delete-account pattern. Prevents password oracle
attacks via stolen session tokens.
2026-04-02 13:12:38 +02:00
J3vb d056229b28 fix: self-signed cert no longer generated as CA (BUG-138)
Changed IsCA to false, removed KeyUsageCertSign, and reduced validity
from 10 years to 2 years. A compromised key can no longer sign
additional certificates trusted by TOFU-pinning clients.
2026-04-02 13:03:02 +02:00
J3vb 9d9f635c2d fix: gate credential persistence on rememberPassword checkbox (BUG-135)
wirePostAuth always called saveCredential regardless of the "Remember
password" checkbox state, leaving the session token in Windows
Credential Manager even when the user opted out. Added rememberPassword
parameter to wirePostAuth and skip saveCredential when false.
2026-04-02 13:01:10 +02:00
J3vb 5dc6fe61b1 chore: remove gitignored docs/brain files from tracking 2026-04-02 12:56:39 +02:00
J3vb f62d7e318b fix: stop leaked camera/screen tracks on reconnect (BUG-098)
teardownForReconnect only cleaned up audio pipeline and token timer,
leaving manual camera/screenshare MediaStreamTracks capturing
indefinitely after unexpected disconnect. Added stopManualCameraTrack
and stopManualScreenTracks calls before room is nulled, plus store
flag resets so the UI reflects the actual state.
2026-04-02 12:55:51 +02:00
J3vb 839b07e8c6 fix: show notification banner on TOFU first-use cert trust (BUG-133)
The cert-tofu event listener now handles "trusted_first_use" status
and shows a visible notification banner with the server hostname and
SHA-256 fingerprint. Adds onCertFirstTrust callback to the WS client
API. First-use certificate trust is no longer silent.
2026-04-02 12:47:07 +02:00
J3vb e2a9811765 fix: updater uses TOFU-pinned cert validation instead of disabling TLS (BUG-134)
Replace danger_accept_invalid_certs(true) with PinnedVerifier-based
rustls config that validates server cert against TOFU fingerprint from
the cert store. For CA-signed servers (no stored fingerprint), system
TLS is used. Shared PinnedVerifier, cert_store_key, and
load_stored_fingerprint are now pub(crate) for reuse.
2026-04-02 12:41:49 +02:00
J3vb df9b5ab90f fix: close reconnect event gap and eliminate silent message drops (BUG-123, BUG-124)
BUG-123: Register client BEFORE writing replay/ready data so broadcasts
during the write window queue in the send buffer instead of being lost.
On handshake failure, unregister before closing.

BUG-124: sendMsg/trySendMsg now close the send channel on buffer
overflow, forcing a disconnect → reconnect with replay recovery
instead of silently dropping messages and diverging state.
2026-04-02 12:32:11 +02:00
J3vb 5a417def52 fix: harden LiveKit tokens — 5min TTL, source restrictions, webhook validation (BUG-127, BUG-128)
BUG-127: Reduce token TTL from 24h to 5min. Webhook participant_joined
now validates voice_states membership and join token match — rogue
participants are removed via LiveKit API.

BUG-128: GenerateToken uses CanPublishSources to restrict track types
(microphone/camera/screen_share) based on actual OwnCord permissions,
preventing SFU-level bypass of USE_VIDEO/SHARE_SCREEN checks.
2026-04-02 12:25:03 +02:00
J3vb d912a5a87d fix: AdminIPRestrict uses trusted_proxies for real client IP (BUG-116)
AdminIPRestrict now accepts trustedProxyCIDRs and resolves the real
client IP from X-Real-IP/X-Forwarded-For when connecting through a
trusted reverse proxy. Without trusted_proxies configured, behavior
is unchanged (RemoteAddr only). Prevents admin panel exposure when
OwnCord is deployed behind nginx/caddy/traefik.
2026-04-02 12:15:47 +02:00
J3vb 5edebb43b3 fix: add 30s session sweep to kick revoked WS connections (BUG-109)
Idle WebSocket connections only revalidated sessions every 10 sent
messages, allowing revoked tokens to stay connected indefinitely.
Add sweepRevokedSessions() on a 30s ticker that checks all connected
clients against the DB and kicks any with deleted/expired sessions
or banned users.
2026-04-02 12:12:56 +02:00
J3vb 77c440c4ae fix: atomic setup prevents TOCTOU race creating multiple owners (BUG-119)
Replace separate UserCount() + CreateUser() with atomic
CreateOwnerIfEmpty() that checks and inserts in a single SQLite
transaction. Concurrent race test validates exactly 1 owner under
20 parallel requests.
2026-04-02 12:09:27 +02:00
J3vb 098eebe674 fix: add CSRF Origin check to setup endpoint (BUG-097)
The first-run setup POST was vulnerable to cross-site request forgery
because it had no Origin validation. Added isSetupOriginAllowed check
that validates the Origin header against configured allowed_origins.
Requests with a mismatched Origin are rejected with 403. Requests
without an Origin header (same-origin or curl) are allowed through.
2026-04-02 11:54:56 +02:00
jevb 9249ff0a78 fix: close DB before backup restore to prevent corruption (BUG-096)
The restore handler was overwriting the live SQLite database while the
old *sql.DB handle remained open. Now: broadcasts server_restart to
clients, checkpoints WAL, closes the DB connection, then copies the
backup file over the closed database. Server must restart after restore.
2026-04-02 11:45:09 +02:00
jevb d2705dff92 fix: filter voice states by channel visibility in ready payload (BUG-095)
Voice states were loaded with GetAllVoiceStates across the entire server,
leaking who was in hidden voice channels. Now voice states are filtered
through the visible channel set before inclusion in the ready payload.
Updated tests to use explicit roles where voice state visibility matters.
2026-04-02 11:40:16 +02:00
jevb 125512d591 fix: fail closed on role lookup in WS ready flow (BUG-094)
If GetRoleByID fails or returns nil during WebSocket connect, the
server now disconnects the client instead of serving a permissive
ready payload with all channels visible. In buildReady, nil role is
now treated as zero-access (no channels) instead of full-access.
Updated tests to pass explicit Owner role where channel visibility
is expected.
2026-04-02 11:29:10 +02:00
jevb b5111dddf1 fix: correct GitHub username in README issues link 2026-04-02 11:25:53 +02:00
jevb 527e7e7d2c fix: exclude DM channels from guild channel listings (BUG-093)
DM channel rows were returned by ListChannels and included in both
the REST channel list and the WebSocket ready payload. Added type="dm"
skip in handleListChannels and buildReady filter loops. DMs are already
delivered separately via dm_channels. 2 new tests verify exclusion for
both member and admin roles.
2026-04-02 11:24:43 +02:00
jevb 68befb49c5 docs: add early alpha warning banner to README 2026-04-02 11:23:08 +02:00
jevb 57d87bb439 fix: require auth + channel ACL on file serving (BUG-092)
Private attachments were accessible without authentication if the UUID
was known. Added AuthMiddleware to the GET /api/v1/files/{id} route,
uploader_id tracking on uploads, and channel-level permission checks
(guild READ_MESSAGES, DM participant, admin bypass) in handleServeFile.

Migration 010 adds uploader_id column to attachments table.
8 new access-control tests covering all authorization paths.
2026-04-02 11:16:16 +02:00
jevb 384e94d9f9 fix: close 3 security audit findings (BUG-108, BUG-122, BUG-126)
BUG-122: Remove channelID==0 bypass in deliverBroadcast that leaked
all channel-scoped broadcasts to unfocused clients. Clients must now
send channel_focus to receive channel events.

BUG-126: Reject edits and reactions on soft-deleted messages in
handleChatEdit and handleReaction.

BUG-108: Revoke all other sessions when a user changes their password
or enables/disables TOTP 2FA. Adds DeleteOtherSessions DB function.

7 new test cases covering all three fixes.
2026-04-02 10:37:34 +02:00
jevb cbc39d7ab5 fix: address code review findings from bug fix session
- voice_join.go: treat GetVoiceState DB error as switch failure instead
  of silently proceeding (HIGH: could bypass capacity check)
- voice_leave.go: move ctx to first parameter per Go idiom, remove
  nolint:revive directive (MEDIUM: style compliance)
- Update all call sites for new parameter order
2026-04-01 18:33:33 +02:00
jevb cfaa253b1b fix: remediate low-signal Go test assertions (BUG-061)
Replace "doesn't panic" style assertions in coverage_boost_test.go
with behavioral checks on return values and state.
2026-04-01 18:25:13 +02:00
jevb 7924070531 fix: remediate low-signal test assertions in client tests (BUG-061, BUG-064)
- audio-pipeline.test.ts: replace no-op assertions with state checks
- device-manager.test.ts: replace toBeDefined/not.toThrow with actual
  value and behavior assertions
- livekit-session.test.ts: replace not.toHaveBeenCalled with state
  verification and return value checks
2026-04-01 18:22:09 +02:00
jevb 09da7db0d3 fix: improve native E2E reliability (BUG-059)
- Increase CDP bootstrap timeout to 60s with exponential backoff
- Replace waitForLoadState("networkidle") with element-based readiness
- Replace fixed waitForTimeout sleeps with waitForSelector/toBeVisible
- Convert data-dependent skips to conditional test.skip with messages
- Add shared helpers for countTextChannels/countVoiceChannels
- Increase login timeout to 60s for rate-limited servers
2026-04-01 18:20:44 +02:00
jevb 8907bfd6b6 fix: clear qualityDebounceTimer on connectionStats stop (BUG-071B)
The quality debounce timer could fire after the stats poller was stopped,
calling listeners against a dead room. Sub-issues A (autoplay listener)
and C (VAD timer) were already fixed in prior work.
2026-04-01 18:12:12 +02:00
jevb 32068e06f6 fix: resolve 4 server bugs (Phase 2: BUG-084, BUG-086, BUG-088, BUG-089)
- BUG-084: Broadcast filter now delivers channel messages to unfocused
  clients (channelID==0) instead of silently dropping them
- BUG-086: leaveVoiceChannelWithRetry retry goroutine respects context
  cancellation and hub stop to prevent leaks on shutdown
- BUG-088: Voice channel switch verifies old state is cleared before
  joining new channel, preventing capacity bypass on DB failure
- BUG-089: handleFreshConnect RemoveParticipant goroutine checks hub
  stop and documents identity-based targeting safety
2026-04-01 18:08:28 +02:00
jevb f3c9f98b91 fix: resolve 4 server bugs (Phase 1: BUG-085, BUG-087, BUG-090, BUG-091)
- BUG-085: ring buffer EventsSince off-by-one — change < to <= so
  afterSeq == oldestSeq returns nil (triggers full ready payload)
- BUG-087: GracefulStop not idempotent — wrap body in sync.Once to
  prevent double lkProcess.Stop() on concurrent calls
- BUG-090: FTS query truncation at byte boundary — use []rune
  truncation to preserve valid UTF-8 for CJK/emoji input
- BUG-091: updater downloadFile double-closes file on Windows —
  add closed sentinel to guard defer against explicit Close()
2026-04-01 17:52:06 +02:00
jevb 7a79b1c248 fix: allow inline styles and scripts in admin panel CSP
The Content-Security-Policy header was blocking inline <style> and
<script> tags, breaking the single-file SPA admin panel entirely.
2026-04-01 17:26:07 +02:00
jevb 6f59fb85af fix: avoid removing video tile on temporary track mute
Track mute events can fire during network blips or SFU layer switching.
Previously this removed the tile entirely, requiring a new TrackSubscribed
event to restore it. Now mute adds a CSS class (track-muted) and unmute
removes it, while only the ended event triggers tile removal.
2026-04-01 17:12:13 +02:00
jevb 8f45307ec5 feat: add stream preview, video grid track lifecycle, and build cleanup
- Integrate stream preview into VoiceChannel sidebar for remote users
  with active camera/screenshare
- Add track lifecycle listeners (ended/mute) to VideoGrid to auto-remove
  stale black tiles
- Call video.play() explicitly for WebView2 autoplay compatibility
- Prevent redundant voice join when already in channel (ChannelSidebar)
- Add attachScrollCollapse for preview cleanup on scroll
- Remove tauri_typegen from build.rs
- Add Server/server.exe to gitignore
- Add stream-preview and video-mode-controller test coverage
2026-04-01 17:07:02 +02:00
jevb b1d633029c fix: resolve remote video streams not displaying due to identity format mismatch
Server generates LiveKit participant identities as "user-{id}:{voiceJoinToken}"
but parseUserId regex required exact "user-{id}" (with $ anchor), returning 0
for all remote participants. This caused the userId > 0 guard in
handleTrackSubscribed to silently drop all remote video callbacks.

- Update parseUserId regex to accept both "user-{id}" and "user-{id}:{token}"
- Fix getRemoteVideoStream to iterate remoteParticipants instead of exact
  identity lookup (which also failed due to the token suffix)
- Add test cases for token-suffixed identities
- Fix pre-existing noUncheckedIndexedAccess TS errors in test files
2026-04-01 17:05:57 +02:00
jevb 39f544f8f1 fix: block audio from new participants when locally deafened
The deafen guard in applyRemoteAudioSubscriptionState only unsubscribed
participants already in the room. Participants joining after deafen had
their audio unconditionally attached. Added a guard at the top of
handleTrackSubscribedAudio that checks localDeafened and calls
publication.setSubscribed(false) before any audio element is created.
2026-04-01 16:38:36 +02:00
jevb 0cfe1a7ced chore: untrack docs/brain/ files (local-only vault)
These were accidentally force-added. The vault is gitignored and
should remain local-only. Files are preserved on disk.
2026-04-01 15:51:50 +02:00
jevb 4cdb10b76c docs: update vault with new dev tools, testing strategy, and session progress
- CLAUDE.md: add mutation testing, load testing, chaos testing commands
- TESTING-STRATEGY.md: add Section 15 (Mutation Testing) with Stryker
  and go-gremlins guidance, kill rate thresholds
- Server-Configuration.md: document waf_enabled and waf_paranoia_level
- Testing-Tools.md: new comprehensive guide for all 6 dev tools
- In Progress.md: update completed tasks for 2026-04-01 session
2026-04-01 15:49:49 +02:00
jevb 57ba535673 fix: replace mutating sort/reverse with toSorted/toReversed (ES2023)
Replace .sort() with .toSorted() and .reverse() with .toReversed()
to avoid in-place mutation (consistent with project immutability rules).
Only disable no-map-spread rule — new Map(existingMap) is the correct
immutable copy pattern, not a perf issue worth flagging.

Oxlint: 0 warnings, 0 errors with all rules enabled except no-map-spread.
2026-04-01 15:44:06 +02:00
jevb e203be5638 fix: resolve all 57 oxlint warnings across client codebase
- prefer-add-event-listener: converted DOM onclick to addEventListener,
  suppressed IDB/AudioWorklet onsuccess/onerror (spec-correct pattern)
- no-await-in-loop: suppressed intentionally sequential loops (polling,
  ordered ops), converted parallelizable loops to Promise.all
- consistent-function-scoping: moved pure utilities to module scope
- no-array-sort: added explicit comparators to all .sort() calls
- require-post-message-target-origin: suppressed for MessagePort
  (AudioWorklet ports don't accept targetOrigin)
- preserve-caught-error: added logging or renamed to _e
- no-shadow: renamed inner variables to avoid shadowing
- no-new: assigned side-effect constructors to variables
- no-array-reverse: replaced with .slice().reverse() to avoid mutation

Result: 0 warnings, 0 errors from oxlint.
2026-04-01 15:36:07 +02:00
jevb 31d90434fd fix: handle BEGIN...END blocks in SQL migration splitter
The splitStatements function naively split on every semicolon, breaking
CREATE TRIGGER definitions that contain semicolons inside their
BEGIN...END bodies. Now tracks depth so trigger bodies are kept intact.

Fixes TestWebhook_ParticipantLeft_NoDoubleBroadcast_AfterFreshCleanup
and TestWebhook_ParticipantLeft_OldToken_DoesNotTeardownReplacement.
2026-04-01 15:35:50 +02:00
jevb ade414bedd fix: handle IPv6 loopback (::1) in LiveKit URL resolution
The previous implementation used serverHost.split(":")[0] to extract
the hostname, which fails for IPv6 addresses — "::1:7880".split(":")[0]
yields "" instead of "::1". Now handles three formats:

- Bracketed: [::1]:7880 → host = "::1"
- Bare IPv6: ::1 → host = "::1" (multiple colons detected)
- IPv4/hostname: example.com:443 → host = "example.com"

Also fixes ensureLiveKitProxy to wrap bare IPv6 in brackets and
correctly detect port presence in bracketed notation.
2026-04-01 15:17:09 +02:00
jevb 74b189fc49 test: add 76 mutation-killing tests for livekitSession (T-451)
Target the #1 weakest file (25.88% mutation score, 170 survivors).
New tests cover connection lifecycle and state/facade methods:

- resolveLiveKitUrl: 8 tests (localhost/remote/proxy/directUrl paths)
- ensureLiveKitProxy: 3 tests (caching, port appending, null guard)
- connectAndSetup: 5 tests (retry logic, stale join, device fallback)
- handleVoiceToken: 2 tests (refresh shortcut, pending drain)
- attemptAutoReconnect: 5 tests (abort, channel change, retry, cleanup)
- Token refresh: 5 tests (timer fire, null guards, clear prevention)
- leaveVoice: 8 tests (cleanup assertions for every field/resource)
- cleanupAll: 3 tests (proxy stop, field nulling)
- setMuted/setDeafened: 5 tests (room interaction, deafen+mute combos)
- retryMicPermission: 5 tests (no-op, success, noise suppressor, failure)
- restoreLocalVoiceState: 7 tests (reconnect vs join error paths)
- Delegation + singleton: 12 tests (facade verification)

Notable: IPv6 ::1 detection bug found in resolveLiveKitUrl (documented).
2026-04-01 15:14:20 +02:00
jevb 45f46d1fd5 fix: make migration runner resilient to duplicate column errors
The migration runner now splits multi-statement SQL files and executes
each statement individually. "duplicate column name" errors are skipped
since the column already exists from a prior partial run. This fixes a
crash on startup when migration 004_voice_optimization.sql re-ran
against a database that already had the columns.
2026-04-01 15:00:55 +02:00
jevb 3bc63e31e7 docs: add mutation testing TODOs (T-451 to T-457), mark T-448 done
Add 7 new tasks for killing surviving Stryker mutants across
livekitSession, media-visibility, streamPreview, screenShare,
safe-render, roomEventHandlers, and livekitDiagnostics.
Mark Coraza WAF middleware (T-448) as completed.
2026-04-01 15:00:01 +02:00
jevb 6518c5b426 fix: resolve Stryker TypeScript checker errors in test files
Add non-null assertions to mock .calls[0] access in notifications tests
and mockListen implementation reference in ws tests. Required by
Stryker's stricter TS checker vs vitest runtime.
2026-04-01 14:59:04 +02:00
jevb bf2b6f23d0 test: add 112 mutation-killing tests for ws, notifications, audioPipeline
Stryker mutation testing identified 262 surviving mutants across these
three files. New tests target boundary conditions, boolean negations,
arithmetic operators, guard clauses, and state transitions to kill
surviving mutants and improve mutation score.

- ws.ts: 50 new tests (dedup, reconnect delay, cert TOFU, generation guards)
- notifications.ts: 32 new tests (sanitization, @everyone, sound params, permissions)
- audioPipeline.ts: 61 new tests (VAD thresholds, gain smoothing, frame counters)
2026-04-01 14:32:38 +02:00
jevb 5cec992c1f chore: add dev tooling — Stryker, gremlins, k6, toxiproxy, Coraza WAF, Zod
Install and configure mutation testing (Stryker for client, go-gremlins
for server), load testing (k6), chaos testing (toxiproxy), WAF middleware
(Coraza with OWASP rules, opt-in via waf_enabled config), and Zod for
runtime schema validation. All tools verified building cleanly.
2026-04-01 13:49:06 +02:00
jevb a24dbd5d55 feat: add syncutil mutex, test scaffolding, and server hardening
- Add syncutil package with deadlock-detecting mutex (build-tag switchable)
- Add main_test.go TestMain scaffolding across all server packages
- Harden concurrency in ws, admin, auth, and updater packages
- Update CI workflow, go.mod/sum, Cargo.lock, and root changelogen tooling
2026-04-01 12:04:15 +02:00
jevb c53035a4aa fix: resolve TypeScript strict mode type errors
Fix 5 typecheck errors caught by tsc strict mode:
- Remove soundboard_play reference from types.test.ts
- Use optional chaining on MountableComponent.destroy
- Add string fallback for ROLE_COLORS lookup
- Add missing Channel fields in screen-share test mock
- Add non-null assertion on mock.calls index
2026-04-01 11:51:07 +02:00
jevb 87ba387623 chore: update tooling, CI workflows, and dependencies
Update CI/release workflows, gitignore, package dependencies,
Tauri config, and add linter/formatter configs (oxlint, prettier,
knip, vitest browser config).
2026-04-01 11:40:55 +02:00