114 Commits
Author SHA1 Message Date
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
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 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 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 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 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 447a4543e7 chore: remaining server changes (code quality, go mod tidy)
Go mod tidy, minor server-side adjustments from security verification
and code quality cleanup pass.
2026-04-01 11:38:33 +02:00
jevb 2a62f31c39 test: boost server coverage — auth 60→95%, db 69→81%, config 75→85%
Add comprehensive tests across all Go packages:
- auth: username validation, concurrent rate limiting, TOTP stores, timing
- config: env overrides, default credential detection, voice defaults
- db: search, message queries, special char handling
- api: handler edge cases, error paths, DM/invite/TOTP coverage
- ws: voice handler paths, integration scenarios
- updater: version comparison, timeout handling

6 of 8 packages now at 80%+ coverage.
2026-04-01 11:38:11 +02:00
jevb b4e15e1234 feat: add user profile management endpoints (T-195)
PATCH /api/v1/users/me — update username/avatar
PUT /api/v1/users/me/password — change password with old pw verification
GET /api/v1/users/me/sessions — list active sessions (single SQL query)
DELETE /api/v1/users/me/sessions/:id — revoke session with ownership check

New files: profile_handler.go, profile_queries.go + tests for both.
All endpoints follow existing writeJSON/errorResponse patterns.
2026-04-01 11:37:55 +02:00
jevb c776d04da2 merge: test/server-core-coverage into dev — server core test coverage 2026-04-01 09:24:38 +02:00
jevb a40b42bbed fix: resolve 24 critical and high issues from full code & security review
CRITICAL (5):
- Hub panic recovery now calls h.Stop() after 3 panics (ws/hub.go)
- Ring buffer EventsSince returns non-nil empty slice for current seq (ws/ringbuffer.go)
- PTT event listener stores unsubscribe handle to prevent leak (ptt.ts)
- verifyTotp respects config.allowSelfSigned instead of hardcoding (api.ts)
- ptt_listen_for_key uses spawn_blocking to avoid thread pool starvation (ptt.rs)

HIGH - Server (13):
- TOTP rate-limit checked after body decode; counters reset on success
- TOTP enable returns 409 if already enabled (must disable first)
- Global search pre-computes accessible channel IDs for FTS WHERE clause
- DeleteAccount queries roles by name instead of hard-coded IDs
- BackupToSafe uses absClean in VACUUM INTO
- Voice camera slot uses atomic EnableCameraIfUnderLimit DB method
- readPump snapshots voiceChID before unregister for TOCTOU safety
- Voice join sets state after token send; rollback takes broadcast flag
- Updater download uses probe pattern instead of overflow write
- Webhook checks Authorization header before reading body
- Storage.Save adds fsync and fixes double-close
- Default WS origin denies cross-origin (was: accept all)

HIGH - Client (6):
- WS reconnect uses generation counter to discard stale events
- AudioPipeline uses generation counter against stale worklet callbacks
- Screenshare mute state preserved across reconnect (not full leave)
- handleVoiceToken uses iterative loop instead of unbounded recursion
- store.ts re-entrancy guard with pending update queue
- Notification AudioContext cleaned up on logout

Reviewed by 4 parallel agents across Server Core, Server Realtime,
Client & Tauri, and Security. 55 total findings; 24 CRITICAL+HIGH
fixed here, 31 MEDIUM+LOW tracked in vault backlog (T-265–T-295).
2026-04-01 09:23:17 +02:00
jevb e74dc0245f style: apply linter fixes to new test files
- totp_handler_test: use url.Parse for URI extraction, add net/url import
- models_test: add error checks on json.Unmarshal calls
2026-04-01 08:48:41 +02:00
jevb d87dabeb65 test: add server core test coverage (Session 1)
New test files:
- db/errors_test.go: sentinel error identity, wrapping, IsUniqueConstraintError (12 tests)
- db/models_test.go: JSON round-trip and tag verification for all model types (14 tests)
- db/account_test.go: DeleteAccount last-admin guard, anonymisation, cascade cleanup (12 tests)
- api/totp_handler_test.go: TOTP verify/enable/confirm/disable handler flows (20 tests)

Upgraded existing:
- permissions/permissions_test.go: multi-bit checks, role hierarchy, deny-all+allow-one (8 tests)
- permissions/checker_test.go: admin DM bypass, voice channel perms, multi-bit combined (4 tests)

Total: 70 new tests across 6 files.
2026-04-01 08:41:09 +02:00
jevb a0fd5e8fda security: fix 11 vulnerabilities from security review
Batch 1 — Immediate priority:
- C4: Atomic voice channel capacity (JoinVoiceChannelIfCapacity)
- H5: Sanitize emoji field with bluemonday (stored XSS)
- H8: Permission check before FTS search (timing oracle)
- M8: Filter ready payload channels by ReadMessages
- H10: Remove password/TOTP from admin ListAllUsers query

Batch 2 — Next sprint:
- C1: TOTP replay prevention (UsedTOTPCodeStore, 90s TTL)
- C2: Per-user TOTP brute-force rate limit (10/15min)
- C3: Delete requires SendMessages or ManageMessages
- H1: Expired sessions deleted on detection
- H3: Bearer token whitespace trimmed
- H6: Log warning when WS origin checking disabled
2026-03-31 19:10:42 +02:00
jevb f3036727ae fix: address remaining code review findings (C-3, H-5, H-6, M-2 through M-16)
- C-3: inject setupLimiter into NewAdminAPI instead of package-level global
- H-5: generateRandomKey returns error instead of panicking
- H-6: replace init() bcrypt with sync.Once lazy initialization
- M-2: remove unsafe-inline from admin CSP
- M-3: sanitize upload filenames (strip control chars, truncate to 255)
- M-5: truncate User-Agent to 512 bytes before storing as device
- M-10: MaxBodySizeUnless uses prefix matching instead of exact path
- M-12: wrap seedExistingDatabase in a single transaction
- M-13: use errors.Is for EOF check in upload handler
- M-14: log writeJSON encoding errors instead of discarding
- M-16: standardize error codes to INTERNAL_ERROR across all handlers
2026-03-31 19:08:02 +02:00
jevb 28f33644de fix: address remaining code review findings (C-3, H-5, H-6, M-1 through M-16)
- C-3: inject setupLimiter into NewAdminAPI instead of package-level global
- H-5: generateRandomKey returns error instead of panicking
- H-6: replace init() bcrypt with sync.Once lazy initialization
- M-2: remove unsafe-inline from admin CSP script-src and style-src
- M-3: sanitize upload filenames (strip control chars, truncate to 255)
- M-5: truncate User-Agent to 512 bytes before storing as device
- M-10: MaxBodySizeUnless uses prefix matching instead of exact path
- M-12: wrap seedExistingDatabase in a single transaction
- M-13: use errors.Is for EOF check in upload handler
- M-14: log writeJSON encoding errors instead of discarding
- M-16: standardize error codes to INTERNAL_ERROR across all handlers
2026-03-31 19:00:17 +02:00
jevb d1c9d4c9cb fix: address critical and high code review findings
- C-1: handle filepath.Abs error in backup path traversal guards
- C-2: WAL checkpoint before live DB restore to prevent corruption
- C-4: default AllowedOrigins to empty (deny cross-origin by default)
- C-5: renumber duplicate 003_ migration prefix (003-008 -> 003-009)
- H-1: sanitize FTS5 query input to prevent operator injection
- H-3: send SIGTERM for graceful shutdown before os.Exit in updater
- H-9: fix RingBuffer memory leak from unbounded backing array growth
- H-10: use errorResponse struct consistently in upload handler
2026-03-31 18:47:06 +02:00
jevb fa1435e4de fix: address critical and high code review findings
- C-1: handle filepath.Abs error in backup path traversal guards
- C-2: WAL checkpoint before live DB restore to prevent corruption
- C-4: default AllowedOrigins to empty (deny cross-origin by default)
- C-5: renumber duplicate 003_ migration prefix (003-008 -> 003-009)
- H-1: sanitize FTS5 query input to prevent operator injection
- H-3: send SIGTERM for graceful shutdown before os.Exit in updater
- H-9: fix RingBuffer memory leak from unbounded backing array growth
- H-10: use errorResponse struct consistently in upload handler
2026-03-31 18:46:33 +02:00
jevbandClaude Opus 4.6 b36c030cac feat: LiveKit video grid improvements, voice state cleanup, and internal tooling
- Video grid: sync stream type attribute on updates, add screenshare data attribute
- Dispatcher: handle voice_token messages, improve video track event handling
- LiveKit session: add video track publication support
- Hub: stale client timeout cleanup, improved voice state management
- Voice join/leave: context propagation, better error handling
- Livekit webhook: structured event handling with room/participant data
- Server DB: voice query improvements, new test coverage
- WS integration tests: expanded coverage for voice and LiveKit flows
- Gitignore: add internal dev tools directory, owncord-server.exe

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 11:41:59 +02:00
jevb 0e29d98d9d fix: resolve CI failures — eslint peer dep conflict and errcheck lint errors
Downgrade @eslint/js to ^9.39.4 to match eslint ^9 peer requirement.
Fix 7 unchecked .Close() return values flagged by errcheck linter.
2026-03-30 21:54:24 +02:00
jevb 90b4f268e2 feat: TOTP 2FA settings UI, server hardening, full validation pass
Client:
- Add TOTP enrollment/disable UI in Settings > Account (AccountTab.ts)
- Fix api.ts enableTotp/confirmTotp/disableTotp to require password param
- Add totp_enabled field to UserWithRole type
- Wire SettingsOverlay TOTP callbacks through MainPage and ConnectPage
- 27 new tests: totp-settings (18), api TOTP methods (6), auth store (3)

Server:
- Fix targetBoolSetting to default false on ErrNotFound (fresh DB compat)
- Fix admin settings test: boolean keys use valid values, not "testvalue"
- Add require_2fa validation to settings handler (normalizeSettingUpdates)
- Remove unused authenticateAdmin from logstream.go

Docs:
- Mark DOCUMENTATION_AUDIT Critical Finding #1 as RESOLVED
- Update CLAUDE.md Key Features with 2FA/TOTP bullet
- Update CLIENT-ARCHITECTURE.md with TOTP components
- Update CHATSERVER.md login flow and rate limiting table
- Create session log, update task tracking (T-192–T-201)
2026-03-29 21:31:18 +02:00
jevb 6b6a6fbea8 refactor: context propagation, LogAudit deadlock fix, ESLint v9, code quality
- Propagate context.Context from WS upgrade through all 17 handlers
- Add ExecContext/QueryRowContext/QueryContext/BeginTx to DB wrapper
- Fix LogAudit deadlock: move audit writes after tx.Commit to avoid
  SQLite write-lock contention (TestAdminAPI_PatchUser_UnbanUser)
- Add ESLint v9 with no-floating-promises, no-unused-vars
- Refactor livekitSession.ts: remove duplicate audio pipeline (267 lines)
- Add delete account UI tests (7 tests)
- Expand WS integration tests
2026-03-29 19:39:46 +02:00
jevb 2976863ad0 fix: atomic invite registration, fail-closed search, proxy-aware rate limiting
- Atomic CreateUserWithInvite prevents invite burn on failed registration
- Channel search fails closed on channel-type and override lookup errors
- Malformed FTS input returns 400 instead of 500
- Search rate limiting uses own namespace, respects trusted proxy IPs
- Login lockout keyed by forwarded client IP behind reverse proxy
- Trusted same-server OG previews re-enabled with self-signed cert support
- Normalized host matching for embeds/attachments
- Regression tests for all changes (auth, channel, embeds)
2026-03-29 19:39:22 +02:00
jevb 4c4526e539 fix: security hardening — 45 issues from full-project Copilot audit
Critical (6):
- C1: SQL injection in VACUUM INTO backup path — strict character allowlist
- C2: Unlimited binary download in updater — 500MB LimitReader
- C3: JSON injection in SSE log stream — json.Marshal instead of concat
- C4: CSS injection via custom themes — reject () and {} in values
- C5: Silent DM message loss — error response on participant lookup failure
- C6: LiveKit URL credential leak — strip creds from diagnostics endpoint

High (11):
- H1: DB errors no longer trigger login rate-limit lockout
- H2: Permission fetch failure returns 500, not empty channel list
- H3: TOCTOU race on duplicate WS — atomic check-and-register in hub
- H5: LiveKit webhook verifies voice channel match (already implemented)
- H7: Server host address validated before storage (hostname regex)
- H8: WS message deduplication on reconnect replay (1000-entry Set)
- H9: Admin setup endpoint rate limited (5/min/IP)
- H10: Backup responses return filename only, not full path
- H11: Update binary recovery failure now alerts admin

Medium (17):
- M1: MIME type from magic bytes, not client header
- M3: Nil guard on DM broadcast recipient
- M5: LiveKit process run-done channel race fixed
- M6: Backup restore calls fsync before close
- M7: Partial download file cleaned up on error
- M8: Admin CSP uses nonce instead of unsafe-inline
- M9: Client rate limiter enforced for presence_update
- M10: Voice joinedAt not reset on double-join
- M11: Unread count skips increment during reconnect replay
- M13: Category type uses exact match, not substring
- M14: Storage LimitReader off-by-one fixed
- M15: GitHub token only sent to GitHub hosts
- M16: Content-parser ReDoS regex replaced with split approach
- M17: Audio device switch error handling added

Low (11):
- L1: CORS uses configured origins instead of wildcard
- L2: HSTS header added when TLS enabled
- L3: Consistent JSON error responses across all endpoints
- L4: File modtime from stat, not time.Now()
- L5: Malformed invite JSON returns 400
- L6: TouchSession failure logged at warn
- L8: MessageInput timers cleared on destroy
- L9: Log persistence flush errors caught
- L10: Credential save failure surfaced to user
- L11: Case-insensitive asset name matching in updater

Found by GitHub Copilot full-project review (claude-sonnet-4.6 + claude-haiku-4.5).
2026-03-29 12:35:04 +02:00
jevb b8879fe237 fix: resolve all 11 open bugs, add account deletion, harden security
- BUG-046: wrap switchActiveDevice in isolated try-catch with fallback
- BUG-047: track pending uploads, block send until complete
- BUG-048: add 100MB size limit and MIME allowlist on paste
- BUG-049: replace requestAnimationFrame with setTimeout for VAD
- BUG-050: clear stale audio elements before auto-reconnect
- BUG-051: add origin check + segment-based path deny-list to proxy
- BUG-052: replace 6 swallowed .catch(() => {}) with logging
- BUG-053: already fixed (TOFU pinning in livekit_proxy.rs)
- BUG-054: account deletion endpoint + UI with password confirmation,
  per-user progressive lockout, and anonymization (not hard delete)
- BUG-055: remove 4 stale vitest coverage exclusions
- BUG-056: fix proxy URL test with proper Tauri invoke mock
- Fix pre-existing themes.test.ts accent color key mismatch
- Harden isOriginAllowed to default-deny when no origins configured
- Return 204 No Content on account deletion (consistency)
2026-03-28 13:21:07 +01:00
jevbandclaude-flow c53d63da47 feat: comprehensive spec docs, test suite, E2E overhaul, and security hardening
Spec Documentation (18 files, 680KB):
- Expanded all 15 existing spec files with deep detail from source code
- Created 3 new specs: DM-SYSTEM, THEME-SYSTEM, RECONNECTION
- Created E2E-BEST-PRACTICES spec
- Audited all specs against source: fixed 50 errors

Unit Tests (143 new):
- Go: dm_queries_test (21), dm_handler_test (17), dm_handlers_test (18), ringbuffer_test (22)
- TS: dm-store (16), disposable (14), themes security (17), ws reconnection (8), dispatcher DM (2)

E2E Tests (22 mocked + 6 native specs):
- New: dm-system, theme-persistence, reconnection (mocked + native)
- Fixed 12 fake assertions, 18 hardcoded timeouts, 5 stale selectors
- Persistent fixture: login once per run instead of per test
- ensureLoggedIn with exponential backoff for rate limiting

Security Fixes:
- DM auth bypass: added IsDMParticipant to handleGetPins, handleSetPinned, handleSearch
- LiveKit InsecureVerifier replaced with PinnedVerifier (TOFU from shared cert store)
- IDOR leak: handleChatEdit/Delete now return opaque error codes
- CSS injection: added deny-list for dangerous CSS functions in themes
- BANNED error now triggers logout instead of infinite reconnect
- CredFree leak fixed: Windows credential memory freed before parsing
- Login lockout off-by-one: limit=9 so 10th failure triggers lockout

Stability Fixes:
- Rate limiter StartCleanup goroutine now started (prevents memory leak)
- Voice mute/deafen rate limiting added (2/sec, matching camera/screenshare)
- DM typing no longer echoes back to sender
- Accept loop spin protection (5 consecutive error limit)
- voice_config protocol drift resolved (3 missing fields added)
- Login rate limit set to 60/min (spec updated, 10-failure lockout is real protection)
- Hardcoded roleNameToId replaced with dynamic lookup from ready payload

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-28 10:39:23 +01:00
jevb 76cb9b9630 fix: security hardening, DM auth, LiveKit stability, and voice call timer
Security fixes (from multi-reviewer code review):
- Add DM participant auth checks to channel_focus, typing, and REST
  message endpoints — prevents unauthorized access to DM channels
- Fix TOCTOU race in GetOrCreateDMChannel using IMMEDIATE transaction
- Validate YAML credentials before LiveKit config interpolation
- Add CSS variable injection prevention in custom theme loader
- Validate localStorage JSON before unsafe type casts

LiveKit stability:
- Track remote mic audio elements for cleanup on abnormal disconnect
- Remove duplicate token refresh timer scheduling
- Add .catch() to all floating applyMicMuteState promises
- Clear reconnectAc after async post-connect work completes
- Fix double cmd.Wait() race in LiveKit process Stop()
- Reorder voice_join guards: validate channel before livekit==nil check
- Add startup warning for external LiveKit webhook CIDR mismatch

DM system fixes:
- Emit dm_channel_close WebSocket event from REST close handler
- Re-open DM for caller when channel already exists
- Fix unread count incrementing for own messages and active DMs
- Reset channelBeforeDm after Back navigation (stale state bug)

New feature:
- Voice call duration timer in VoiceWidget (MM:SS / HH:MM:SS elapsed)
- Accent color restored on app startup (was only applied in settings)

Test infrastructure:
- Add DM tables to all test schemas (hubTestSchema)
- Inject test LiveKit client in voice handler tests (fixes 28 failures)
2026-03-27 16:14:54 +01:00
jevb 99dd25ec9a feat(server): add DM REST endpoints, WebSocket routing, and ready payload
Task A: REST API endpoints in api/dm_handler.go — POST /api/v1/dms
(create/get DM channel), GET /api/v1/dms (list open DMs), DELETE
/api/v1/dms/{channelId} (close DM). Routes registered in router.go.

Task B: WebSocket DM routing — handleChatSend, handleChatEdit,
handleChatDelete, and handleReaction all check channel type and use
participant-based auth for DM channels instead of role permissions.
DM messages delivered via SendToUser to each participant (bypassing
channel-subscription model). Auto-reopens DM for recipient on new
message with dm_channel_open event. New broadcastToDMParticipants
helper. New WS event types: dm_channel_open, dm_channel_close.

Task C: Ready payload includes dm_channels from GetUserDMChannels.
Also adds GetDMParticipantIDs helper to db/dm_queries.go.
2026-03-27 13:58:48 +01:00
jevb e938a80ac8 feat(server): add DM schema migration and database query layer
Add migration 008 with dm_participants and dm_open_state tables.
Add dm_queries.go with GetOrCreateDMChannel, GetUserDMChannels,
OpenDM, CloseDM, IsDMParticipant, and GetDMRecipient functions.
2026-03-27 13:46:06 +01:00
jevb 7404347a1d fix: address code review — 8 issues across server and client
Server fixes:
- voice_leave: broadcast voice_leave even on DB error so peers don't see ghost users (H1)
- migrate: record migration inside transaction for atomicity (H2)
- password: use init() with panic for dummyHash to catch bcrypt init failures (M1)
- password: replace //nolint:errcheck with explicit _ = discard (M2)
- handlers: clarify edit permission comment, fix error message wording (M3)
- auth_handler: distinguish duplicate username (400) from DB error (500) (M4)

Client fixes:
- media: revert YouTube oEmbed to browser fetch — no need to disable cert verification (C1)
- messages.store: fix prependMessages cap to keep newest messages, not oldest (H5)
- ChannelSidebar: ref-count globalDragAc to prevent multi-instance teardown race (H6)
- attachments: replace console.error with project logger (M6)
- ws: clarify lastSeq reset comment to match actual behavior (M5)
2026-03-24 21:35:40 +01:00
jevb e0437d4d8d feat: LiveKit migration — permissions, auth hardening, voice improvements
Pre-review snapshot of LiveKit migration changes including:
- Permission computation fix (allow-wins semantics)
- Timing-safe password comparison with dummy hash
- Rate limiter window fix
- Dev credential clearing for LiveKit
- Voice leave/join broadcast improvements
- Migration transaction wrapping
- Chat edit/delete permission guards
- TOTP verification endpoint
- Embed regex injection fix
2026-03-24 21:30:23 +01:00
jevb ce2b2369bf refactor: deduplicate pin handler and DB scan logic 2026-03-21 21:42:14 +01:00
jevb fe76034b7e feat: add server-side pin/unpin endpoints 2026-03-21 21:36:10 +01:00
jevb fc29968d3d fix: virtual scroll jumping with images/GIFs
Server:
- Extract image width/height on upload via image.DecodeConfig (header-only)
- Add width/height columns to attachments table (migration 007)
- Include dimensions in AttachmentInfo JSON (optional, backward-compatible)

Client:
- Reserve exact space for images using server-provided dimensions
- Fallback min-height: 200px for external/old images, cleared on load
- Remove premeasureAll() — was caching wrong heights for unloaded images
- Smart per-type height estimates: 32px dividers, 42/72px text, +220px
  per image attachment, +320px for YouTube embeds
- Batch ResizeObserver corrections to single RAF with anchor-based scroll
  preservation (topmost visible item stays in place)
- Fenwick tree for O(log n) offset lookups (replaces O(n) linear scans)
- CSS contain: layout style on .msg-image to isolate layout shift
2026-03-21 12:34:31 +01:00
jevb 7978ec40e8 fix: security hardening, LiveKit class refactor, and eng review fixes
Server:
- Fix YAML injection in LiveKit config generation (quote values)
- Revert token TTL to 4h (no server-side JWT revocation)
- Derive LiveKit publish permissions from user role (prevent SFU bypass)
- Add CAS guard for webhook/voice_leave race condition
- Add voice_leave broadcast to rollbackVoiceJoin (prevent ghost state)
- Limit webhook body to 64KB (prevent memory abuse)
- Add rate limit to voice_token_refresh handler (1/60s)
- Add LiveKit health check endpoint (GET /api/v1/livekit/health, 503 on degraded)
- Add voice_token_refresh WS handler for client-initiated token refresh
- Consolidate voice quality constants (single source of truth)
- Fix video limit TOCTOU race (count from DB instead of LiveKit API)
- Raise default voice_max_video from 10 to 25 (Discord parity)
- Add CountActiveCameras DB query
- Non-blocking broadcast send, circuit breaker, exponential backoff
- Close send channel before context cancel in serve.go
- Guard voice mute/deafen for active channel
- Delete orphaned message on attachment link failure
- Redact query string from proxy logs (prevent token leak)
- Use instance-level HTTP client for health checks (no redirect following)
- Set cmd.WaitDelay to prevent goroutine leak on Windows
- Log buildJSON marshal errors

Client:
- Refactor livekitSession.ts from singleton module to LiveKitSession class
- Share single AudioContext for all analysers (was 1 per participant)
- Extract createRoom() helper (DRY)
- Add token refresh timer (3.5h interval, re-arms on failure)
- Skip setSpeakers if unchanged (sort in-place, no allocations)
- Distinguish user-initiated leave from connection error in retry
- Add YouTube videoId validation (prevent iframe src injection)
- Add try/finally to disableCamera
- Wrap store subscription callbacks in try/catch
- Track and cancel initial scroll RAF on cleanup
- Add 5s timeout + encodeURIComponent to YouTube oEmbed fetch
- Clean raw mic stream on RNNoise suppressor failure
- Full voice cleanup on logout via cleanupAll()

Tests:
- Add 7 new server tests (webhook parsing, voice guards, quality fallback)
- Fix 2 pre-existing test failures (mute/deafen invalid payload)
2026-03-21 11:59:14 +01:00
jevb 3236918012 refactor: server hardening + client decomposition + protocol resilience
Server:
- Split monolithic voice_handlers.go into voice_join/leave/controls/broadcast
- Add metrics endpoint (admin-IP-restricted /api/v1/metrics)
- Add orphaned attachment cleanup in maintenance loop
- Add sentinel errors (db/errors.go, ws/errors.go)
- Add ring buffer for event replay on reconnect
- Add heartbeat monitoring with stale connection sweep
- Improve hub with panic recovery, graceful shutdown, seq tracking
- Typed message structs replace raw map[string]interface{}

Client:
- Decompose MainPage into ChatArea + SidebarArea controllers
- Add disposable.ts lifecycle management pattern
- Add member list right-click context menu (kick/ban/role)
- Tighten CSP (media-src, font-src, object-src, base-uri)
- Improve store with shallowEqual, 500-msg cap, batch updates
- Add search API endpoint wiring
- Fix LiveKit session cleanup and reconnection

Docs:
- Add CODEMAPS for architecture, backend, frontend, data, deps
- Add protocol-schema.json (machine-readable, 36 message types)
- Add platform research report
- Update PROTOCOL.md with seq/replay fields
2026-03-21 10:08:44 +01:00
jevb f3734bf827 fix: voice rejoin failure, SDP race, deafen bypass + add server voice logging
- Fix SDP signaling race condition: add per-client negoMu to serialize
  renegotiateParticipant / handleVoiceOffer / handleVoiceAnswer so
  concurrent OnTrack goroutines don't race through rollback
- Fix handleVoiceLeave triple-fire: early return when clearVoice()
  returns zeros so ICE callbacks don't re-enter and corrupt state
- Fix SQLite SQLITE_BUSY errors: add busy_timeout=5000 pragma and
  SetMaxOpenConns(1) for file-based databases
- Fix deafen bypass: new remote audio elements now respect localDeafened
  state so late-arriving streams are muted immediately
- Add debug-level logging for SDP negotiation, track fan-out, ICE
  candidates, voice state changes, room lifecycle, and participant
  add/remove
- Bump version to 1.1.1
2026-03-19 21:35:18 +01:00
jevb 851a8b8b21 fix: add rows.Err() check in GetAttachmentsByMessageIDs
Prevents silently returning truncated attachment maps when the DB
cursor errors mid-iteration. Matches the pattern used in all other
query loops across the codebase.
2026-03-19 04:27:11 +01:00
jevb a7df9c2b3c fix: resolve 5 remaining medium/low issues from third-pass go-review
- NEW-1: Add rows.Err() check in ListMembers to catch cursor errors
- NEW-2: Add minVal parameter to queryInt so offset=0 is not rejected
- NEW-3: Fix copyFile double-close by removing defer, using explicit
  close on both success and error paths
- NEW-4: Add GetAllChannelPermissionsForRole batch query, eliminating
  N+1 GetChannelPermissions calls in channel list and search handlers
- NEW-5: Cap fetchBody with io.LimitReader(1 MiB) to prevent memory
  exhaustion from malformed release assets
2026-03-19 04:19:03 +01:00
jevb 13797e7075 fix: resolve 6 medium issues from full go-review
- MED-1: Document soundboard channelID=0 server-wide permission intent
- MED-2: Document os.Exit(0) in update handler skipping deferred cleanup
- MED-3: Replace os.ReadFile/WriteFile with streaming io.Copy in backup
  restore to avoid loading entire DB into memory
- MED-4: Add GetAllVoiceStates bulk query, eliminating N+1 per-channel
  queries in collectAllVoiceStates
- MED-5: Wrap handlePatchSettings updates in a transaction for atomicity
- MED-6: Add rows.Err() check after scan loop in getReactionsBatch
- MED-9: Replace manual port-stripping in serverHost with net.SplitHostPort
  for correct IPv6 handling
2026-03-19 04:04:53 +01:00
jevb 65a8403a92 fix: resolve 1 critical and 5 high security/reliability issues from go-review
- CRIT-2: Prevent HTTP header injection in Content-Disposition via mime.FormatMediaType
- HIGH-1: Call hub.GracefulStop() on server shutdown to clean up WebSocket/voice state
- HIGH-2: Remove double send on serveErr channel that caused goroutine leak
- HIGH-3: Handle GetUserByID error after registration to prevent nil panic
- HIGH-4: Return nil,nil on sql.ErrNoRows in GetAttachmentByID (matching codebase convention)
- HIGH-5: Add voiceDone channel to bound RTP goroutine lifetime on voice teardown
- Fix handleVoiceICE to return VOICE_ERROR when no PC (consistent with other voice handlers)
- Update tests to match corrected behavior
2026-03-19 03:53:40 +01:00
jevb 13be0fd6d3 feat: file uploads, URL previews, emoji search, voice mute fixes, UX improvements
Server:
- Add POST /api/v1/uploads and GET /api/v1/files/{id} endpoints
- Add CreateAttachment DB method for file upload records
- Allow empty message content when attachments are present
- CORS headers on file serving for WebView2 compatibility

Client — File uploads & attachments:
- Clipboard paste (Ctrl+V) and attach button (+) for file uploads
- Preview bar above input with thumbnail, spinner, and remove button
- Images fetched via Tauri HTTP plugin as base64 data URIs (bypasses
  self-signed cert rejection in WebView2)
- Three-layer image cache: memory → IndexedDB → network
- In-flight deduplication prevents duplicate concurrent fetches
- Image lightbox with click-to-zoom, scroll wheel zoom, pan, keyboard shortcuts

Client — URL previews & embeds:
- URLs in messages rendered as clickable links
- YouTube embeds with thumbnail, play button, video title via oEmbed API
- Generic link previews with OG metadata (title, description, image)
- Fetched via Tauri HTTP plugin with Facebook crawler User-Agent
- YouTube title cache and OG metadata cache prevent re-fetch on re-render
- Links open in default browser via tauri-plugin-opener

Client — Voice & audio fixes:
- Mute uses replaceTrack(null) for reliable RTP-level muting in WebView2
- Deafen also mutes mic; undeafen/unmute unmutes both
- Muted users show crossed mic icon, deafened show crossed mic + headphone
- Re-apply mute state after input device switch

Client — UX improvements:
- Disable browser context menu globally (only custom menus show)
- Emoji search now matches by keyword names (smile, heart, fire, etc.)
- Emoji picker closes on click outside
- User bar status text moved below username
- Messages sorted chronologically (oldest first, newest at bottom)
- Scroll to bottom on initial load with deferred retries for layout shifts
- Image attachments constrained to 400x350px with click-to-lightbox
2026-03-18 14:13:52 +01:00
jevb fca1b0cc0c test: boost server test coverage to 80%+ across all packages
Add comprehensive tests for db, storage, updater, and ws packages
covering edge cases, error paths, and voice handler functions.
New test files for attachment queries and WebSocket coverage boost.
2026-03-18 05:03:38 +01:00
jevb 6f35973c1b feat: TOFU cert pinning, voice channel sidebar, scroll-to-message, profiles, and review fixes
- Add cert mismatch modal for TOFU certificate pinning
- Separate voice channels from text in sidebar with user lists
- Implement scrollToMessage and jump-to-pinned-message in overlay
- Add server profiles with credential auto-fill on connect page
- Fix credential auto-fill race condition on rapid profile clicks
- Add channel_focus event for channel-scoped message delivery
- Fix member list case-insensitive role filtering
- Server normalizes role names to lowercase for protocol consistency
- Remove redundant permission-denied log in handleChannelFocus
- Voice store: bulk set states from ready payload, leave cleanup
- WebSocket reconnect and structured logging improvements
- Add tests for cert modal, overlay managers, voice sidebar,
  message list scroll, quick switcher, and profile management
2026-03-17 20:25:15 +01:00
jevb 45b720811e fix: address PR #15 review issues (#16-#23)
- #16: Fix golangci-lint issues (unchecked Close(), unused funcs, naming)
- #17: Add KeybindsTab and LogsTab unit tests (19 tests, 81%+ coverage)
- #18: Add rate limiting to chat_edit and chat_delete handlers
- #19: Fix cert mismatch handling via event listener instead of string match
- #20: Validate SHA-256 colon-hex fingerprint format in Rust ws_proxy
- #21: Optimize session+ban check with single JOIN query
- #22: Sort channels by position when redirecting after deletion
- #23: Rename admin test files for clarity

Also fixes ban expiry regression (H-1 from code review) by using
auth.IsEffectivelyBanned() to properly respect temporary ban expiry.
2026-03-17 14:54:50 +01:00
jevb ce4326766a fix: resolve all golangci-lint issues blocking CI server build
Fix 70+ errcheck violations by adding explicit error discards (`_ =`)
for unchecked return values across test helpers and deferred Close()
calls. Remove unused `senderID` field from broadcastMsg and unused
`defaultCleanupMaxWindow` const. Apply De Morgan's law, remove empty
branch, and simplify redundant type declaration per staticcheck.
2026-03-17 08:09:52 +01:00
jevb 9c1d99683c fix: address PR review findings (issues #9-#14)
- Fix capacity over-allocation and use strings.Builder in getReactionsBatch (#9)
- Replace `any` types and cache Tauri invoke in window-state.ts (#10)
- Remove custom `contains` helper, fix NilHub tests to pass nil (#11)
- Add nil guards before hub method calls in admin handlers (#12)
- Run golangci-lint v2: modernize interface{}/any, range-over-int loops,
  remove dead code, fix errcheck, add .golangci.yml config (#13)
- Add 23 client unit test suites (694 tests), exclude Tauri-coupled
  files from coverage, achieve 80%+ threshold (#14)

Closes #9, closes #10, closes #11, closes #12, closes #13, closes #14
2026-03-17 04:11:04 +01:00
jevb 1b596367c4 fix: address PR review findings (issues #3-#8)
- Fix double-close panic in Hub.Stop/GracefulStop using sync.Once (#3)
- Bump golangci-lint action to v9 with v2.11.3 for Go 1.25 support (#4)
- Add input validation guards to SearchMessages (#5)
- Handle promise rejections in InviteManager with error toasts (#6)
- Add missing reply_to and edited_at columns to admin test schema (#7)
- Add ClientCount to HubBroadcaster interface and wire into stats endpoint (#8)
2026-03-17 03:20:37 +01:00
jevb 8f4349ba42 feat: server enhancements, client test selectors, and UI polish
Server:
- Add message search and pinned messages support
- Add admin hub integration and live connection stats
- Update admin test mocks for hub interface

Client:
- Add data-testid attributes to components for E2E testing
- Add window management capabilities (position, size, maximize)
- Add prod E2E test config and script
- Fix CSS imports (use vite bundling instead of HTML link tags)
- Add inline styles to InviteManager overlay for reliability
- Update CHATSERVER.md references from WPF to Tauri

Docs:
- Update quick-start guide
2026-03-17 02:56:19 +01:00