Commit Graph

11 Commits

Author SHA1 Message Date
4cb8efb6ce feat: SSRF protection — block private IP ranges on outgoing requests
Some checks failed
CI / fmt (push) Successful in 23s
CI / test (push) Has been cancelled
CI / clippy (push) Has been cancelled
SsrfVerifier rejects private/reserved IPs (loopback, RFC1918, link-local,
CGNAT, ULA) on all federation fetches. Raw reqwest calls in webfinger and
backfill also validated. Debug mode bypasses via PermissiveVerifier.

Closes #4
2026-05-30 02:48:35 +02:00
7171a1791a feat: actor cache TTL with staleness-aware re-fetch
Adds fetched_at to RemoteActor, configurable TTL via builder
(.actor_cache_ttl_secs, default 24h), and get_or_refresh_remote_actor
helper that re-fetches stale actors from origin.

Closes #3
2026-05-30 02:46:54 +02:00
f08d11034d feat: expose signed_fetch for authorized-fetch / Secure Mode
Builder: .signed_fetch_actor_id(uuid) sets instance-level signing actor.
Service: .signed_fetch(&url) performs a signed GET returning raw JSON.

Closes #2
2026-05-30 02:43:51 +02:00
c1c8a37d0d fix: remove actor/followers/following routes from router()
These paths need content negotiation in real apps (AP JSON vs UI JSON).
k-ap can't serve the UI half, so the consuming app owns the route and
calls actor_json/followers_collection_json/following_collection_json
to produce the AP response.

The route conflict caused a panic when thoughts mounted its own
/users/{username}/... routes alongside service.router().

router() now registers only what k-ap fully owns:
- POST /inbox, POST /users/{id}/inbox (signature verification)
- GET /users/{id}/outbox
- GET /users/{id}/featured
- GET /.well-known/webfinger, nodeinfo, /nodeinfo/2.0
2026-05-29 03:28:17 +02:00
757c6d14ec fix 2026-05-29 03:20:21 +02:00
f00514850b test: add 31 meaningful unit tests for business logic
Activity receive() tests (src/tests/activities.rs):
- Accept: updates following_status to Accepted with correct user/actor
- Reject: removes following with correct user/actor
- Undo(Follow): removes follower + calls on_actor_removed
- Undo(Like): calls on_unlike for local objects; ignores remote objects
- Undo(Announce): removes announce record + calls on_announce_removed for local;
                  removes record but skips notification for remote objects
- Create: uses object["id"] not activity id; mention fires on_mention + on_create
- Update: uses object["id"]
- Delete(object): calls on_delete; does NOT call on_actor_removed
- Delete(actor): calls on_actor_removed; does NOT call on_delete
- Announce(local): records announce + calls on_announce_received
- Announce(remote): calls on_announce_of_remote; does NOT record announce
- Like(local): calls on_like
- Like(remote): silently ignored
- Add: uses object["id"] not activity id
- Block: removes both following and follower
- Domain block: activity skipped before any processing
- Actor block: Follow skipped before HTTP dereference (SSRF fix)
- Idempotency: duplicate delivery skipped

Actor serialization tests (src/tests/actors.rs):
- actor_type=Service serializes as "Service"
- discoverable=false serializes
- also_known_as serializes as JSON array (all aliases, not just first)
- optional fields omitted when None
- featured URL serialized when set

Visibility addressing tests (src/tests/broadcast.rs):
- Public: to=[AS_PUBLIC], cc=[followers]
- FollowersOnly: to=[followers], cc=[] — AS_PUBLIC absent
- Private: both empty
2026-05-29 02:44:23 +02:00
48fded426f fix: AP protocol correctness gaps
Undo(Announce): now removes announce record from ActorRepository and
  calls ApObjectHandler::on_announce_removed (default no-op, override
  to decrement boost counts). Announce counts no longer drift.

Undo(Block): now logged at info level instead of silently ignored.
  No automatic relationship restoration (spec doesn't require it).

AddActivity: now uses object["id"] as the stable ap_id (same as
  CreateActivity), falling back to activity id only if object has no
  id field. Fixes keying watchlist/collection items by the wrong id.

Featured collection: GET /users/{id}/featured now served by the router.
  ApContentReader::get_featured_objects() has a default empty-list impl
  — override to expose pinned posts without any breaking changes.
2026-05-29 02:29:38 +02:00
73a68860c1 style: clippy fixes and linter formatting 2026-05-29 01:52:14 +02:00
df6ff4c1e8 refactor!: CQRS repository split — v0.3.0
FederationRepository (34 methods) → 4 focused traits:
  ActivityRepository  (2)  — idempotency tracking
  FollowRepository    (18) — follower/following graph + migration
  ActorRepository     (6)  — keypairs, remote actor cache, announce tracking
  BlocklistRepository (8)  — domain + actor blocklists

ApObjectHandler (10 methods) → 2 traits:
  ApContentReader  (3) — get_local_objects_for_user/page, count_local_posts
  ApObjectHandler  (9) — all inbox callbacks (on_create, on_mention, etc.)

Builder changes from positional args to named setters:
  ActivityPubService::builder(base_url)
    .activity_repo(arc)
    .follow_repo(arc)
    .actor_repo(arc)
    .blocklist_repo(arc)
    .user_repo(arc)
    .content_reader(arc)
    .object_handler(arc)
    .build()

No behaviour changes.
2026-05-29 01:47:23 +02:00
7424d1dc54 fix: address 3 PARTIAL plan items
#15 @context security vocab: actor JSON now uses actor_ap_context()
     which includes W3C security vocab + Mastodon toot extensions
     (manuallyApprovesFollowers, discoverable, featured).
     Applied to actor_handler, actor_json(), broadcast_actor_update().
     Activity JSON keeps plain AS context (no security vocab needed).

#17 HTTP Digest (documented, no code change): production mode
     (debug=false) REQUIRES Digest header on inbound POSTs via
     require_digest() in the non-compat normalization config.
     Added doc comment to ApFederationConfig::new() to clarify.

#26 Integration tests: 3 new tokio tests in src/tests/integration.rs
     using in-memory trait stubs. Tests cover:
     - check_guards idempotency (duplicate activity rejected)
     - check_guards domain block (blocked domain skipped)
     - extract_and_dispatch_mentions (on_mention called for local actor)
2026-05-29 01:00:45 +02:00
aec768b5a0 refactor(service): split into delivery/broadcast/follow/backfill submodules; remove dead content_to_html 2026-05-29 00:28:48 +02:00