1
Fork 0
mirror of https://github.com/thegeneralist01/archivr synced 2026-07-21 18:55:36 +02:00
Commit graph

70 commits

Author SHA1 Message Date
9544e707ed
fix: address 6 code-review findings
NixOS module (4 fixes):
- Remove RestrictNamespaces=true: Chromium (launched by single-file for
  web captures) needs Linux user namespaces; blocking them broke captures
- Escape TOML string values: labels/paths with quotes or backslashes
  would produce invalid archivr-server.toml and prevent startup
- Bracket IPv6 listen addresses: ::1:8080 is rejected by Rust's
  SocketAddr parser; [::1]:8080 is the correct form (RFC 2732)
- Add optional storePath per archive: covers archives initialised with
  a custom store path outside the default sibling store/ directory

Rust (1 fix):
- extract_client_ip loopback branch: take last XFF value not first.
  When a proxy appends to X-Forwarded-For an attacker controls the
  first entry; the last entry is always set by the trusted local proxy

Frontend (1 fix):
- Gate fetchArchives on authState === 'authenticated'. The empty-dep
  useEffect fired on mount (before login), hit 401, and never retried
  after login because authState was not in the dependency array
2026-06-29 21:30:45 +02:00
71e176cbd1
security: fix two Codex review findings + add port option
Fix 1 — NixOS: store/ dir missing from ReadWritePaths
  archive_path points to .archivr/; captures write artifacts to the
  sibling store/ directory. Whitelisting only .archivr/ caused POST
  /captures to fail under ProtectSystem=strict.
  Fix: use builtins.dirOf a.path to whitelist the parent, which
  contains both .archivr/ and store/.

Fix 2 — Rust: X-Forwarded-For was unconditionally trusted
  An attacker could send a different IP on every login attempt,
  bypassing LOGIN_MAX_ATTEMPTS entirely.
  Fix: use ConnectInfo<SocketAddr> (via into_make_service_with_connect_info)
  as the primary rate-limit key; XFF is trusted only when the TCP peer
  is loopback (i.e. a local reverse proxy). Tests without a real socket
  fall back to XFF unchanged.

Port configuration:
  NixOS module: split 'bind' string into separate 'listenAddress'
  (default 127.0.0.1) and 'port' (default 8080) options. openFirewall
  now uses cfg.port directly instead of parsing it from the bind string.
  README NixOS example updated accordingly.
2026-06-29 20:32:08 +02:00
5ce68b58f3
feat(server): require auth on list_entries and search_entries_handler
Both handlers accepted AuthUser but served guests.
auth.require_auth()? added as first line closes the gap.
Existing unauthenticated tests updated with session cookies.
2026-06-29 20:13:31 +02:00
007c5fc5fb
feat(server): enforce auth on all public read endpoints
Add auth_user: AuthUser + require_auth() to entry_detail, list_runs,
serve_artifact, serve_entry_favicon, serve_blob, list_tags,
list_entry_tags, list_collections_handler, list_entry_collections_handler.
Add require_auth() to get_collection_handler (already had extractor).

Update existing tests to pass session cookies.
2026-06-29 20:12:00 +02:00
a8e52bdf71
security: add per-IP sliding-window rate limit on POST /api/auth/login
5 attempts / 15 min per IP. Excess attempts get 429 + Retry-After.
Client IP read from X-Forwarded-For with loopback fallback.
In-memory; prune inline on each check.
Refactored app() into app_with_state() + thin wrapper.
Uses parking_lot::Mutex (no unwrap, no poisoning).
2026-06-29 20:06:41 +02:00
3b3b0d7207
feat(server): add HTTP security response headers middleware
Injects X-Content-Type-Options, X-Frame-Options, Referrer-Policy,
Content-Security-Policy, and Permissions-Policy on every response.
Registered as outermost layer so it covers setup_guard 503s too.
No new dependencies.
2026-06-29 20:03:44 +02:00
4311e85f95
ui: comprehensive UI polish pass
- LoginPage/SetupPage: centered card layout with display font, styled fields and submit button
- Topbar: fix duplicate Settings nav item; add styled user-menu with username + logout button
- RunsView: format ISO timestamps to readable dates, add colored status badges (completed/failed/running)
- AdminView: view-tabs system, styled admin-table, admin-input, status-badge (active/disabled), btn-primary
- SettingsView: replace all inline styles with form-section/form-field/field-input/btn-primary/btn-danger
- CollectionsView: restructure create form with proper field labels and btn-primary
- TagsView: add Tags section heading with separator
- ContextRail: show visibility as human-readable label not raw number; fix assign-error class
- styles.css: add auth-loading, view-tabs, form utilities, btn variants, status badges,
  run-status pills, token-banner/row, checkbox-row, coll-create-form, tag-tree-header CSS
2026-06-28 22:14:08 +02:00
ae92448fa3
build(frontend): bundle assets for collections UI 2026-06-26 17:33:50 +02:00
383cec1f81
feat(collections): routes — PATCH|DELETE /api/archives/:id/collections/:uid, visibility_bits in GET response 2026-06-26 17:33:38 +02:00
533e9d5d2e
feat(collections): db — update_collection, delete_collection helpers 2026-06-26 17:33:35 +02:00
ea0a8f80d1
test(settings): routes tests for profile update, password change, instance settings 2026-06-26 17:19:26 +02:00
44f18f4dcc
build(frontend): bundle assets for settings UI 2026-06-26 17:18:30 +02:00
dabe014e7e
feat(settings): routes — PATCH /api/auth/me, GET|PATCH /api/admin/instance-settings 2026-06-26 17:17:37 +02:00
69af2d2060
feat(settings): db — display_name column, InstanceSettings struct, profile and instance-settings helpers 2026-06-26 17:17:34 +02:00
cb390dfaab
build(frontend): bundle assets for collections UI 2026-06-26 17:05:22 +02:00
dd56fc8c70
test(collections): update tag-search test to send auth (private entries require auth) 2026-06-26 17:01:43 +02:00
24c5321f6e
feat(collections): collection CRUD + entry-visibility routes 2026-06-26 17:00:19 +02:00
02cab149fa
feat(collections): visibility-filtered entry queries + collection helpers 2026-06-26 16:51:48 +02:00
ab054f6256
feat(collections): schema — collections + collection_entries tables 2026-06-26 16:48:22 +02:00
2335776347
feat(users): admin routes — list/create users, roles, set status 2026-06-26 16:22:08 +02:00
48da356eee
feat(users): user management DB helpers (list_users, create_user, assign_role, custom roles) 2026-06-26 15:03:40 +02:00
9f7b3c912a
build(frontend): update bundled assets for async capture polling 2026-06-26 12:59:00 +02:00
da2b58ef96
feat(capture): mark stalled running jobs failed on server startup 2026-06-26 12:57:59 +02:00
cfd51778b1
feat(capture): async capture_handler (spawn_blocking) + job poll endpoint 2026-06-26 12:57:44 +02:00
27b37c3b6d
feat(capture): CaptureJobSummary type and archive::get_capture_job 2026-06-26 12:56:38 +02:00
d86a4d92a8
feat(capture): capture_jobs schema and DB helpers 2026-06-26 12:56:10 +02:00
37fd057c1f
fix(auth): use rfc3339 for session expires_at; update tag tests for auth guards 2026-06-26 12:03:13 +02:00
f9d2ac80e7
feat(auth): session cleanup background task (24h interval) 2026-06-26 11:57:14 +02:00
cb376aa986
feat(auth): apply ROLE_USER guard to WRITE routes (captures, tags) 2026-06-26 11:56:38 +02:00
36c7d575e2
feat(auth): API token endpoints (create, list, delete) 2026-06-26 11:55:05 +02:00
affb8aadc0
feat(auth): login, logout, /me, setup endpoints + setup_guard middleware 2026-06-26 11:53:58 +02:00
db28d038d9
feat(auth): AuthUser extractor, password helpers, token generation 2026-06-26 11:50:29 +02:00
a4239ba5fc
feat(auth): add auth_db_path to AppState, registry, and main.rs 2026-06-26 11:46:23 +02:00
86e8049f1b
feat(auth): session and token DB helpers 2026-06-26 11:32:32 +02:00
f482707b75
feat(auth): user and role DB helpers (create_owner, compute_role_bits) 2026-06-26 11:31:20 +02:00
696a5e1ac7
feat(auth): add initialize_auth_schema, open_auth_db, and auth record types 2026-06-26 11:30:02 +02:00
57fc48d73c
feat(auth): add argon2, rand, axum-extra dependencies 2026-06-26 11:28:51 +02:00
5fba6987a3
fix(capture): remove debug println! from hash_exists 2026-06-25 16:19:33 +02:00
351e74df09
feat(fonts): wire font extraction into capture pipeline + blob serving route
- capture.rs: add archive_id: Option<&str> to perform_capture; when Some,
  call font_extractor::extract_and_rewrite before hashing HTML, register
  each font as a deduplicated blob + 'font' artifact
- main.rs: pass None as archive_id (CLI keeps fonts embedded)
- routes.rs: add GET /api/archives/:id/blobs/:sha256 (serve_blob handler),
  pass Some(&archive_id) to perform_capture in capture_handler,
  add ApiError::internal constructor
2026-06-25 16:07:50 +02:00
22b0a55730
feat(fonts): hash_bytes, get_blob_by_sha256, font_extractor module
- fix(hash): hash raw bytes instead of lossy UTF-8; add hash_bytes
- feat(database): add get_blob_by_sha256 lookup
- feat(font_extractor): extract embedded font data-URIs from archived HTML
2026-06-25 16:02:57 +02:00
f6d636fce2
feat(singlefile): add UA, disable-web-security, and user-data-dir
- --user-agent: realistic Chrome UA so servers don't block headless string
- --browser-args=[--disable-web-security, --user-data-dir]: lets single-file
  inline fonts from any cross-origin CDN (e.g. fonts.gstatic.com) regardless
  of ACAO headers; user-data-dir required for --disable-web-security to take
  effect in newer Chromium builds (otherwise silently ignored)
2026-06-25 11:39:24 +02:00
ab353f9209
fix(singlefile): improve font capture and eliminate ES module viewer errors
Font fidelity:
- --browser-wait-delay=2000: Cloudflare Fonts injects @font-face CSS after
  HTML parse; the font hook needs extra time to see it after networkidle2
- --remove-unused-fonts=false: preserve @font-face rules even when fonts
  haven't rendered yet (font-display:swap, off-screen text)
- --remove-alternative-fonts=false: preserve unicode-range subsets instead
  of stripping them as 'alternatives'

ES module viewer error fix:
- Write a user script (sf-strip-scripts.js) that listens for
  single-file-on-before-capture-start and removes all <script> elements
  (except application/ld+json) from the live DOM before serialization.
  Scripts still execute during capture for CSS fidelity; none end up in
  the saved file, so no data:-URL base ES module resolution errors.
2026-06-24 21:30:14 +02:00
852cc45956
fix(singlefile): disable CSS stripping and script blocking for full render fidelity
Defaults that were destroying CSS fidelity:
- --remove-unused-styles=true: strips CSS nesting rules (site uses & selector)
  and any rule targeting JS-applied classes
- --remove-alternative-medias=true: deletes @media blocks that don't match
  the capture viewport, breaking responsive layout
- --block-scripts=true: prevents JS from applying classes before CSS snapshot

All three now set to false.
2026-06-24 21:15:36 +02:00
fbb96fbd05
build(frontend): update bundled assets after favicon+rendering changes 2026-06-24 21:05:35 +02:00
1206d7103d
feat(server): add GET /entries/:uid/favicon endpoint 2026-06-24 19:16:21 +02:00
cc2dbf4ac2
feat(archive): add has_favicon to EntrySummary 2026-06-24 19:16:21 +02:00
653efa9705
feat(capture): store favicon artifact for WebPage entries 2026-06-24 19:16:21 +02:00
ee697625fb
feat(singlefile): add SaveResult, favicon extraction, wait for networkidle2 2026-06-24 19:16:21 +02:00
fe9ff2dafe
feat(capture): add Source::WebPage, route HTML URLs to singlefile downloader 2026-06-24 18:39:02 +02:00
fd06632073
feat(singlefile): add single-file-cli downloader module 2026-06-24 18:38:57 +02:00