Skip to content

feat: v7.2.0 — Share tokens, thumbnails, download restrictions#89

Merged
GeiserX merged 4 commits intomainfrom
ai/v7.2.0-features
Mar 10, 2026
Merged

feat: v7.2.0 — Share tokens, thumbnails, download restrictions#89
GeiserX merged 4 commits intomainfrom
ai/v7.2.0-features

Conversation

@GeiserX
Copy link
Owner

@GeiserX GeiserX commented Mar 10, 2026

Summary

  • Share tokens: Link-shareable auth scoped to specific chats with PBKDF2 hashing, expiry, revocation, and use tracking
  • Download restrictions: no_download flag on viewer accounts and share tokens blocks explicit downloads/exports while preserving inline media rendering
  • On-demand thumbnails: WebP thumbnail generation with decompression bomb protection and path traversal guards
  • App settings: Key-value store with admin CRUD endpoints
  • Audit log improvements: Prefix-match action filtering, token auth event tracking

Type of Change

  • New feature (non-breaking change that adds functionality)

Database Changes

  • Schema changes (Alembic migration required)

Migration 010 creates:

  • viewer_tokens table (share token authentication)
  • app_settings table (key-value store)
  • no_download column on viewer_accounts
  • no_download + source_token_id columns on viewer_sessions

All operations are idempotent via sa.inspect(). Entrypoint stamping checks all 3 table/column artifacts before marking 010 as complete.

Data Consistency Checklist

  • All chat_id values use marked format (via _get_marked_id())
  • All datetime values pass through _strip_tz() before DB operations
  • INSERT and UPDATE operations handle the same fields identically

Testing

  • Tests pass locally (python -m pytest tests/ -v) — 31 tests in test_v720_features.py
  • Linting passes (ruff check .)
  • Formatting passes (ruff format --check .)
  • Manually tested in development environment

Test coverage:

  • Token auth: valid/invalid/empty/rate-limited/no_download
  • Token CRUD: create/list/delete/revoke, master-only enforcement
  • Token revocation invalidates sessions: revoke, delete, scope change all clear active sessions
  • Label-only update preserves sessions: non-scope changes don't disrupt users
  • no_download blocks explicit downloads (download=1) but allows inline media
  • no_download blocks chat export
  • no_download persisted in DB sessions and restored after restart
  • source_token_id tracked for precise session invalidation
  • create_viewer passes is_active and no_download flags
  • Settings: get/set
  • Thumbnails: path traversal, disallowed size, non-image rejection
  • Audit: action prefix filter

Security Checklist

  • No secrets or credentials committed
  • User input properly validated/sanitized
  • Authentication/authorization properly checked

Security specifics:

  • Share tokens: PBKDF2-SHA256 (600K iterations), secrets.compare_digest for constant-time comparison
  • Token revocation: immediately invalidates all active sessions via source_token_id tracking
  • Session persistence: no_download survives container restarts (stored in viewer_sessions)
  • Thumbnails: Image.MAX_IMAGE_PIXELS limit, Path.resolve() + is_relative_to() traversal guard
  • Rate limiting: shared with login endpoint (15 attempts / 15 min per IP)
  • Export endpoint: respects no_download restriction

Deployment Notes

  • Docker image rebuild required (Dockerfile.viewer adds Pillow system dependencies)
  • Migration 010 auto-applies on container startup
  • Existing sessions are unaffected (new columns default to 0/NULL)
  • requirements-viewer.txt adds Pillow>=10.0.0; also declared in pyproject.toml [project.optional-dependencies.viewer]

Add share token authentication for link-shareable access scoped to
specific chats. Tokens use PBKDF2-SHA256 (600K iterations) with
constant-time comparison. Includes on-demand WebP thumbnail generation
with Pillow, server-side no_download enforcement, app_settings table,
audit log action filtering, and admin UI for token management.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link

🐳 Dev images published!

  • drumsergio/telegram-archive:dev
  • drumsergio/telegram-archive-viewer:dev

The dev/test instance will pick up these changes automatically (Portainer GitOps).

To test locally:

docker pull drumsergio/telegram-archive:dev
docker pull drumsergio/telegram-archive-viewer:dev

Python 3.14 allows `except X, Y:` as equivalent to `except (X, Y):`,
making the parentheses redundant. Align with ruff format output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link

🐳 Dev images published!

  • drumsergio/telegram-archive:dev
  • drumsergio/telegram-archive-viewer:dev

The dev/test instance will pick up these changes automatically (Portainer GitOps).

To test locally:

docker pull drumsergio/telegram-archive:dev
docker pull drumsergio/telegram-archive-viewer:dev

High:
- Token revocation now invalidates active sessions immediately. Sessions
  track source_token_id; revoke/delete/scope-change calls
  _invalidate_token_sessions() to purge both in-memory and DB sessions.
- Migration 010 stamping checks all 3 artifacts (viewer_tokens,
  app_settings, viewer_accounts.no_download) before stamping complete.

Medium-High:
- no_download and source_token_id persisted in viewer_sessions table.
  save_session() stores both; _viewer_session_to_dict() returns both;
  _resolve_session() restores them on DB fallback/restart.

Medium:
- create_viewer() now reads and passes is_active and no_download from
  request body to create_viewer_account().
- no_download no longer breaks inline media. Only explicit downloads
  (download=1 query param) and exports are blocked. Frontend hides
  download buttons for restricted users.
- Token expiry timezone fixed: frontend converts datetime-local to
  UTC ISO before sending.

Low-Medium:
- Audit log filter uses startswith() instead of exact match, so
  "viewer_updated" matches "viewer_updated:username".

Standards:
- Version bumped to 7.2.0 in pyproject.toml and src/__init__.py
- SECURITY.md: added 7.x.x as supported
- pyproject.toml: added viewer optional dep group for Pillow
- CHANGELOG updated with all security/fix entries

Tests (12 new):
- Token revocation invalidates sessions (revoke, delete, scope change)
- Label-only update preserves sessions
- no_download blocks explicit download but allows inline
- no_download blocks export
- no_download persisted via save_session
- source_token_id persisted and tracked
- no_download restored from DB session
- create_viewer passes no_download and is_active flags

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@GeiserX
Copy link
Owner Author

GeiserX commented Mar 10, 2026

Review Response — All Findings Addressed

Thanks for the thorough review. Every finding was valid. Here's what was fixed in commit c11329f:

High: Token revocation not enforced on active sessions ✅

  • Added source_token_id field to SessionData and ViewerSession model
  • auth_via_token() now stores source_token_id=token_record["id"] in sessions
  • New _invalidate_token_sessions(token_id) purges both in-memory and DB sessions
  • update_token() calls it when is_revoked, allowed_chat_ids, or no_download changes
  • delete_token() calls it before deleting the token record
  • 4 new tests: revoke invalidates, delete invalidates, scope change invalidates, label-only change preserves

High: Migration 010 stamping incomplete ✅

  • PostgreSQL block now checks viewer_tokens AND app_settings AND viewer_accounts.no_download
  • SQLite block does the same with PRAGMA table_info for column checks
  • Only stamps 010 when all 3 artifacts are present

Medium-High: no_download not persisted in DB sessions ✅

  • Added no_download and source_token_id columns to ViewerSession model
  • Migration 010 updated to add both columns to viewer_sessions table
  • save_session() now accepts and stores both fields
  • _viewer_session_to_dict() returns both fields
  • _resolve_session() and startup restore both read them from the DB row
  • 3 new tests: no_download persisted via save_session, source_token_id tracked, no_download restored from DB

Medium: create_viewer ignores is_active and no_download ✅

  • create_viewer() now reads both flags from request body
  • create_viewer_account() in adapter.py now accepts is_active and no_download parameters
  • Response includes no_download field
  • 2 new tests: create with no_download=1, create with is_active=0

Medium: no_download breaks inline media ✅

  • Changed approach: serve_media() now only blocks when download=1 query param is present
  • Inline rendering (images, video, audio in <img>/<video> tags) works normally for restricted users
  • Frontend download links add ?download=1 and are hidden with v-if="!noDownload"
  • Lightbox download button also hidden for restricted users
  • Document download links show as disabled (pointer-events-none, reduced opacity)
  • Export endpoint (GET /api/chats/{chat_id}/export) also blocks for restricted users
  • 3 new tests: blocks explicit download, allows inline media, blocks export

Medium: Token expiry timezone-shifted ✅

  • Frontend createToken() now converts datetime-local value to UTC via new Date(...).toISOString() before sending
  • Backend already correctly strips timezone from ISO-parsed datetime, so the UTC ISO string is stored as naive UTC
  • Comparison against datetime.utcnow() is now consistent

Low-Medium: Audit filter mismatch ✅

  • get_audit_logs() now uses ViewerAuditLog.action.startswith(action) instead of exact equality
  • Added missing dropdown options: "Token updated" and "Setting updated"
  • Filter value "viewer_updated" now correctly matches "viewer_updated:username"

Standards ✅

  • Version bumped to 7.2.0 in both pyproject.toml and src/__init__.py
  • SECURITY.md now lists 7.x.x as supported
  • Pillow declared in pyproject.toml under [project.optional-dependencies.viewer]
  • PR body updated to follow .github/pull_request_template.md
  • CHANGELOG updated with Security, Fixed, and Changed sections

Test Summary

31 tests total (12 new), all passing. Covers every finding above.

@github-actions
Copy link

🐳 Dev images published!

  • drumsergio/telegram-archive:dev
  • drumsergio/telegram-archive-viewer:dev

The dev/test instance will pick up these changes automatically (Portainer GitOps).

To test locally:

docker pull drumsergio/telegram-archive:dev
docker pull drumsergio/telegram-archive-viewer:dev

The test_create_viewer mock was missing the no_download field added
by the review fix, causing a KeyError → 500 in CI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link

🐳 Dev images published!

  • drumsergio/telegram-archive:dev
  • drumsergio/telegram-archive-viewer:dev

The dev/test instance will pick up these changes automatically (Portainer GitOps).

To test locally:

docker pull drumsergio/telegram-archive:dev
docker pull drumsergio/telegram-archive-viewer:dev

@GeiserX GeiserX merged commit 923f021 into main Mar 10, 2026
6 of 7 checks passed
@GeiserX GeiserX deleted the ai/v7.2.0-features branch March 10, 2026 15:09
GeiserX added a commit that referenced this pull request Mar 23, 2026
High:
- Token revocation now invalidates active sessions immediately. Sessions
  track source_token_id; revoke/delete/scope-change calls
  _invalidate_token_sessions() to purge both in-memory and DB sessions.
- Migration 010 stamping checks all 3 artifacts (viewer_tokens,
  app_settings, viewer_accounts.no_download) before stamping complete.

Medium-High:
- no_download and source_token_id persisted in viewer_sessions table.
  save_session() stores both; _viewer_session_to_dict() returns both;
  _resolve_session() restores them on DB fallback/restart.

Medium:
- create_viewer() now reads and passes is_active and no_download from
  request body to create_viewer_account().
- no_download no longer breaks inline media. Only explicit downloads
  (download=1 query param) and exports are blocked. Frontend hides
  download buttons for restricted users.
- Token expiry timezone fixed: frontend converts datetime-local to
  UTC ISO before sending.

Low-Medium:
- Audit log filter uses startswith() instead of exact match, so
  "viewer_updated" matches "viewer_updated:username".

Standards:
- Version bumped to 7.2.0 in pyproject.toml and src/__init__.py
- SECURITY.md: added 7.x.x as supported
- pyproject.toml: added viewer optional dep group for Pillow
- CHANGELOG updated with all security/fix entries

Tests (12 new):
- Token revocation invalidates sessions (revoke, delete, scope change)
- Label-only update preserves sessions
- no_download blocks explicit download but allows inline
- no_download blocks export
- no_download persisted via save_session
- source_token_id persisted and tracked
- no_download restored from DB session
- create_viewer passes no_download and is_active flags
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant