Releases: pretyflaco/vezir
Release list
v0.10.0 — rotating refresh-token sessions
Rotating refresh-token sessions — no more 24h forced logout
Interactive logins (nostr / Google) now return a short-lived access JWT (default 60 min) plus a rotating refresh token (vzrt_…). The client silently exchanges the refresh token for a fresh pair when a request 401s, so an actively-used session stays signed in without a new signer prompt / Google device grant. Follows RFC 9700 (OAuth 2.0 Security BCP, Jan 2025) / OAuth 2.1.
Added
POST /api/auth/refresh—{"refresh_token": "vzrt_…"}→ a new pair; the presented token is single-use. Rate-limited on the login bucket.POST /api/auth/logout— revokes the caller's own session family. Admin:GET /api/auth/sessions,POST /api/auth/sessions/{sid}/revoke,POST /api/auth/sessions/revoke-all.sessionstable (migration0.10.0-sessions, idempotent + additive) — the first server-side per-session revocation Vezir has had; previously an access JWT could only be invalidated by rotating the whole.session-secret.- Reuse detection — a session is a token family; replaying a consumed refresh token revokes the entire family and logs a security event. A one-generation grace window (
prev_refresh_hash) tolerates a legitimate client whose rotation response was lost. - Bounded lifetime — refresh tokens expire on idle (default 7 days, reset each rotation) and on an absolute cap from creation (default 30 days), after which a full re-login is required.
- New env vars
VEZIR_ACCESS_TTL,VEZIR_REFRESH_IDLE_TTL,VEZIR_SESSION_MAX_TTL(all optional, safe defaults). - Client:
VezirClienttransparently refreshes-and-retries once on a 401 and persists the rotated pair toteams.json. New CLIvezir logout; operatorvezir session list/vezir session revoke. Refresh tokens stored hashed server-side (SHA-256) and0600client-side.
Compatibility
Backward-compatible: session_jwt is still returned (aliased to the access token); vzr_ machine tokens and pre-refresh clients are unaffected and degrade to the existing re-login-on-401 behavior.
Tests
804 passing (was 780 in 0.9.0). +24 refresh-session tests; vezir/server/sessions_auth.py added to the strict mypy allowlist.
Published to PyPI: https://pypi.org/project/vezir/0.10.0/
v0.8.13 — don't route to needs_labeling on a spurious tiny REMOTE
Fixed
A single near-empty, noisy REMOTE (or raw SPEAKER_n) repeatedly sent sessions to needs_labeling. In practice every session picked up one REMOTE with a handful of seconds of backchannel ("Thank you.", "Fine.") or heavily distorted noise that voiceprint never matched; that lone placeholder forced a human labeling round even when the real conversation was fine.
_has_unresolved_speakers() now ignores an unresolved raw speaker (YOU/REMOTE/REMOTE_N/SPEAKER_N) that is tiny — at or below VEZIR_TINY_SPEAKER_MAX_SECONDS (default 5.0s) of speech and VEZIR_TINY_SPEAKER_MAX_SEGMENTS (default 3) segments. A session routes to needs_labeling only when a substantial speaker is still unlabeled. _speaker_resolution() (used by vezir relabel) applies the same rule so reporting stays consistent.
This is the service-layer safety net for the millet-side 0.12.15 absorb_tiny_speakers() fix; it takes effect immediately for new sessions and, via vezir relabel, clears already-stuck sessions. Two new env vars (VEZIR_TINY_SPEAKER_MAX_SECONDS, VEZIR_TINY_SPEAKER_MAX_SEGMENTS) let operators tune the noise threshold.
v0.8.12 — remove sessions from a team
Added
- Delete a session. An admin or the session's original uploader can now permanently remove a session and its on-disk artifacts.
- New endpoint
DELETE /api/sessions/{id}. Authorization: the server-wide admin token bit ORrow.github == caller(the original uploader). A same-team non-owner non-admin gets 403; a caller from another team gets 404 (existence-hiding, matching_enforce_team_visibility). - New
queue.delete_session()— hard delete modeled ondelete_team: removes thejobsrow,session_teamsrows, the on-disksessions/<id>/directory, and thelogs/<id>.logfile. - New CLI
vezir session rm <id>— confirms first (--yes/-yto skip); talks to the server over HTTP. - New TUI action:
ctrl+don the session-detail screen (plus a[^d] Deletebutton) shows a confirm modal before deleting. - Client API:
VezirClient.delete_session()+ a new_deleteplumbing helper.
- New endpoint
- Local-only delete (documented limitation). Deletion does not un-sync: artifacts already pushed to the team's git remote remain. The response carries a
warningwhen the session looks synced; remove the git copy manually if needed.
Per-team admin role stays deferred; this uses the existing global admin bit plus the uploader, consistent with the current model.
v0.8.11 — accept MP3 uploads
Added
- MP3 audio uploads. The service now accepts
.mp3alongside.wavand.ogg, end-to-end:- Server (
uploads.py):.mp3inACCEPTED_EXTS;audio/mpeg+audio/mp3in the Content-Type allowlist. Magic-byte validation accepts either an ID3v2 tag (ID3) or a raw MPEG frame sync (0xFF, high-3-bits-set) — MP3 has no single fixed prefix — on both the one-shot and resumable (tus.io) paths. - Client (
uploader.py):.mp3inACCEPTED_AUDIO_EXTS+ Content-Type map. The WAV-only pre-upload compression step no-ops on MP3. - TUI file picker (
record_screen.py):.mp3shown and selectable. - Server-side audio discovery for speaker-clip extraction (
labels.py) and audio cleanup (worker.py) now include*.mp3.
- Server (
- Decoding already worked — millet's audio loader is ffmpeg-backed. Native MP3 discovery in millet session directories ships in millet-pipeline 0.12.14.
Fixed
tests/conftest.pynow stripsVEZIR_GOOGLE_*env vars so an operator host with Google sign-in configured no longer fails the "unconfigured"test_google_auth.pycases.
Tests
- One-shot MP3 accept (ID3 + frame-sync variants), spoofed-MP3 rejection, resumable MP3 accept.
v0.8.5 — fresh-Mac onboarding: Python cap, doctor recorder preflight, CI publish
Closes the remaining fresh-Mac onboarding gaps (after millet-record 0.4.3/0.4.4 fixed the empty _bin/ packaging bug).
Fixed
requires-pythoncapped at>=3.10,<3.14.brew install python3on a fresh Mac gives 3.14, for whichcoincurve(via the[nostr]extra) has no wheel — the install fell back to a failing source build. pip/pipx now select 3.13 or refuse with a clear message. Relax once coincurve ships cp314 wheels.
Added
vezir doctormacOS recorder preflight. On Apple Silicon,doctornow shells out tomillet checkand reports ffmpeg, the bundledmeet-record-macsidecar, and Microphone/System-Audio permission status — plus a Gatekeeper-quarantine hint when the sidecar resolves but won't run. Advisory (warn, never error).
Changed
- Pin
millet-record>=0.4.4so a fresh install gets the recorder wheel that bundles the macOS binary. - CI: tag-triggered PyPI publish via Trusted Publishing (OIDC, no stored token), replacing manual
twine upload.
Install
pip install --upgrade vezir # client
pip install --upgrade 'vezir[tui]' # client + TUI
pip install --upgrade 'vezir[server]' # server
v0.8.4 — quiet false-positive token/TLS warnings for identity sign-in
Diagnostic-only patch: stop the client and vezir doctor from emitting warnings that were written for the old vzr_-token + internal-CA world. After a vezir login (Nostr/Google) they were misleading, not errors — the JWT bearer and public HTTPS cert work fine.
Fixed
vzr_token warning no longer fires for session JWTs.vezir scribe/upload/pullprintedtoken does not start with 'vzr_'even though a session JWT is the correct bearer.config.validate_token_formatnow recognises a JWT (a.b.cwith aneyJpayload prefix, matching the server's fast-path) and skips the opaque-token heuristics.vezir doctorunderstands session JWTs. Reportstoken is a session JWT (identity sign-in)instead of the spuriousvzr_warning.vezir doctorno longer warns aboutSSL_CERT_FILEon public-cert servers. A missingSSL_CERT_FILE/VEZIR_CADDY_ROOT_CERT_PATHis normal when the server uses a public (Let's Encrypt) cert; the no-cert case is now an informational line, not a warning. Set those vars only for an internal-CA server (e.g. Caddy).
Notes
- No behaviour change — purely diagnostic wording. Works against any vezir server ≥ 0.8.0.
Install
pip install --upgrade vezir # client
pip install --upgrade 'vezir[tui]' # client + TUI
pip install --upgrade 'vezir[server]' # server
v0.8.3 — Google device-flow DNS resilience
0.8.3 — Google device-flow DNS resilience
Fixed
/api/auth/google/device/startretries transient DNS/network errors. On a host with flaky DNS the first call to Google's device-code endpoint could fail to resolve and surface as502 "could not reach Google to start sign-in"(it worked on the next tap). The POST is now retried with backoff (same classifier as the 0.8.1 JWKS/token path); only a genuine network failure after all retries returns 502.device/polltoken exchange is retry- and unreachable-tolerant. A DNS/network failure reaching Google's token endpoint during polling returns 202authorization_pending(keep polling) instead of a hard 502, so a mid-flow blip no longer aborts the sign-in.
Tests
Full suite 687 passed; new tests for the device/start transient-retry and the device/poll network→202 behavior.
v0.8.2 — security hardening + Google prefill
0.8.2 — security hardening + Google prefill
Response to a 0.8.x security audit, plus a Google sign-in prefill fix.
Security
- NIP-98 replay protection. A valid login event could be replayed within its ~180s freshness window to mint a second session JWT. The server now records consumed event ids (in-memory TTL) and rejects reuse (401).
- Header-injection-resistant login URL. Set
VEZIR_PUBLIC_URL(orserver.jsonpublic_url) and NIP-98 login-URL verification pins to that fixed base instead of trustingX-Forwarded-Proto/Host— so a caller reaching the app server directly can't spoof the signed-URL target. Falls back to the previous header behavior when unset. Set this in production. - Exact Google domain match — the Workspace-domain check compares the email's domain part precisely (no suffix/subdomain ambiguity).
/healthno longer returns thedata_dirfilesystem path (unauthenticated info disclosure).- Loud startup warning when
VEZIR_DISABLE_RATELIMITis set. - DST-correct timestamp parsing (
calendar.timegm, true UTC) for token-expiry checks.
Fixed
- Google sign-in prefill. Google's device endpoint returns only a bare
verification_url; the server now synthesizesverification_url_complete(…/device?user_code=<CODE>) so clients open a pre-filled verification page.
Tests
Full suite 685 passed; 11 new tests covering replay rejection, the VEZIR_PUBLIC_URL header-injection guard, exact-domain matching, the synthesized URL, the /health change, and UTC timestamp parsing.
Upgrade note
Operators should set VEZIR_PUBLIC_URL=https://<your-host> to close the login-URL header-trust path.
v0.8.1 — Google sign-in UX + JWKS/DNS resilience
0.8.1 — Google sign-in UX + JWKS/DNS resilience
Two fixes surfaced by the vezir-android 0.6.0 on-device e2e.
Fixed
- Google device sign-in no longer fails on a transient DNS blip. The ID-token verification fetches Google's JWKS from
www.googleapis.com; on a host with flaky DNS that first fetch could fail (Name or service not known) and surface as a terminal401 "Google ID token verification failed"(it worked on the next try). The verify now retries transient network/DNS errors with backoff and, if still unreachable, the poll endpoint returns 202authorization_pendingso the client keeps polling — the user never sees the 401. Genuine bad-token errors still fail fast (401). Addsclock_skew_in_seconds=10defensively.
Added
verification_url_completepassthrough from/api/auth/google/device/start(the URL with theuser_codeembedded), so clients can open a pre-filled verification page instead of making the user read and type the code by hand.
Tests
- Full suite 674 passed; new tests for the
verification_url_completepassthrough, the network-error→202 behaviour, and the transient-vs-token error classifier.
v0.8.0 — nostr + Google sign-in; public VPS front
0.8.0 — nostr (NIP-46) + Google sign-in; VPS public-access front
Two new sign-in methods and a public-access front, so members reach the server over ordinary HTTPS and authenticate with their identity (no token setup).
Added
vezir login— nostr sign-in via a remote signer (NIP-46 / Amber). Client-initiatednostrconnect://flow (QR + URI in the terminal); approve in your signer (Amber / nsec.app); the server mints a short-lived session JWT (~24h) reused asAuthorization: Bearer. Samelookup_identitypath asvzr_tokens, so every existing route works unchanged. Authorize withvezir npub add --npub … --github ….vezir login --method google— Google Workspace sign-in. A second method for members who don't use a Nostr signer, via the OAuth 2.0 Device Authorization Grant (works headless / in a TUI): the client shows a short code + URL, the user approves in any browser with their@blinkbtc.comaccount, and the server mints the same session JWT as the nostr path. The server proxies Google's device + token endpoints so the OAuth client secret never leaves the server; the ID token is verified (issuer,email_verified,hd/email domain == allowed domain) and the email must be allow-listed. Authorize withvezir google add --email …@blinkbtc.com --github ….- Server NIP-98 + npub allowlist + JWT issuance (
nip98.py,nostr_members,nostr_auth.py); pure-Python NIP-46 client with NIP-04/44. Google support addsgoogle_members(email allowlist) andgoogle_auth(device-grant router + ID-token verification). - VPS public-access front (
infra/vps/,infra/caddy/): WireGuard (server dials out) + nftables TLS-passthrough, so clients reach the server over ordinary outbound HTTPS — works from CGNAT/IPv6-only links — while TLS terminates on the server (the VPS sees only ciphertext). Supersedes per-client nvpn/Tailscale for reaching the server.
Fixed (NIP-46 robustness — validated against real Amber)
- Multi-relay fan-out across the proven 5-relay set, so a single relay dropping ephemeral kind-24133 events can't strand login.
- Persistent subscription + periodic re-publish + relay reconnect for flaky/restrictive networks.
- Client clock-skew correction. The signer filters incoming requests by its own clock (
since); an unsynced client clock running behind made the signer never receive our requests (connect ok, then hang). We learn the offset from the signer's connect-event timestamp and stamp requests accordingly (clamped ±300s), plus an NTP-sync preflight warning invezir login.
Fixed (client TLS trust)
- Client now trusts the public and internal CA instead of replacing the store. The public VPS front serves a publicly-trusted (Let's Encrypt) certificate, but the client honored
SSL_CERT_FILE/VEZIR_CADDY_ROOT_CERT_PATHby pointing httpx at only the Caddy internal CA — so on boxes where those vars name the internal root, public TLS validation failed withCERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate. A newvezir.client.trust.resolve_verifyseeds the public roots (from certifi, independent ofSSL_CERT_FILE) and appends any configured internal CA, so a single client validates both the public front and internaltls internalhosts.
Notes
vzr_bearer tokens are retained for machine/CI use.