Commit Graph
845 Commits
Author SHA1 Message Date
Claude f66d6c5274 refactor(server/db): delegate messages + reactions to dbgen; finalize D2
messages/reactions: CreateMessage, GetMessage (messageFromGen mapper),
EditMessage (EditMessageContent), DeleteMessage (SoftDeleteMessage),
AddReaction, RemoveReaction, GetReactions (GetReactionCounts),
SetMessagePinned, UpdateReadState. Retired the obsolete scanMessage.

Kept raw by design (no clean sqlc mapping): FTS search, cursor-paginated
GetMessages/GetMessagesForAPI/GetPinnedMessages, getReactionsBatch,
GetChannelUnreadCounts, GetLatestMessageID (interface{} MAX result).

D2 status: 97 db.DB methods now delegate to dbgen across every domain;
43 raw d.sqlDB calls remain by design (db.go passthroughs, migrate.go,
variable-length IN(), FTS, multi-statement transactions, PRAGMA/VACUUM).
sqlc is no longer dead code — audit A-2026-07-05 resolved. Full rationale
+ the kept-raw list in docs/plans/sqlc-adoption.md.

Verified: go build ./...; go test ./db ./service ./ws ./auth; sqlc-verify;
gofmt + go vet clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UA17KPvqGBX3XbXYnMf1rA
2026-07-19 15:48:39 +00:00
Claude c7e6702c8c refactor(server/db): delegate voice, dm, channels, admin to dbgen (D2)
voice: all reads (GetVoiceState, GetChannelVoiceStates, GetAllVoiceStates)
and writes (join/leave/mute/deafen/camera/screenshare/clear, capacity +
camera-limit atomic guards); CountChannelVoiceUsers stays raw (no query).
dm: OpenDM, CloseDM, IsDMParticipant, GetDMParticipantIDs; GetOrCreateDMChannel
(serializable tx), GetUserDMChannels (aggregate), GetDMRecipient stay raw.
channels: List/Get (shared channelFromFields mapper), Create/Update/Delete,
slow-mode/max-users setters, permission overrides get/list-for-role/upsert/
delete; ListChannelRoleOverrides + GetChannelTypes (variable IN) stay raw.
admin: UserCount, GetServerStats counts (PRAGMA stays raw), ListAllUsers,
UpdateUserRole, ForceLogoutUser, GetUserSessions, AdminUpdateChannel,
AdminDeleteChannel, LogAudit, GetAuditLog, GetSetting, SetSetting,
GetAllSettings, CountUsersWithoutTOTP; AdminCreateChannel + Backup stay raw.

Added b2i64 and strToNullPtr mapper helpers; retired the obsolete
scanChannel/nullableString. Behavior and public signatures unchanged.

Verified: go build ./...; go test ./db ./service ./permissions ./admin;
sqlc-verify.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UA17KPvqGBX3XbXYnMf1rA
2026-07-19 15:43:46 +00:00
Claude d769b8bdb6 refactor(server/db): delegate invites + attachments to dbgen (D2)
invites: CreateInvite, GetInvite, UseInviteAtomic, RevokeInvite,
ListInvites. attachments: CreateAttachment, GetAttachmentByID,
GetAttachmentWithChannel, DeleteOrphanedAttachments. Added ptrI64toI /
ptrItoI64 helpers for the *int64<->*int narrowing (invite max_uses,
attachment width/height). LinkAttachmentsToMessage and
GetAttachmentsByMessageIDs keep raw SQL (variable-length IN() lists sqlc
can't express). Behavior and signatures unchanged.

Verified: go build ./...; go test ./db (Invite, Attachment).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UA17KPvqGBX3XbXYnMf1rA
2026-07-19 15:36:59 +00:00
Claude e46bd0e015 refactor(server/db): delegate users, sessions, profile to dbgen (D2)
Convert the auth_queries.go user + session reads/writes and
profile_queries.go to the sqlc-generated layer, adding shared
userFromGen/sessionFromGen mappers (db/mappers.go) for the
int64/*string -> int/bool/string domain-model narrowing.

Delegated: GetUserByID, GetUserByUsername, UpdateUserStatus,
UpdateUserTOTPSecret, ResetAllUserStatuses, BanUser, UnbanUser,
ListMembers, CreateSession (EvictOldestSessions + InsertSession),
GetSessionByTokenHash, GetSessionWithBanStatus, DeleteSession,
DeleteOtherSessions, DeleteExpiredSessions, TouchSession,
UpdateUserProfile, UpdateUserPassword, ListUserSessions,
DeleteSessionByID (query changed to :execresult so the RowsAffected
ErrNotFound check is preserved). Behavior and public signatures unchanged.

Verified: go build ./...; go test ./db ./service ./auth; sqlc-verify.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UA17KPvqGBX3XbXYnMf1rA
2026-07-19 15:34:27 +00:00
Claude 44323373e3 refactor(server/db): adopt sqlc as the query layer — phase 1 (D2)
Wire the sqlc-generated dbgen package into db.DB so it stops being dead
code (audit A-2026-07-05) and becomes the real, CI-verified query layer.

db.DB now holds a *dbgen.Queries (initialized in Open via dbgen.New).
Query method bodies delegate to it; sqlc owns the SQL text and parameter
binding (make sqlc-verify), while db keeps its stable public API and
domain model types so no caller in api/admin/ws/service changes. The
migration is incremental — a method either delegates to d.q.* or still
runs raw SQL — so both layers are correct during the transition.

Converted domains (now load-bearing through sqlc):
- blocks: BlockUser, UnblockUser, IsBlocked, IsEitherBlocked,
  ListBlockedUsers (added the query to blocks.sql + regenerated).
  Empty ListBlockedUsers now returns []int64{} instead of nil, matching
  the MemStore backend — a latent inconsistency fixed, not a regression.
- lockouts: UpsertLockout, LoadActiveLockouts, CleanupExpiredLockouts,
  DeleteLockout (RFC3339 time formatting/parsing kept in the wrappers).
- roles: GetRoleByID, ListRoles, GetRoleForUser via a shared roleFromGen
  mapper (int64 position/is_default -> int/bool). GetUserWithRole stays
  raw for now.

Remaining domains stay on raw SQL and are tracked in
docs/plans/sqlc-adoption.md; store/ event+plugin SQL is intentionally
excluded (that layer is removed in D3). Decisions doc + audit closure
updated (A-2026-07-05 -> in progress).

Verified: go build ./...; go test -race ./db ./service ./auth ./ws (api
green non-race, race run matches CI's -timeout 20m); make sqlc-verify and
protocol-verify pass with the regenerated output committed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UA17KPvqGBX3XbXYnMf1rA
2026-07-19 15:01:54 +00:00
J3vb 752c8f2bcc Merge pull request #1186 from J3vb/claude/blueprints-architectural-audit-k927qb
Client HTTP TOFU proxy: cert-pin the REST path (closes A-2026-07-02)
2026-07-19 16:28:16 +02:00
Claude dab4d73e09 feat(client): TOFU HTTP proxy for REST — close audit A-2026-07-02 (D5)
The REST path previously used tauri-plugin-http with
danger.acceptInvalidCerts, so it accepted ANY certificate while the WS
and LiveKit paths were TOFU-pinned in Rust — and the bearer token rides
every REST request. This routes REST through a new Rust loopback
TCP->TLS proxy that pins the server certificate to the same
trust-on-first-use fingerprint as the WS proxy.

Rust (src-tauri):
- New http_proxy.rs: per-host loopback tunnels (HttpProxyState map);
  per-connection TOFU via CaptureVerifier + tofu_check, sharing
  ws_proxy's cert store (cert_store_key) and emitting the same
  cert-tofu events (first-use banner / mismatch modal). First request's
  Host is rewritten and Connection: close injected so one request rides
  each connection. Mismatch returns a clean 502 to the loopback fetch.
- Register HttpProxyState + start_http_proxy/stop_http_proxy in lib.rs.
- Drop the dangerous-settings feature from tauri-plugin-http.

TypeScript (src):
- New lib/httpProxy.ts: ensureHttpProxy(host) (per-host cache +
  concurrent-start dedup) / stopHttpProxy(host).
- api.ts, profiles.ts (health), attachments.ts (image + download) resolve
  server URLs to http://127.0.0.1:{port}; remove the allowSelfSigned
  config field and every acceptInvalidCerts block. External hosts (CDNs,
  OG previews, YouTube) keep normal TLS validation.
- main.ts constructs the API client without allowSelfSigned.
- capabilities/default.json: allow http://127.0.0.1:* fetch scope.

Tests:
- New tests/unit/http-proxy.test.ts (cache, dedup, stop/restart).
- api.test.ts and attachments-render.test.ts: mock httpProxy, replace the
  acceptInvalidCerts assertions with proxy-origin assertions.

Verified: tsc --noEmit clean; new + affected vitest suites green
(176 tests); the http_proxy pure logic (host validation, header rewrite)
passes as standalone Rust unit tests; oxlint/eslint counts unchanged
from HEAD; prettier clean. The full Tauri build (cargo) requires GUI
system libs not present in this environment and runs on CI/real runners.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UA17KPvqGBX3XbXYnMf1rA
2026-07-19 14:26:25 +00:00
J3vb 4bb388c738 Merge pull request #1185 from J3vb/claude/blueprints-architectural-audit-k927qb
Audit follow-through: decisions, quick wins, protocol codegen, spec refresh, Solid removal
2026-07-19 16:07:38 +02:00
Claude 8c768bf4c6 docs: add design note for the client HTTP TOFU proxy (D5)
Pins the implementation approach for closing audit finding A-2026-07-02:
a loopback TCP-to-TLS tunnel reusing the livekit_proxy pattern and the
shared per-host fingerprint store, with ws_proxy-equivalent TOFU
first-trust/mismatch flows (required because the first TLS contact with
a server is the login HTTP request), per-host tunnel lifecycle for
multi-profile health polling, and removal of the acceptInvalidCerts
path plus the dangerous-settings feature flag as the ratchet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UA17KPvqGBX3XbXYnMf1rA
2026-07-19 13:58:58 +00:00
Claude bf508c9e6b chore(client): remove abandoned SolidJS beachhead (D6)
The Solid.js migration was abandoned (per CHANGELOG); the 154-LOC
beachhead and its scaffolding remained in-tree, leaving two UI paradigms
for contributors. Removed:

- src/components/solid/ (Badge, ChannelListItem, PluginContainer — none
  imported by production code)
- src/lib/solidMount.ts and src/lib/solidAdapter.ts
- tests/setup-solid.ts and tests/setup-solid.test.tsx
- vite-plugin-solid from vite.config.ts and vitest.config.ts (and the
  now-unneeded tsx test include + setupFiles)
- jsx/jsxImportSource from tsconfig.json
- solid-js, @solidjs/testing-library, vite-plugin-solid from package.json

docs/client-architecture.md (which described the SolidJS design) is
retired to a pointer at docs/architecture/client.md; README links
updated. Audit A-2026-07-12 and decision D6 marked closed.

Verified: tsc --noEmit clean (previous 3 test-file errors were caused by
the Solid jsx config and are gone); oxlint/eslint error counts identical
to HEAD (pre-existing); vitest runner healthy on a sample suite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UA17KPvqGBX3XbXYnMf1rA
2026-07-19 13:57:28 +00:00
Claude ca3b0fee3e docs: refresh api.md, protocol.md, schema.md against current code (D7)
One-PR spec refresh per decision D7, using the 2026-07-19 audit's
conformance matrix as the checklist:

api.md
- Remove the deleted version field from /health and /api/v1/info
  (anti-fingerprinting C-2) and document the removal.
- Document the profile surface (PATCH /users/me, PUT /users/me/password,
  GET/DELETE /users/me/sessions), the user-blocks surface
  (GET/PUT/DELETE /api/v1/blocks), and the plugin admin surface
  (/api/v1/admin/plugins).
- Correct GET /api/v1/files/{id}: auth is required and caching is
  'private, no-cache' (was documented as public/immutable, unauthenticated).
- Add search (30/min) and upload (10/min) rate limits; note announcement
  channel type as planned-only.

protocol.md
- Document auth_ok.replay_source and the 3-tier reconnect replay
  (ring buffer -> events table -> full resync) with the visibility
  watermark.
- Add the Voice End-to-End Encryption section (voice_e2ee_announce/offer
  in both directions, key-holder semantics, rate limits) and
  voice_token.is_key_holder.
- Document user_update; mark voice_speakers and member_leave as defined
  but not currently emitted; extend voice_config fields.
- Update the reference tables (19 client->server / 30 server->client)
  and point them at protocol-schema.json as the generated inventory.

schema.md
- Correct the migration history to the real 001-015 numbering.
- Document the previously missing tables: login_attempts, settings,
  emoji, sounds (dead schema), rate_lockouts, user_blocks, events,
  plugins, plugin_kv; add attachments.uploader_id and new indexes.
- Fix the channel-type list to text/voice/dm (013 triggers) and correct
  the permission formula to (base & ~deny) | allow to match
  permissions.EffectivePerms.
- Note the sqlc/dbgen layer and link the architecture data-model doc.

Also correct the audit_log_v6 claim (transient rename inside migration
003, not a coexisting table) in the audit and data-model blueprint, and
update the audit/decisions trackers (A-2026-07-03 closed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UA17KPvqGBX3XbXYnMf1rA
2026-07-19 13:51:44 +00:00
Claude 2e7a80171b feat(server,client): protocol codegen + audit quick-wins batch
Protocol codegen (decision D4, audit A-2026-07-08):
- Add docs/protocol-schema.json as the real single source of truth for
  WS message-type constants, making the long-standing 'generated from'
  comment in both constant files true.
- Add Server/scripts/genprotocol, a generator emitting both
  Server/ws/message_types.go and Client .../lib/protocolTypes.ts
  (constants byte-for-byte value-identical to before; only headers,
  ordering alignment, and provenance comments changed).
- Add make protocol-generate / protocol-verify and wire protocol-verify
  into CI next to sqlc-verify.

Quick wins (decision D8):
- admin: log LogAudit write failures in the backup handlers instead of
  discarding them (prior audit #10).
- api: fix self-contradictory upload Cache-Control to 'private,
  no-cache' per remediation plan W3-4; drop the now-unused
  fileCacheMaxAgeSeconds constant; update test.
- ws: route the hub settings cache through db.GetSetting instead of
  inline SQL.
- ws: fix a latent data race — main.go wires SetEventPersister and
  SetEventStore after NewRouter has already started the hub Run loop,
  which reads those fields on the broadcast/replay paths. They (and
  pluginSink, which one test sets post-Run) are now atomic pointers;
  the remaining pre-Run-only setters reject late calls with an error
  log instead of racing silently.

Update the audit closure table and decisions doc statuses accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UA17KPvqGBX3XbXYnMf1rA
2026-07-19 13:32:58 +00:00
Claude fde254cea0 docs: record maintainer decisions for the 2026-07-19 audit
Add docs/plans/audit-2026-07-19-decisions.md capturing the eight decisions
taken on the audit's open questions (announcement channels: implement;
data layer: adopt sqlc + remove store/; protocol constants: real codegen;
client HTTP TOFU pinning: next security work; Solid beachhead: delete;
specs: one refresh PR after codegen) with sequencing, and mark the greenlit
items (protocol codegen + quick-wins batch).

Update the closure table in docs/audit-2026-07-19.md from OPEN to DECIDED
for the seven findings these decisions cover.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UA17KPvqGBX3XbXYnMf1rA
2026-07-19 12:51:06 +00:00
J3vb 145132d203 Merge pull request #1184 from J3vb/claude/blueprints-architectural-audit-k927qb
docs: architecture blueprints + 2026-07-19 spec-conformance audit
2026-07-19 14:43:02 +02:00
Claude 522f996fd7 chore: retrigger CI
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UA17KPvqGBX3XbXYnMf1rA
2026-07-19 12:19:45 +00:00
Claude cf8e5aae24 docs: add architecture blueprints and 2026-07-19 audit
Add docs/architecture/ — a curated blueprint set with 10 Mermaid diagrams
covering system context, deployment topology, server package map, REST
request lifecycle, WebSocket auth/replay/dispatch, the full data model
(migrations 001-015), voice/E2EE flow, and the client module map.

Add docs/audit-2026-07-19.md — successor to audit-2026-04-07.md:
re-verifies carried-over findings, catalogues spec-vs-code drift in
api.md/protocol.md/schema.md (incl. the announcement channel-type
contradiction and the undocumented voice-E2EE protocol surface), records
server/client/CI findings with file:line evidence, and closes with a
12-item prioritized improvement backlog.

Link both from the README docs index.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UA17KPvqGBX3XbXYnMf1rA
2026-07-19 12:18:20 +00:00
J3vb 7f3e5d18d8 Merge pull request #1183 from J3vb/claude/repo-issues-features-w6r87a
Fix open issues: window restore, Wayland, password reveal, screenshare volume/FPS, dual-homed voice, private channels
2026-07-19 13:30:20 +02:00
Claude 8c590c0b6d fix(server): harden private-channel visibility propagation
Three review findings on the #93 feature:

- RefreshChannelVisibility targeted clients by their connect-time role
  snapshot; a user whose role changed mid-session was evaluated against the
  stale role. Resolve the current role from the DB per client (fail closed).
- Visibility updates are targeted, unsequenced messages, so a client that
  disconnected before an override change and later resumed via replay never
  converged (stale sidebar until a fresh connect). Track a visibility-change
  sequence watermark and force resumes from at/before it onto the
  full-ready path.
- The admin SPA interpolated channel/user names into single-quoted JS
  strings inside onclick attributes with HTML-escaping only; a name
  containing a quote broke out of the string literal (XSS in the admin
  panel, reachable by any user allowed to create channels). Add a jsq()
  helper (JS-escape then HTML-escape) and use it for every onclick name
  interpolation.

Follow-up to #93.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwtnpHAoSFr1ZibQgQkNQK
2026-07-19 11:20:15 +00:00
Claude ccb8c54dd2 fix(server): regenerate empty livekit.yaml instead of treating it as user-managed
A zero-byte or whitespace-only livekit.yaml (truncated write, touch(1)
placeholder) has no auto-generated marker and was permanently treated as a
user-managed config, wedging LiveKit startup with an empty config file.

Follow-up to #111.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwtnpHAoSFr1ZibQgQkNQK
2026-07-19 11:20:14 +00:00
Claude d576f06aa1 fix(client): make source-quality screen share FPS take effect at capture
createLocalScreenTracks injects a default 1080p30 resolution when none is
set and mutates the passed options object, so (a) a 'source' share was
captured at 30 fps regardless of the FPS setting, with only a best-effort
applyConstraints afterwards, and (b) the shared 'source' preset object was
permanently mutated after the first share. Capture options are now always
copies; 'source' with an explicit 60/120 override passes a zero-size
resolution sentinel (uncapped in livekit's constraint translation) with the
frame rate in the raw video constraints, so the fps applies at
getDisplayMedia time.

Follow-up to #115.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwtnpHAoSFr1ZibQgQkNQK
2026-07-19 11:20:01 +00:00
Claude 1201992ab0 fix(client): skip window-state save while minimized
A minimized window reports placeholder coordinates (-32000 on Windows); the
move event fired by minimize was persisting them, so quitting while
minimized silently discarded the remembered position (the new off-screen
validation then falls back to centered). Skip the save while minimized so
the last real geometry survives.

Follow-up to #124.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwtnpHAoSFr1ZibQgQkNQK
2026-07-19 11:20:00 +00:00
Claude 3cb8dc34d5 chore(server): drop unnecessary int64 conversion in perms test
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwtnpHAoSFr1ZibQgQkNQK
2026-07-19 11:03:59 +00:00
Claude 5ac407a71a test(client): account for the screen share FPS select in settings tests
Follow-up to the screen share FPS setting (#115).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwtnpHAoSFr1ZibQgQkNQK
2026-07-19 10:50:11 +00:00
Claude 9e6ff47194 feat(server,admin): private channels via per-role permission overrides
The read side of channel visibility was already complete — channel_overrides
rows deny ReadMessages and every list/focus/send/voice path enforces them —
but nothing could write those rows. Add the missing write path and UI:

- db: UpsertChannelOverride / DeleteChannelOverride / ListChannelRoleOverrides
  (roles LEFT JOIN overrides so the UI gets everything in one call)
- admin API: GET/PUT/DELETE /admin/api/channels/{id}/permissions[/{roleId}]
  with unknown permission bits masked via the new permissions.AllPerms,
  audit logging, and immediate permission-cache invalidation
- ws: Hub.RefreshChannelVisibility sends targeted channel_create /
  channel_delete to connected clients after an override change, unsubscribes
  hidden clients from the channel topic, and clears their focus. Sent outside
  the sequenced replay path on purpose: a replayed channel_delete would be
  filtered by the post-change allowed-channel set, inverting its audience.
- admin panel: per-channel Access modal (lock icon) with per-role
  "Can access" checkboxes; unchecking writes deny = ReadMessages|ConnectVoice

Known limits (follow-ups): users offline during a revoke keep a stale
sidebar entry until their next fresh connect (server still denies access),
and users already in a voice channel are not kicked when it goes private.

Closes #93

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwtnpHAoSFr1ZibQgQkNQK
2026-07-19 10:50:11 +00:00
Claude b9180bdbeb feat(server): support dual-homed voice hosts and user-managed livekit.yaml
Servers reachable via both a LAN IP and a public IP could only serve voice
on one of them: config.yaml accepts a single voice.node_ip and OwnCord
regenerates data/livekit.yaml on every start, discarding manual edits.
LiveKit has no multi-IP list, but it does support advertising internal host
candidates alongside the external mapping.

- New voice.advertise_internal_ip (OWNCORD_VOICE_ADVERTISE_INTERNAL_IP):
  emits rtc.advertise_internal_ip: true so LAN clients get a reachable
  candidate while remote clients keep using node_ip.
- livekit.yaml escape hatch: if the file exists without the auto-generated
  marker header, OwnCord leaves it untouched, giving operators access to
  every LiveKit option (ips.includes, interfaces, stun_servers, ...). The
  generated header documents how to take ownership.

Closes #111

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwtnpHAoSFr1ZibQgQkNQK
2026-07-19 10:50:11 +00:00
Claude 6f0a04113f feat(client): screen share FPS setting with 60 and 120 fps options
Screen share frame rate was hardcoded per quality (5/15/30). Add a
"Screen Share FPS" setting (30 default / 60 / 120) next to Stream Quality:

- 30 keeps the existing per-quality caps unchanged
- 60/120 override the capture constraints and publish maxFramerate for all
  qualities, with bitrate scaled 1.5x/2x to keep the image sharp
- "source" quality (no fixed resolution) applies the fps to the live
  capture track via applyConstraints, best-effort

Actual delivered fps still depends on what the capture source and display
can sustain.

Closes #115

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwtnpHAoSFr1ZibQgQkNQK
2026-07-19 10:50:11 +00:00
Claude 85f05e999c fix(client): make the screenshare volume slider actually change volume
Three defects made the screenshare tile's volume slider ineffective:

- The 0-200 slider mapped to element volume /200 clamped to [0,1], while
  the element attached at 1.0 — dragging the upper half did nothing. The
  screenshare slider is now 0-100 with 100 = 1.0 (HTMLAudioElement.volume
  cannot exceed 1.0; mic tiles keep the 0-200 boost range via LiveKit's
  GainNode-backed setVolume).
- Setting a volume before the screenshare audio track attached was silently
  dropped. The per-user volume now persists independently of the element
  map and is applied on attach.
- Changing the master output volume overwrote per-user screenshare volumes
  with just the master multiplier; they now scale together.

The slider and mute button also initialize from persisted state when a tile
is rebuilt, and unmuting via the button re-applies the restored volume.

Fixes #121

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwtnpHAoSFr1ZibQgQkNQK
2026-07-19 10:50:10 +00:00
Claude 3eb990165d fix(client): hide native WebView2 password reveal icon
WebView2/Edge renders its own password-reveal eye inside password inputs,
stacking with the app's custom toggle on the login form. Hide the native
::-ms-reveal / ::-ms-clear controls globally.

Fixes #123

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwtnpHAoSFr1ZibQgQkNQK
2026-07-19 10:50:10 +00:00
Claude 82893e036b fix(client): start reliably on Wayland; degrade PTT without X11
On Wayland sessions (notably GNOME + NVIDIA), WebKitGTK's DMABUF renderer
can crash or render a blank window, so the client failed to start. Set
WEBKIT_DISABLE_DMABUF_RENDERER=1 on Wayland unless the user has already set
it themselves.

device_query's global key state needs an X11/XWayland display and panicked
per poll on pure-Wayland setups. Use DeviceState::checked_new() so push-to-
talk degrades to inactive with a single warning instead.

Fixes #96

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwtnpHAoSFr1ZibQgQkNQK
2026-07-19 10:50:10 +00:00
Claude 54430eed45 fix(client): validate saved window position against connected monitors
Restoring a stale window position (e.g. from a disconnected monitor) placed
the window off-screen with no way to see it. Before applying the saved
position, check that the rect is reachable on some monitor reported by
availableMonitors(): at least 100px of horizontal overlap and a grabbable
title bar row. If not, keep the default centered placement. Also reject
non-finite or non-positive saved dimensions. If monitors cannot be queried,
restore proceeds unchanged.

Fixes #124

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwtnpHAoSFr1ZibQgQkNQK
2026-07-19 10:50:10 +00:00
J3vbandClaude Fable 5 9a0404cb6f docs: refresh README badges, platform matrix, and branch policy for alpha.2
- Replace static badge block with CI, release, status, Go, Tauri,
  platforms, and license badges (release badge tracks the public
  OwnCord-releases repo)
- Update platform support table for v1.1.0-alpha.2 assets (Linux x64
  server, Linux x64/ARM64 client)
- Stamp build examples with the current version
- Point contributing docs at main now that dev is pruned

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 11:29:04 +02:00
J3vbandClaude Fable 5 66cdd2ad9f chore(release): stamp client version 1.1.0-alpha.2
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 10:50:37 +02:00
J3vb 8e62f38d25 Merge pull request #1182 from J3vb/fix/security-hardening-review
P1: security-hardening remediation (Postgres deletion + W1-1..W2-7)
2026-07-19 10:49:31 +02:00
J3vbandClaude Fable 5 963bea9a64 test(service): update moderation callsites for context parameter
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 10:18:40 +02:00
J3vbandClaude Fable 5 bc7d65ab29 fix(service): thread request context through BanUser/UnbanUser
contextcheck (CI lint) flagged the admin handler calling BanUser without
the request context — the service opened its telemetry span from
context.Background(), detaching the ban from its request trace. Both
moderation entrypoints now take ctx; the span joins the caller's trace.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 10:18:40 +02:00
J3vbandClaude Fable 5 4c2fecbf02 test(ws): update emit-test hub literal for the single event channel
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 09:33:33 +02:00
J3vbandClaude Fable 5 0baccb58ee fix(ws): process register/unregister on one ordered channel
Register and Unregister travelled on two separate channels, and Run's
select picks randomly when both are ready: a fast connect/disconnect could
process the unregister first (a silent no-op for a not-yet-known client)
and then the register — admitting an already-dead connection as a ghost
client that held presence and swallowed broadcasts until the stale sweep
reaped it minutes later. One tagged event channel preserves each
connection's Register→Unregister submission order, making the inversion
structurally impossible. Found via TestHub_ConcurrentRegisterUnregister
failing the P1 gate under -race on windows-latest (2 ghosts after churn);
that test now settles in milliseconds instead of polling out its deadline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 09:33:33 +02:00
J3vbandClaude Fable 5 2eec831d6a refactor(updater): export FileSHA256 and reuse it for the update snapshot (W3-2)
admin's fileSHA256 duplicated VerifyChecksum's hashing body. One exported
helper now serves both the TOCTOU snapshot in handleApplyUpdate and
VerifyChecksum itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 09:04:28 +02:00
J3vbandClaude Fable 5 9d44942b04 test(service): lock CanPost DM-block and permission refusals (W2-7)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 09:03:03 +02:00
J3vbandClaude Fable 5 b329a61a7c fix(ws): route plugin broadcasts through the service-layer send check (W2-7)
requireChannelBroadcastAccess went through RequireChannelAccess, whose DM
branch checks only participant membership — a blocked user's plugin
broadcast could reach the person who blocked them — and it issued a raw
GetRoleByID per broadcast, bypassing the permission cache. The gate now
delegates to MessageService.CanPost (extracted over checkSendPermission),
so DM blocks, channel permissions, and future posting policy apply from
exactly one place; fails closed when no service is wired. First brick of
the permission-path unification. MemStore.GetDMRecipient gets an honest
implementation so the block path is testable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 09:03:03 +02:00
J3vbandClaude Fable 5 7fbfbef581 test(plugin): lock vetted-IP dial fallback and fail-closed vetting (W2-6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 08:58:57 +02:00
J3vbandClaude Fable 5 9145f893d1 fix(plugin): restore multi-address fallback in the SSRF-guarded dialer (W2-6)
After validating every resolved IP, the guarded dial connected only to
ips[0] — an allowlisted dual-stack or round-robin host whose first record
was down hard-failed despite reachable vetted alternatives. The dial now
tries each vetted address in order (all records still validated before
any dial: one poisoned private record refuses the whole request).

Also removes the redundant rejectPrivateAddrs pre-resolves (initial
request + redirect hop): the guarded dial is the authoritative check and
every path flows through it, so the pre-resolve only cost an extra DNS
round trip while re-opening the rebinding TOCTOU it was meant to close.
Folds the W3-2-adjacent double-resolve cleanup from the plan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 08:58:57 +02:00
J3vbandClaude Fable 5 68e24d32d4 test(api): lock distinct client keys under broad trusted CIDRs (W2-5)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 08:56:34 +02:00
J3vbandClaude Fable 5 a762dc712d fix(api): keep per-client keys distinct under broad trusted_proxies (W2-5)
With trusted_proxies covering client networks (e.g. 10.0.0.0/8 over LAN
clients), the right-to-left XFF walk skipped every entry, exhausted, and
fell back to the proxy's RemoteAddr — collapsing all clients into one
rate-limit/lockout bucket, so one user's failed logins locked out
everyone. On exhaustion the walk now returns the leftmost valid entry
(furthest-upstream hop), the best distinct per-client key such a config
allows. An untrusted RemoteAddr still never gets its headers honoured.

Also (W3-3): the CIDR list parses once per request instead of once per
XFF candidate, config load warns about invalid CIDR entries at startup
(a silently skipped entry silently un-trusts the proxy), and the sample
config documents that trusted_proxies must list only proxy hops.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 08:56:34 +02:00
J3vbandClaude Fable 5 92abe7c173 test(plugin): lock reinstall command rebinding + hijack refusal (W2-3)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 08:52:31 +02:00
J3vbandClaude Fable 5 2a4b2e1628 fix(plugin): make in-place plugin upgrades rebind commands (W2-3)
installFromDisk replaced r.plugins/r.byName with a fresh *Instance but
left r.commands keyed to the old pointer and the old module running:
re-installing an enabled plugin blocked its own command re-registration
(RegisterCommand compared ownership by pointer) and kept dispatch routing
into the orphaned module until restart. Reinstall now deactivates the old
instance and clears its bindings, and RegisterCommand compares ownership
by plugin identity (manifest name) — the same plugin re-binds freely, a
different plugin still cannot hijack an owned command.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 08:52:31 +02:00
J3vbandClaude Fable 5 47663e2be3 test(service): cover password-change partial-success contract (W2-2)
Revocation failure: no error, audit still written, RevokeFailed set,
password committed. Transient failure: absorbed by exactly one retry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 08:50:35 +02:00
J3vbandClaude Fable 5 e5491c20aa fix(service): report password change as partial success when revocation fails (W2-2)
UpdateUserPassword commits first; when DeleteOtherSessions then errored the
handler returned 500 and skipped the audit row — telling the user the
change failed while the new password was already live, walking them into
retrying with a dead password and tripping the confirm lockout. The
committed change now always audits and reports success; revocation gets
one bounded compensating retry, and a persistent failure surfaces as a
200 + warning (sessions_revoked count) the client can show.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 08:50:35 +02:00
J3vbandClaude Fable 5 f3005572b4 test(api): lock client-update bucket isolation (W2-1)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 08:50:34 +02:00
J3vbandClaude Fable 5 26a367a7a1 fix(api): give client-update polling its own rate-limit bucket (W2-1)
The empty-prefix middleware shared per-IP buckets with verify-totp,
password change, and the sensitive endpoints, so a client's 30/min
auto-poll could 429 its own user's 2FA or password change. Dedicated
"client_update:" prefix, mirroring "livekit_proxy:".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 08:50:34 +02:00