mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-02 19:43:10 +03:00
release: v1.2.0-alpha.3 (#1377)
Bump the client version in package.json (+lock), tauri.conf.json and Cargo.toml (+lock) so release.yml's verify-versions gate passes and deployed clients see the update; refresh the literal version in the README and docs build examples; add the curated CHANGELOG entry covering the 199 verified defects fixed since v1.2.0-alpha.2 (#1366-#1375), the observability/backup/deployment hardening in #1376, migration 031, and the new config keys. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+229
@@ -5,6 +5,235 @@ tooling (`npm run changelog`) auto-generates entries from commit messages
|
||||
on each release; this file is the curated counterpart that calls out
|
||||
behavioural changes operators must know about.
|
||||
|
||||
## v1.2.0-alpha.3
|
||||
|
||||
- **fix:** eight bug-hunt batches closed **199 verified defects** since
|
||||
`v1.2.0-alpha.2` — 30 in #1366/#1367, 110 in #1369–#1372, 34 in #1374 and
|
||||
25 in #1375 — each fixed test-first with the failing assertion watched red
|
||||
against the unpatched code. The behavioural consequences worth knowing
|
||||
about are listed below; the rest are one-line correctness fixes with no
|
||||
operator-visible change.
|
||||
- **security(client): voice E2EE was never actually enabled** (#1370). The
|
||||
full ECDH/HKDF/AES-GCM key exchange completed, the room key was set, and
|
||||
the UI showed 🔒 Secured — but `createRoom` never called
|
||||
`room.setE2EEEnabled(true)`, so every audio and video frame reached the
|
||||
SFU in plaintext. It is enabled now, and a dead E2EE worker is no longer
|
||||
invisible to the Secured badge. Related voice-crypto fixes: a joining key
|
||||
holder sent its room-key offers *before* its own announce, so existing
|
||||
participants dropped them as "unknown peer" (#1370, #1374); rotation
|
||||
offers exceeded the server rate limit in large channels and permanently
|
||||
starved the same peers; both rotation paths and the reconnect-to-Secured
|
||||
path now carry session-generation guards; a departing peer's ephemeral
|
||||
key is retired on leave so a replayed pre-leave announce cannot overwrite
|
||||
the fresh key they rejoined with (#1372, #1374). The client also refreshed
|
||||
its LiveKit token every 23 hours while the server mints it with a 5-minute
|
||||
TTL, so auto-reconnect failed for any voice session older than five
|
||||
minutes (#1370).
|
||||
- **security(server):** access-control holes (#1369–#1372, #1374, #1375) —
|
||||
`voice_join` into a 1:1 DM had no block gate, so a blocked user could
|
||||
enter the blocker's DM voice room; the attachment-serve admin bypass let
|
||||
an ADMINISTRATOR download files from private DMs they were not in; the
|
||||
archived-channel read-only gate covered `SendMessage` only, so edit,
|
||||
reaction, pin, purge, delete and `channel_focus` still mutated or
|
||||
subscribed to archived channels (every write sink now routes through one
|
||||
`requireChannelWritable` gate); `EditMessage` and `handleReaction` DM
|
||||
detection failed *open* on a `GetChannel` error, skipping the block gate;
|
||||
group-DM creation only block-checked the creator, letting a third party
|
||||
force two users who blocked each other into a shared room; an invisible
|
||||
user's real custom status leaked on both presence emitters; `PATCH
|
||||
/users/{id}` with `banned` + `role_id` committed and broadcast the ban
|
||||
before authorizing the role change; admin API-token creation accepted a
|
||||
negative `expires_hours` and minted a token that never expires; upload
|
||||
rejections echoed raw storage errors (absolute server paths) to any
|
||||
authenticated user; the GIF proxy's log redaction missed the
|
||||
percent-encoded API key; `chat_command` was the only client message type
|
||||
without a rate limiter while each frame ran a WASM plugin invocation; and
|
||||
the login and typing rate limiters built their keys from *unvalidated*
|
||||
input, letting an unauthenticated caller pin unbounded heap for six hours.
|
||||
- **fix(auth):** accounts whose username contains `'`, `"` or `&` were
|
||||
permanently unloggable — registration HTML-escaped the name but login did
|
||||
not — and a profile rename to such a name locked the user out (#1370). If
|
||||
you had users hit this, they can log in again with no action on your side.
|
||||
Also: message search returned 500 for any query containing a hyphen (the
|
||||
one FTS5 operator the sanitizer allowlisted); usernames with an uppercase
|
||||
non-ASCII letter could never be @mentioned; registration recorded the
|
||||
reverse-proxy address as the session IP.
|
||||
- **server:** WS hub, reconnect and replay (#1369, #1371, #1372, #1374,
|
||||
#1375) — REST DM events never bumped the visibility watermark, while
|
||||
*every* ordinary DM message re-emitted `dm_channel_open` and bumped the
|
||||
global watermark, forcing every other client's next reconnect into a full
|
||||
resync; the client's `lastSeq` was never reset by a full-ready resync and
|
||||
desynced permanently; cold-tier replay had no interior-gap detection, so
|
||||
events the persister dropped were skipped and presented as a complete
|
||||
resume; `buildReady` swallowed three DB errors and shipped an
|
||||
authoritative-looking empty snapshot (the client wiped its DM list, member
|
||||
list and unread badges) and dropped the user's own live voice room when
|
||||
not READ-visible; `channel_focus` could re-subscribe after a concurrent
|
||||
visibility revoke, and role demotion's live-subscription revocation was
|
||||
gated on a cosmetic role re-read; a failed reconnect handshake ran the
|
||||
full disconnect teardown twice; presence events from every source now
|
||||
share one ordered per-client FIFO.
|
||||
- **server:** voice lifecycle (#1369, #1371, #1374) — a stale join's
|
||||
rollback deleted `voice_states` by user id alone, destroying a concurrent
|
||||
newer membership; deleting a voice channel raced a concurrent `voice_join`
|
||||
into a permanent hub/SFU ghost no sweep could heal; the stale-state sweep
|
||||
could delete a just-committed join's row, leaving the client in voice with
|
||||
no DB row; `handleVoiceJoin` handed out a live 5-minute LiveKit credential
|
||||
*after* a concurrent kick/move/revocation had already torn the membership
|
||||
down (the token is now withheld); the `participant_left` webhook never
|
||||
told the leaver, and a transient DB read error on `participant_joined`
|
||||
ejected a legitimate participant mid-call; `voice_mod_move` lacked the
|
||||
archived-channel gate; `CleanupVoiceForChannel` resolved an empty
|
||||
`voice_leave` audience because both callers archive first. Camera and
|
||||
screenshare now draw from the same per-channel `voice_max_video` budget —
|
||||
screenshare had no cap check at all, and the camera gate did not count
|
||||
screensharing occupants.
|
||||
- **server:** DM and message fan-out (#1369, #1371, #1372, #1375) — a DM
|
||||
send, edit, delete or reaction survived a transient participant lookup
|
||||
failure by silently dropping live fan-out to everyone including the
|
||||
sender; emoji create/delete and group-DM creation tied their broadcasts to
|
||||
the request context, so an aborted request committed the mutation and
|
||||
skipped the event; slow mode consumed its cooldown token before content
|
||||
validation, so a rejected send locked the composer for the full window; an
|
||||
attachment-metadata read failure broadcast the message with no
|
||||
attachments; `GET /channels/{id}/pins` had no LIMIT and failed permanently
|
||||
past ~32k pins; pinning a soft-deleted message returned 500;
|
||||
`LinkAttachmentsToMessage` no longer claims a user's live avatar as a
|
||||
message attachment; `PATCH /channels/{id}` now rejects a blank name.
|
||||
- **server:** admin and plugins (#1369, #1370, #1372, #1375) — "Restore
|
||||
backup" wrote to a hardcoded `data/chatserver.db`, so it silently no-oped
|
||||
on any server with a configured `database.path`; the WAF inline engine
|
||||
rejected every request body ≥ 1 MiB, breaking plugin install and large
|
||||
avatar uploads when `waf_enabled` was on; self-account-deletion emitted no
|
||||
`member_ban`, so every other client kept the deleted user; the admin live
|
||||
log stream blanked every error attribute to `{}`; `CheckForUpdate` had no
|
||||
in-flight dedupe and stampeded GitHub on cache expiry; a failed self-update
|
||||
swap left every client counting down to a restart that never came (a
|
||||
corrective `update_aborted` is now broadcast, and deferred cleanup runs
|
||||
before the restart exits — on Windows that file-handle release is the
|
||||
reason the restart exists). Plugin enable/re-install left
|
||||
`plugins.enabled = 1` while the runtime instance was deactivated, and
|
||||
uninstall reported success while the on-disk directory survived and
|
||||
resurrected the plugin on the next start.
|
||||
- **fix(client):** voice reliability (#1366, #1367, #1370–#1372, #1374,
|
||||
#1375) — a failed voice channel-switch left the user live in the call
|
||||
(mic hot, audio flowing) with the voice UI hidden and no way to leave;
|
||||
selecting the "Default" microphone (or losing the pinned one to a hot
|
||||
unplug) never changed the capture device; a camera or screenshare disable
|
||||
that completed while the enable's `publishTrack` was in flight left the
|
||||
server and every peer believing it was on (`leaveVoice` and reconnect
|
||||
teardown now bump the same generation guard); a `VIDEO_LIMIT` rollback
|
||||
assumed the camera and tore down a working camera while leaving refused
|
||||
screen tracks published — it now correlates by envelope id; auto-idle's
|
||||
return-to-online `presence_update` was always swallowed by the 1-per-10s
|
||||
limiter, so every user showed Idle to everyone else after their first idle
|
||||
period; connection-quality degradation was never reported; a group-DM
|
||||
decline silenced every other participant's ring and never reached the
|
||||
caller.
|
||||
- **fix(client):** messaging and stores (#1366, #1367, #1369, #1372) —
|
||||
re-opening a channel visited earlier in the session rendered a permanently
|
||||
stale window (live broadcasts only cover the focused channel; the tail is
|
||||
now refetched); the virtual scroll window never followed the scroll
|
||||
position, so rows past the initial overscan rendered as blank space; a
|
||||
scroll-up page past the 500-row cap deleted the user's pending/failed
|
||||
rows, the only copy of their composed text; the scroll-to-bottom button
|
||||
and "Jump to Present" pill scrolled out of view exactly when they became
|
||||
visible; a user named exactly "System" had every message rendered as a
|
||||
server notice with no moderation controls; DM permalinks failed until the
|
||||
DM had been opened once; the reaction picker dropped the server's custom
|
||||
emoji; Ctrl+K was dead with CapsLock on; the composer's slow-mode cooldown
|
||||
was applied to whichever channel was mounted, not the one that sent.
|
||||
- **fix(client):** settings, session and platform (#1367, #1370–#1372,
|
||||
#1375) — the built-in light theme overrode only 4 of ~45 tokens (composer
|
||||
and inputs near-invisible), the Font Size slider and High Contrast toggle
|
||||
were no-ops, and the tray Status menu bypassed the client's own status
|
||||
state so a tray-set Do Not Disturb silenced nothing; a failed TOTP verify
|
||||
tore down the overlay so the code could not be re-entered; channel
|
||||
create/edit/delete modals locked up permanently on an API failure; login
|
||||
to an IPv6-literal host was impossible; a host stored with an explicit
|
||||
`:443` lost its bearer token and cert-pinned proxy on attachment fetches;
|
||||
one malformed stored server profile discarded *all* saved profiles; a
|
||||
banned/revoked token reconnected forever if the session ended before
|
||||
MainPage mounted; a previous server's block list, collapsed categories and
|
||||
DM notes bled into the next server; the Rust HTTP proxy tunnel's data
|
||||
phase had no deadline, so a remote that completed TLS then went silent
|
||||
parked the connection forever (bounded at 600s — loose on purpose, this
|
||||
path carries uploads); the autostart toggle raced its own write.
|
||||
- **infra:** observability, backups, guardrails and deployment hardening
|
||||
(#1376). **`/health` now returns a real verdict** — hub dispatch-loop
|
||||
liveness, a bounded DB ping and a free-disk check, answering **503 with a
|
||||
subsystem reason** (`hub`, `database`, `disk`) when degraded; results are
|
||||
cached so the unauthenticated endpoint cannot amplify load. Point uptime
|
||||
monitors at it and treat any 503 as actionable. **The hub's panic breaker
|
||||
now exits the process** so a supervisor can restart it, instead of leaving
|
||||
broadcast delivery silently dead while clients still appear online — if
|
||||
you run the bare binary without a supervisor, use the new hardened
|
||||
`deploy/owncord.service` systemd unit (see "Running as a Linux Service").
|
||||
**Backups now actually run:** `backup_schedule` and `backup_retention`
|
||||
had existed in the admin panel since the initial schema but were never
|
||||
read by any code; the 15-minute maintenance loop now enforces them,
|
||||
verifies each backup with `PRAGMA integrity_check` (and again before a
|
||||
restore may overwrite the live DB), and prunes by age keeping the newest.
|
||||
Expect backup files to start appearing and pruning for the first time.
|
||||
`/api/v1/metrics` gains reconnect-tier, backpressure, DB-writer-wait,
|
||||
permission-cache, `ws_conn_rejects` and `disk_free_mb` signals, and the
|
||||
declared-but-never-recorded OTel instruments are wired. Upload storage
|
||||
failures return **507** instead of blaming the client with a 400. A
|
||||
single-process lock beside the SQLite file makes a second server process
|
||||
fail fast instead of silently fighting the first. **Unknown config keys
|
||||
now warn at startup** (a typo previously kept the default silently), and
|
||||
startup warns when `admin_allowed_cidrs` is customized while
|
||||
`trusted_proxies` is empty. Shutdown now joins the pruner and maintenance
|
||||
loop before the DB closes, drains HTTP handlers into a live hub, and skips
|
||||
the 5s client-notice window when nobody is connected. Write-path work:
|
||||
no-op read-state UPSERTs are skipped, boot-time `ANALYZE` runs only when a
|
||||
migration applied, role-scoped override changes evict only that role's
|
||||
members from the permission cache, and connect/disconnect presence passes
|
||||
through a 300ms latest-wins coalescer (wire format and seq ordering
|
||||
unchanged).
|
||||
- **config:** new keys, all defaulting to current behaviour (#1376) —
|
||||
`server.max_ws_connections` (0 = unlimited; over the cap answers 503 +
|
||||
Retry-After), `server.metrics_allowed_cidrs` and
|
||||
`server.livekit_webhook_allowed_cidrs` (both fall back to
|
||||
`admin_allowed_cidrs`, so a central Prometheus scraper or an
|
||||
externally-hosted LiveKit no longer requires widening the admin
|
||||
perimeter), `database.max_readers` (0 = auto), `backup.dir`
|
||||
(`data/backups`), `security.auth_rate_limit_multiplier` (1.0; raise for
|
||||
shared-NAT communities), `event_persistence.replay_ring_size` (1000) and
|
||||
`event_persistence.replay_cold_limit` (5000 — watch `reconnect_tier_full`
|
||||
before raising). Three stored-but-inert admin settings (`server_icon`,
|
||||
`max_upload_bytes`, `voice_quality`) are now shown read-only with a
|
||||
pointer at the real `config.yaml` keys instead of pretending to apply.
|
||||
Documented in `docs/server-configuration.md`.
|
||||
- **deploy:** new `chatserver healthcheck` subcommand probes `/health`
|
||||
pinning the server's own certificate from disk (WebPKI when none exists,
|
||||
i.e. ACME) and is now the docker-compose healthcheck — the distroless
|
||||
image has no shell; plain `docker compose` only *surfaces* unhealthy, pair
|
||||
it with a watchdog for auto-restart. Compose gains json-file log rotation
|
||||
(`10m` × 3) on both services. `release.yml` now cold-boots the freshly
|
||||
built server binaries and Docker image and probes them healthy **before
|
||||
anything is signed or pushed** — the release feed drives signed
|
||||
self-updates, so a binary that compiled but died on boot would previously
|
||||
have shipped itself to every auto-updating instance. New "Reverse Proxy
|
||||
Topology" docs section (nginx snippet; only WebRTC media ports need to be
|
||||
directly reachable, `/livekit/*` is already proxied). Release binaries
|
||||
are built with Go 1.26.6 (stdlib CVE fixes flagged by govulncheck).
|
||||
- **migrations:** **031** normalizes legacy `sessions.expires_at` values to
|
||||
RFC3339-UTC and adds `idx_sessions_expires_at`, so the 15-minute expired-
|
||||
session sweep is an index lookup instead of a full-table scan on the
|
||||
writer. Applies automatically on first start; no operator action needed.
|
||||
- **protocol:** no wire changes — `docs/protocol-schema.json`,
|
||||
`message_types.go` and `protocolTypes.ts` are byte-identical to
|
||||
`v1.2.0-alpha.2`. Older clients and servers interoperate unchanged.
|
||||
- **fix(ws):** the LiveKit health check shared the process-wide
|
||||
`http.DefaultTransport` pool with every other user in the server; it now
|
||||
owns a private transport (#1356).
|
||||
- **chore:** bug-hunt tooling under `.claude/` (fix pipeline, findings
|
||||
ledger, circuit breaker, single-finder hunt with graph-fed targeting —
|
||||
#1361–#1365, #1373); dependency bumps (OTel 1.45.0, koanf, sqlite,
|
||||
eslint/oxlint/knip/typescript-eslint, tauri-plugin-updater, GitHub
|
||||
Actions; #1353–#1360). No runtime impact.
|
||||
|
||||
## v1.2.0-alpha.2
|
||||
|
||||
- **feat(client):** the login form has an **Auto connect** checkbox under
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "owncord-client",
|
||||
"version": "1.2.0-alpha.2",
|
||||
"version": "1.2.0-alpha.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "owncord-client",
|
||||
"version": "1.2.0-alpha.2",
|
||||
"version": "1.2.0-alpha.3",
|
||||
"dependencies": {
|
||||
"@jitsi/rnnoise-wasm": "^0.2.1",
|
||||
"@tauri-apps/api": "^2.10.1",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "owncord-client",
|
||||
"private": true,
|
||||
"version": "1.2.0-alpha.2",
|
||||
"version": "1.2.0-alpha.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
Generated
+1
-1
@@ -3021,7 +3021,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owncord-client"
|
||||
version = "1.2.0-alpha.2"
|
||||
version = "1.2.0-alpha.3"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"device_query",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owncord-client"
|
||||
version = "1.2.0-alpha.2"
|
||||
version = "1.2.0-alpha.3"
|
||||
edition = "2021"
|
||||
# Effective minimum: tauri 2.11 declares rust-version = "1.77.2", so the crate
|
||||
# cannot build below it. Declaring it here enables Cargo's MSRV-aware resolver
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"productName": "OwnCord",
|
||||
"version": "1.2.0-alpha.2",
|
||||
"version": "1.2.0-alpha.3",
|
||||
"identifier": "com.owncord.client",
|
||||
"build": {
|
||||
"frontendDist": "../dist",
|
||||
|
||||
@@ -138,11 +138,11 @@ Two main components:
|
||||
```bash
|
||||
# Server (Windows)
|
||||
cd Server
|
||||
go build -o chatserver.exe -ldflags "-s -w -X main.version=1.2.0-alpha.2" .
|
||||
go build -o chatserver.exe -ldflags "-s -w -X main.version=1.2.0-alpha.3" .
|
||||
|
||||
# Server (Linux)
|
||||
cd Server
|
||||
CGO_ENABLED=0 go build -o chatserver -ldflags "-s -w -X main.version=1.2.0-alpha.2" .
|
||||
CGO_ENABLED=0 go build -o chatserver -ldflags "-s -w -X main.version=1.2.0-alpha.3" .
|
||||
|
||||
# Client
|
||||
cd Client/tauri-client
|
||||
|
||||
+1
-1
@@ -2017,7 +2017,7 @@ Owner-only self-update from GitHub Releases (minisign/Ed25519-verified; see
|
||||
|
||||
```json
|
||||
{
|
||||
"current": "v1.2.0-alpha.2",
|
||||
"current": "v1.2.0-alpha.3",
|
||||
"latest": "v1.2.0",
|
||||
"update_available": true,
|
||||
"required_assets_present": true,
|
||||
|
||||
+2
-2
@@ -16,13 +16,13 @@ Production deployment guide for OwnCord server on Windows and Linux.
|
||||
**Windows:**
|
||||
```bash
|
||||
cd Server
|
||||
go build -o chatserver.exe -ldflags "-s -w -X main.version=1.2.0-alpha.2" .
|
||||
go build -o chatserver.exe -ldflags "-s -w -X main.version=1.2.0-alpha.3" .
|
||||
```
|
||||
|
||||
**Linux:**
|
||||
```bash
|
||||
cd Server
|
||||
CGO_ENABLED=0 go build -o chatserver -ldflags "-s -w -X main.version=1.2.0-alpha.2" .
|
||||
CGO_ENABLED=0 go build -o chatserver -ldflags "-s -w -X main.version=1.2.0-alpha.3" .
|
||||
```
|
||||
|
||||
- `-s -w` strips debug info (smaller binary)
|
||||
|
||||
+2
-2
@@ -55,11 +55,11 @@ Full Docker details: [Deployment Guide](deployment.md#docker-linux).
|
||||
```bash
|
||||
# Server (Windows)
|
||||
cd Server
|
||||
go build -o chatserver.exe -ldflags "-s -w -X main.version=1.2.0-alpha.2" .
|
||||
go build -o chatserver.exe -ldflags "-s -w -X main.version=1.2.0-alpha.3" .
|
||||
|
||||
# Server (Linux)
|
||||
cd Server
|
||||
CGO_ENABLED=0 go build -o chatserver -ldflags "-s -w -X main.version=1.2.0-alpha.2" .
|
||||
CGO_ENABLED=0 go build -o chatserver -ldflags "-s -w -X main.version=1.2.0-alpha.3" .
|
||||
|
||||
# Client
|
||||
cd Client/tauri-client
|
||||
|
||||
Reference in New Issue
Block a user