Skip to content

Releases: pretyflaco/vezir

v0.10.0 — rotating refresh-token sessions

Choose a tag to compare

@pretyflaco pretyflaco released this 02 Jul 18:20

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.
  • sessions table (migration 0.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: VezirClient transparently refreshes-and-retries once on a 401 and persists the rotated pair to teams.json. New CLI vezir logout; operator vezir session list / vezir session revoke. Refresh tokens stored hashed server-side (SHA-256) and 0600 client-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

Choose a tag to compare

@pretyflaco pretyflaco released this 25 Jun 13:28

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

Choose a tag to compare

@pretyflaco pretyflaco released this 20 Jun 08:45

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 OR row.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 on delete_team: removes the jobs row, session_teams rows, the on-disk sessions/<id>/ directory, and the logs/<id>.log file.
    • New CLI vezir session rm <id> — confirms first (--yes/-y to skip); talks to the server over HTTP.
    • New TUI action: ctrl+d on the session-detail screen (plus a [^d] Delete button) shows a confirm modal before deleting.
    • Client API: VezirClient.delete_session() + a new _delete plumbing helper.
  • Local-only delete (documented limitation). Deletion does not un-sync: artifacts already pushed to the team's git remote remain. The response carries a warning when 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

Choose a tag to compare

@pretyflaco pretyflaco released this 20 Jun 05:07

Added

  • MP3 audio uploads. The service now accepts .mp3 alongside .wav and .ogg, end-to-end:
    • Server (uploads.py): .mp3 in ACCEPTED_EXTS; audio/mpeg + audio/mp3 in 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): .mp3 in ACCEPTED_AUDIO_EXTS + Content-Type map. The WAV-only pre-upload compression step no-ops on MP3.
    • TUI file picker (record_screen.py): .mp3 shown and selectable.
    • Server-side audio discovery for speaker-clip extraction (labels.py) and audio cleanup (worker.py) now include *.mp3.
  • 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.py now strips VEZIR_GOOGLE_* env vars so an operator host with Google sign-in configured no longer fails the "unconfigured" test_google_auth.py cases.

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

Choose a tag to compare

@pretyflaco pretyflaco released this 17 Jun 08:18

Closes the remaining fresh-Mac onboarding gaps (after millet-record 0.4.3/0.4.4 fixed the empty _bin/ packaging bug).

Fixed

  • requires-python capped at >=3.10,<3.14. brew install python3 on a fresh Mac gives 3.14, for which coincurve (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 doctor macOS recorder preflight. On Apple Silicon, doctor now shells out to millet check and reports ffmpeg, the bundled meet-record-mac sidecar, 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.4 so 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

Choose a tag to compare

@pretyflaco pretyflaco released this 16 Jun 11:00

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 / pull printed token does not start with 'vzr_' even though a session JWT is the correct bearer. config.validate_token_format now recognises a JWT (a.b.c with an eyJ payload prefix, matching the server's fast-path) and skips the opaque-token heuristics.
  • vezir doctor understands session JWTs. Reports token is a session JWT (identity sign-in) instead of the spurious vzr_ warning.
  • vezir doctor no longer warns about SSL_CERT_FILE on public-cert servers. A missing SSL_CERT_FILE / VEZIR_CADDY_ROOT_CERT_PATH is 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

Choose a tag to compare

@pretyflaco pretyflaco released this 13 Jun 16:12
96bf50e

0.8.3 — Google device-flow DNS resilience

Fixed

  • /api/auth/google/device/start retries 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 as 502 "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/poll token exchange is retry- and unreachable-tolerant. A DNS/network failure reaching Google's token endpoint during polling returns 202 authorization_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

Choose a tag to compare

@pretyflaco pretyflaco released this 13 Jun 15:29
4867640

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 (or server.json public_url) and NIP-98 login-URL verification pins to that fixed base instead of trusting X-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).
  • /health no longer returns the data_dir filesystem path (unauthenticated info disclosure).
  • Loud startup warning when VEZIR_DISABLE_RATELIMIT is 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 synthesizes verification_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

Choose a tag to compare

@pretyflaco pretyflaco released this 13 Jun 13:05
e9ef188

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 terminal 401 "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 202 authorization_pending so the client keeps polling — the user never sees the 401. Genuine bad-token errors still fail fast (401). Adds clock_skew_in_seconds=10 defensively.

Added

  • verification_url_complete passthrough from /api/auth/google/device/start (the URL with the user_code embedded), 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_complete passthrough, the network-error→202 behaviour, and the transient-vs-token error classifier.

v0.8.0 — nostr + Google sign-in; public VPS front

Choose a tag to compare

@pretyflaco pretyflaco released this 13 Jun 11:14

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-initiated nostrconnect:// flow (QR + URI in the terminal); approve in your signer (Amber / nsec.app); the server mints a short-lived session JWT (~24h) reused as Authorization: Bearer. Same lookup_identity path as vzr_ tokens, so every existing route works unchanged. Authorize with vezir 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.com account, 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 with vezir 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 adds google_members (email allowlist) and google_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 in vezir 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_PATH by pointing httpx at only the Caddy internal CA — so on boxes where those vars name the internal root, public TLS validation failed with CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate. A new vezir.client.trust.resolve_verify seeds the public roots (from certifi, independent of SSL_CERT_FILE) and appends any configured internal CA, so a single client validates both the public front and internal tls internal hosts.

Notes

  • vzr_ bearer tokens are retained for machine/CI use.