improvements/all-fixes #1

Merged
MalcolmMielle merged 10 commits from improvements/all-fixes into main 2026-10-28 23:54:43 +00:00
No description provided.
SQLite defaults to foreign_keys=OFF, making the REFERENCES constraints
in the schema decorative. Added three pragmas on connection init:

- PRAGMA foreign_keys = ON — enforce FK constraints (reviews.actor_id,
  follows.actor_id must reference valid actors)
- PRAGMA journal_mode = WAL — better concurrent read/write performance
- PRAGMA busy_timeout = 5000 — wait for locks instead of failing

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- signing.py: Import RSAPrivateKey/RSAPublicKey and assert isinstance()
  after loading PEM keys. The cryptography library's load_pem_* functions
  return union types that don't expose .sign()/.verify() without narrowing.

- db.py: Replace passlib (unmaintained, no type stubs) with direct bcrypt
  usage. bcrypt.hashpw/checkpw are fully typed and passlib was only a
  wrapper around bcrypt anyway. Removed passlib from pyproject.toml.

- auth.py: Change login_required_handler second parameter from _LoginRequired
  to Exception to match FastAPI's ExceptionHandler protocol.

- web.py: Add response_model=None to register and login_form routes, and
  use HTMLResponse | RedirectResponse union return types where the handler
  returns both TemplateResponse (via HTMLResponse) and RedirectResponse.

- signing.py: Also fix overly broad 'except (InvalidSignature, Exception)'
  to 'except InvalidSignature'.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
The follows table stores actor_id (follower) and target_actor_uri (followed),
but _handle_follow was storing them swapped: the followed person as actor_id
and the follower as target_actor_uri. This caused:

- /followers endpoint returned following list (swapped)
- /following endpoint returned followers list (swapped)
- get_followers/get_following function names didn't match their queries
- Fanout delivered to actor URIs instead of inbox URIs

Fixes:
- _handle_follow now stores sender_actor.id as follower, target_actor.uri as followed
- get_followers returns actors who follow this actor (was returning following)
- get_following returns actors this actor follows (was returning followers)
- Added get_follower_inboxes for fanout (returns inbox URIs, not actor URIs)
- api_create_review uses get_follower_inboxes for federation delivery

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
The /actor/{username}/outbox endpoint called get_reviews(limit=50) which
returns every review on the server, not just the ones authored by the
requested actor. Any actor's outbox exposed all reviews.

Added get_reviews_by_actor() to the db layer and used it in the outbox
route so each actor's outbox only contains their own published reviews.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- CSRF: Add CSRFMiddleware that validates csrf_token form field against
  session token on all form-encoded POST requests. Exempts ActivityPub
  inbox endpoints (HTTP Signatures) and /api/bootstrap (curl setup).
  Added hidden csrf_token inputs to all templates and updated the review
  form JS to send application/x-www-form-urlencoded.

- SECRET_KEY: Generate a random key with secrets.token_hex(32) when
  SECRET_KEY env var is not set, with a loud warning. Replaces the
  hardcoded 'dev-secret-change-me-in-production' default that allowed
  session forgery if deployed without configuration.

- ap_id collision: Use uuid.uuid4() for review ActivityPub IDs instead
  of content-based slug (first 30 chars), which collided when two
  reviews shared the same opening text.

- Exception handling: Replace 'except (InvalidSignature, Exception)'
  with 'except InvalidSignature' in signature verification — the broad
  catch was masking programming errors.

- Bootstrap password: Generate a random secrets.token_urlsafe(16)
  password instead of the hardcoded 'alice'.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- tests/conftest.py: Fixtures for test database (temp file, fresh per test),
  async HTTP client (httpx ASGITransport), registered user, and DB actor.

- tests/test_signing.py: Keypair generation, sign/verify round-trip,
  wrong key rejection, missing headers, tampered path, query string handling.

- tests/test_db.py: Password hashing, actor CRUD, review CRUD (create,
  list, bounds filter, duplicate ap_id, rating constraint), follow operations.

- tests/test_api.py: Bootstrap endpoint, review creation with auth,
  listing, bounds filtering, ap_id uniqueness.

- tests/test_activitypub.py: WebFinger, Actor document, Outbox,
  Followers/Following collections, Inbox signature rejection.

- tests/test_web.py: Page rendering, register/login/logout flow,
  duplicate username, wrong password, auth redirect.

Added pytest, pytest-asyncio, httpx to dev dependencies.
Configured asyncio_mode=auto in pyproject.toml.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- conftest.py: Add get_csrf_token() helper to extract CSRF token from pages.
  Update registered_user fixture to include CSRF token.

- test_web.py: Include CSRF tokens in all form submissions. Add test for
  CSRF rejection on register. Fix login tests to get CSRF from index page
  (login page redirects when already authenticated).

- test_api.py: Include CSRF tokens in review creation. Remove redundant
  _login helper (registered_user fixture already logs in). Add CSRF
  rejection test for review creation.

- test_activitypub.py: Include CSRF tokens in outbox test flow.

- test_db.py: Update follows tests for corrected semantics (get_followers
  returns followers, get_following returns following). Add tests for
  get_follower_inboxes and get_reviews_by_actor.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
New endpoints:

- POST /api/follow — Follow a remote actor by URI or acct (user@domain).
  Resolves via WebFinger, fetches and caches the remote actor, stores
  the follow relationship locally (pending), and sends a signed Follow
  activity to the remote inbox.

- DELETE /api/reviews/{id} — Delete a review. Only the author can
  delete their own reviews (403 otherwise). Federates a Delete activity
  to followers so remote servers also remove the review.

- GET /api/reviews/{id} — Fetch a single review by ID (404 if missing).

- GET /api/reviews — Added limit/offset query params for pagination.
  Total count returned via X-Total-Count response header (backward
  compatible — response body is still a bare array).

Inbox handler:

- Added _handle_delete for Delete activities — removes the referenced
  review locally, but only if the sender is the original author.

New db functions:

- get_review_by_id, delete_review, count_reviews
- get_reviews_by_bounds now accepts offset parameter

Tests: 8 new tests (67 total) covering single review fetch, delete
(author, not-found, auth), pagination, and follow (invalid target, auth).

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- .env file support: Added python-dotenv dependency. The app now loads
  .env automatically on startup. Added .env.example template and .env
  to .gitignore. Updated README with .env setup instructions.

- Configurable reload: Added RELOAD config option (default: true).
  uvicorn auto-reload is now controlled by the RELOAD env var instead
  of being hardcoded. Set RELOAD=false in production.

- Nominatim proxy: Added GET /api/geocode and GET /api/reverse-geocode
  endpoints that proxy requests to Nominatim. The frontend now calls
  these backend endpoints instead of hitting nominatim.openstreetmap.org
  directly from the browser, which avoids exposing user IPs to a
  third-party service. The proxy sets a proper User-Agent header as
  required by Nominatim's usage policy.

- README: Updated Configuration section with SECRET_KEY and RELOAD,
  added new API endpoints to the API reference table.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
Split the two largest modules into focused packages:

app/routes/activitypub.py (401 lines) → app/routes/activitypub/ package:
- helpers.py     — URI builders, require_local_actor, fetch_remote_actor
- webfinger.py   — /.well-known/webfinger endpoint
- actor.py       — /actor/{username} endpoint
- inbox.py       — /actor/{username}/inbox + activity handlers
- collections.py — Shared inbox, Outbox, Followers, Following
- __init__.py    — Router aggregation + backward-compatible re-exports

app/db.py (367 lines) → app/db/ package:
- connection.py  — get_db, SCHEMA, connection pragmas
- actors.py      — Actor CRUD, password hashing, list_local_actors
- reviews.py     — Review CRUD, bounds queries, pagination, count
- follows.py     — Follow/follower relationship operations
- __init__.py    — Re-exports all public functions for backward compat

All existing imports (from app.db import ...) continue to work via
the package __init__.py re-exports. The _fetch_remote_actor private
function was renamed to fetch_remote_actor (public) since it's used
across modules.

Updated tests/conftest.py to import from app.db.connection for the
_db global. Updated README project layout and added testing docs.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
MalcolmMielle deleted branch improvements/all-fixes 2026-10-28 23:54:43 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MielleLabs/pheme!1
No description provided.