chore: release v0.2.1
Release / release (push) Successful in 1m27s

This commit is contained in:
2026-04-22 02:35:38 +03:00
parent 6c3dd67c1b
commit 645331d320
4 changed files with 28 additions and 69 deletions
+25 -66
View File
@@ -1,82 +1,41 @@
## v0.2.0 (2026-04-22)
## v0.2.1 (2026-04-22)
First feature release since the initial `v0.1.0` cut: a broad polish pass across
the backend, frontend, and schema, plus two fixes landed on top.
Security-focused release on top of v0.2.0. Hardens the restore/backup flow,
CSRF/SSRF surfaces, JWT revocation on role change, and template-context
leakage; adds a new **per-tracking-config quiet hours** feature with
app-level IANA timezone support; plus a handful of performance fixes.
### Features
#### Immich commands & tracking
- **Per-tracking-config quiet hours with app-level IANA timezone** — new `Timezone` app setting (defaults to `UTC`) and a `Quiet Hours` section on the Immich tracking-config form. HH:MM windows (including overnight, e.g. `22:0007:00`) are interpreted in the configured timezone and suppress all notifications for that tracker. ([6c3dd67](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6c3dd67))
- Per-chat album scope for Immich commands (`/search`, `/latest`, `/memory`, etc.) with a new *Edit album scope* modal on command-tracker listeners (inherit or explicit multiselect) ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
- `/search` and `/find` accept a trailing page number; Immich client `search_smart` / `search_metadata` take a `page` param ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
- Auto-organize rules now set the target album's thumbnail to the first added image (falls back to any asset type) when the album has none ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
### Security
#### Dashboard & status
- **Signed & verified pending-restore bundles** — SHA256 stored in `AppSetting` and checked on startup apply; files outside `data_dir` are refused and permissions tightened to `0600`. ([56993d2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/56993d2))
- **Same-origin check on `POST /api/backup/apply-restart`** — Bearer-in-localStorage was CSRF-reachable from any XSS'd admin tab; require matching `Origin`/`Referer`. ([56993d2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/56993d2))
- **JWT `token_version` bumps on demotion** — role/username change and admin password reset now bump `token_version` so already-issued tokens lose admin. Last-admin TOCTOU guarded by `COUNT` + post-commit recheck that rolls back on race. ([56993d2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/56993d2))
- **SSRF guard extended** to `ImmichClient.__init__` and the `external_domain` setter — admin-mutable URLs were bypassing the check that webhook / Slack / Discord paths already used. Dev `scripts/restart-backend.sh` now sets `NOTIFY_BRIDGE_ALLOW_PRIVATE_URLS=1` so homelab Immich instances still work. ([56993d2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/56993d2))
- **Redact & cap Immich error bodies** (~120 chars) before they flow into `ActionExecution.error` / `EventLog.details` (both UI-visible). ([56993d2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/56993d2))
- **Deny-list sensitive keys** (`api_key`, `token`, `secret`, `password`, `authorization`, `cookie`, …) in template-context merges so a rogue template cannot exfiltrate provider creds via `{{ api_key }}`. ([56993d2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/56993d2))
- **Cap user-controlled Immich search params** — `query` ≤ 256, `person_ids` ≤ 50, `size` ≤ 100 — so a Telegram listener cannot DoS upstream. ([56993d2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/56993d2))
- **Stream upload reads** with a running byte counter + `Content-Length` precheck instead of buffering the full body and then rejecting. ([56993d2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/56993d2))
- **Log Telegram `parse_mode` fallbacks** instead of swallowing silently — template escape bugs now surface in server logs. ([56993d2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/56993d2))
- **Rollback partial imports** on pending-restore failure (error recorded on a fresh session). ([56993d2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/56993d2))
- Action events (`action_success` / `action_partial` / `action_failed`) are emitted on every non-dry-run and surfaced on the dashboard with icons, filters, and colors ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
- Clear-events button + confirm modal on the dashboard (`DELETE /api/status/events`, scoped to the current user) ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
- Event rows render live tracker/provider/action names via FK join, with snapshot fallback when an entity has been deleted ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
### Performance
#### Backup & restore
- Full backup restore flow: prepare-restore writes a pending marker, a restart banner offers Apply-now / Apply-later, and the lifespan hook applies on next startup and archives under `data/applied_restores/` ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
- Manual *Create backup* button on the Backup page (`POST /api/backup/files`, same format as scheduled) ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
- `apply-restart` sends SIGTERM so the lifespan shutdown runs; `NOTIFY_BRIDGE_SUPERVISED` env var gates the button ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
#### Users & deletion protection
- `PATCH /api/users/{id}` for username and role changes with a last-admin guard, plus an *Edit user* modal on the Users page ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
- Deletion protection now returns structured `{message, entity, blocked_by}`; `ApiError` carries `.blockedBy` and the new `BlockedByModal` is wired into 8 deletion flows ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
#### Telegram
- Per-receiver locale for Telegram test messages (resolves `TelegramChat.language_override` per chat instead of applying the first receiver's locale to everyone) ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
- Telegram poller detects the "webhook is active" 409 and auto-calls `deleteWebhook` for bots whose DB `update_mode` is polling (throttled per bot) ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
- New `TelegramClient.get_chat` and `set_album_thumbnail` helpers (CLAUDE.md rule 6 — all Bot API calls go through `TelegramClient`) ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
#### Form UX
- Auto-select first available tracking / template / command / config + bot on create forms (trackers, command-trackers, targets, template/command configs) ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
- Global provider selector is visible even when there is only one provider ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
- Telegram target `disable_url_preview` defaults to `true` ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
### Bug Fixes
- Telegram target list: load chats/listeners **before** expanding so the slide animation computes the right height ([cf4976d](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/cf4976d))
- Test dispatch falls back to tracker defaults for tracking/template config (matching `load_link_data`), distinguishes "no template config linked" vs. "slot missing in linked config", and the frontend `testTrackerTarget` now treats `{success:false,error:...}` in a 2xx body as a failure instead of flashing a success snack ([80c034d](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/80c034d))
- Immich person-asset lookup switched from the removed `GET /api/people/{id}/assets` to `POST /api/search/metadata` with `personIds` — fixes `/person` and auto-organize rules silently returning zero candidates on Immich 1.106+ ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
- `add_assets_to_album` now surfaces the Immich error body on non-2xx responses ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
- Immich tracker "Checking links" parallelised (concurrency cap 6) ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
---
### Development / Internal
#### Schema / Migrations
- `event_log`: add `user_id`, `action_id`, `action_name` (+ defensive migration and backfill of `user_id` from `notification_tracker`) ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
- `command_tracker_listener`: add `allowed_album_ids` ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
#### Refactors & Internals
- Bounded concurrency (semaphores) in `NotificationDispatcher._preload_asset_data` and `_refresh_telegram_chat_titles`; chat-title sweep extended to 24h since `save_chat_from_webhook` covers active chats opportunistically ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
- Periodic-summary test path reuses the shared `collect_scheduled_assets` primitive (`limit=0`) so test and production go through one path ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
- New `fetchAuth` helper for multipart/binary calls (reuses `api()`'s refresh + `ApiError` mapping) ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
- `parseDate` helper for consistent UTC date rendering ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
#### Seeds
- Rename "Default Commands" → "Default Immich Commands"; `track_assets_removed` now defaults to `False` ([a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e))
- **Fix N+1** in `_refresh_telegram_chat_titles` — single `IN` query instead of `session.get` per chat. ([56993d2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/56993d2))
- **Parallelize album & shared-link fetches** in `test_dispatch` via `asyncio.gather`, and per-receiver Telegram test sends in the notifier with a semaphore of 5. ([56993d2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/56993d2))
- **Early-exit `collect_scheduled_assets(limit=0)`** so the periodic-summary test path skips the full per-album filter/sample (was O(album_assets)). ([56993d2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/56993d2))
- **Explicit `CREATE INDEX IF NOT EXISTS`** for `event_log` (`user_id` / `action_id` / `provider_id`) so the first boot after upgrade isn't left unindexed for the dashboard query. ([56993d2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/56993d2))
- **Add `AbortController` timeout (120s)** to `fetchAuth` so uploads/downloads don't hang indefinitely. ([56993d2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/56993d2))
---
<details>
<summary>All Commits</summary>
| Hash | Message | Author |
|------|---------|--------|
| [cf4976d](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/cf4976d) | fix(telegram): load chats/listeners before expanding to fix slide animation height | alexei.dolgolyov |
| [80c034d](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/80c034d) | fix(test-dispatch): fall back to tracker defaults, surface soft errors | alexei.dolgolyov |
| [a7a2b4e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/a7a2b4e) | feat: large polish pass — UX fixes, per-chat scope, restore/backup, action events | alexei.dolgolyov |
- [6c3dd67](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6c3dd67) — feat(tracking): per-config quiet hours with app-level IANA timezone _(alexei.dolgolyov)_
- [56993d2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/56993d2) — fix(security,perf): harden restore, CSRF, token_version + perf pass _(alexei.dolgolyov)_
</details>