47 Commits

Author SHA1 Message Date
d1ce277ff5 chore: bump to 0.4.0, update changelog
All checks were successful
CI / fmt (push) Successful in 21s
CI / clippy (push) Successful in 2m51s
CI / test (push) Successful in 3m49s
2026-05-30 02:50:46 +02:00
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
9f9c4e769b fix: persist inbound Block to blocklist, clear on Undo(Block)
Closes #1
2026-05-30 02:39:14 +02:00
ca949691e4 ci: add Gitea CI workflow (fmt, clippy, tests)
All checks were successful
CI / fmt (push) Successful in 18s
CI / clippy (push) Successful in 2m37s
CI / test (push) Successful in 3m43s
2026-05-30 02:26:54 +02:00
62c9bf2e4e fix: add missing RemoteActor fields in get_blocked_actors fallback v0.3.1 2026-05-29 04:04:08 +02:00
485c407edb feat(RemoteActor): add bio, banner_url, followers_url, following_url, also_known_as fields
Bump to 0.3.1. These fields are available on DbActor at follow/ingest
time but were discarded when constructing RemoteActor. Now populated
in from_json and follow(), so consuming repos can store and return
rich actor profiles without extra queries.
2026-05-29 04:03:23 +02:00
fad95f0550 docs: update README with Gitea registry installation 2026-05-29 03:36:22 +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
v0.3.0
2026-05-29 03:28:17 +02:00
757c6d14ec fix 2026-05-29 03:20:21 +02:00
88fd1bfbdc docs: add CHANGELOG for v0.3.0 2026-05-29 02:49:23 +02:00
90ea438764 docs: update README to reflect current API state
- also_known_as: Vec<String> (was Option<String>)
- broadcast_create_note/update_note: add mentioned_inboxes param + example
- EventPublisher: add BackfillRequested variant to match/example
- Add run_backfill_for_follower and import_remote_outbox sections
- Add featured collection section (get_featured_objects override)
- Expand routes table to include /featured endpoint
- Add count_accepted_followers + get_accepted_followers_page to follow section
- FederationEvent table: add BackfillRequested
- ApObjectHandler/ApContentReader: note which methods have defaults
- Inbound section: mention Undo(Announce) and Move improvements
2026-05-29 02:46:16 +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
5288696795 fix: pre-release improvements — scale, correctness, API clarity
#1  count_accepted_followers / get_accepted_followers_page: new DB-side
    methods on FollowRepository — no more loading all followers into memory
    to count or page them.

#2  Move re-follows are now non-blocking: tokio::spawn instead of
    awaiting each sign_and_send inside receive() — inbox handler no longer
    stalls for slow remote servers during account migration.

#3  Remove get_local_objects_for_user from ApContentReader (dead code).
    Backfill and outbox both use the paginated get_local_objects_page.

#6  Rename backfill_outbox → import_remote_outbox with a clear doc
    explaining the direction (import FROM a remote server, not to a follower).

#7  also_known_as: Option<String> → Vec<String> on ApUser, LookedUpActor,
    and DbActor. from_json now stores all aliases; move_act.rs checks all.

Mentions: broadcast_create_note / broadcast_update_note now accept
    mentioned_inboxes: Vec<Url> — delivery goes to followers + mentioned
    actors who aren't already followers. Deduplication is done before
    sending. Pass vec![] if note has no external mentions.

Docs: ApObjectHandler and ApContentReader now have complete doc comments
    with contracts, idempotency guidance, and error-handling semantics.
2026-05-29 02:19:39 +02:00
d5f75b4b57 feat: route backfill through EventPublisher; add run_backfill_for_follower 2026-05-29 02:05:03 +02:00
0519bed66c docs: update README to v0.3.0; add docs/ to .gitignore 2026-05-29 01:56:03 +02:00
2ee0452fa8 chore: add Makefile with check/fmt/clippy/test/fix targets; apply rustfmt 2026-05-29 01:53:56 +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
e11b0a6609 feat: add ApVisibility (Public/FollowersOnly/Private) to broadcast_create_note and broadcast_update_note 2026-05-29 01:15:26 +02:00
4ef1315671 feat: add discoverable field to ApUser — no longer hard-coded to true 2026-05-29 01:11:55 +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
db6a451788 fix: address remaining 3 NOT DONE plan items
#18 featured collection: add featured_url to ApUser/DbActor/Person;
     serialized as featured field in AP JSON when set by consumer.

#19 Tombstone in Delete: broadcast_delete_to_followers now sends
     {"type":"Tombstone","id":"..."} instead of bare URL string.

#21 Backfill pagination: run_backfill uses get_local_objects_page
     with cursor-based loop — avoids loading all posts into memory;
     delivers newest-to-oldest in BATCH_SIZE chunks.
2026-05-29 00:52:37 +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
90a0d91b39 refactor(activities): split into per-activity files with check_guards DRY helper 2026-05-29 00:17:31 +02:00
7ccc18e85c feat: production hardening — security, scale, protocol, DX
Breaking changes to FederationRepository, ApObjectHandler, ApUser:

FederationRepository:
- add is_activity_processed / mark_activity_processed (inbox idempotency)
- add get_accepted_follower_inboxes (DB-side dedup/filtering, replaces in-memory load-all)

ApObjectHandler:
- add on_announce_of_remote (cross-server boosts, previously silently dropped)

ApUser:
- add manually_approves_followers: bool
- add actor_type: ApActorType (was hardcoded Person)

Security:
- block check before actor HTTP fetch in Follow (prevents SSRF on blocked actors)
- 4xx responses use generic "not found"/"bad request" (no internal leak)
- 1 MB DefaultBodyLimit on inbox routes
- zeroize private key after generation

Delivery:
- all broadcasts are now non-blocking (tokio::spawn fallback, or EventPublisher queue)
- EventPublisher redesigned with typed FederationEvent enum (DeliveryRequested/DeliveryFailed)
- new deliver_to_inbox() public method for queue consumers
- configurable delivery_max_attempts and delivery_initial_delay_secs via builder
- Follow saved as Pending BEFORE delivery (race condition fix)

Router:
- GET /users/{id} (actor), GET /users/{id}/followers, GET /users/{id}/following now mounted

Protocol:
- mention extraction from Create/Update tag arrays → on_mention() dispatched
- WebFinger: add aliases field (acct: URI + AP actor URL)
- outbox: add last link, use count_local_posts for totalItems
- idempotency guard added to every inbound activity receive()
- actor serializes display_name and configurable actor_type/manually_approves_followers

Bump: 0.1.10 → 0.2.0
2026-05-28 23:35:41 +02:00
b557bd9d46 docs: update README to v0.1.10 — add new methods, fix signatures, document Move handling 2026-05-28 02:50:05 +02:00
d80cfd0431 feat: add mark_follower_accepted/rejected to ActivityPubService v0.1.10 2026-05-28 02:44:05 +02:00
432f39cbb4 feat: add broadcast_move to ActivityPubService; bump to v0.1.9 v0.1.9 2026-05-28 01:41:13 +02:00
2c509cbf88 feat: implement MoveActivity::receive with record migration and re-follow 2026-05-28 01:37:50 +02:00
52614d406a feat(repository): add migrate_follower_actor to FederationRepository 2026-05-28 01:33:13 +02:00
1949fce620 fix: accept follow for migrated actor URLs via UUID lookup v0.1.8 2026-05-28 00:30:58 +02:00
699258f830 feat: add targeted tracing logs for actor lookup and verification v0.1.7 2026-05-27 22:55:39 +02:00
9412a9739a fix: allow www. apex equivalence in actor domain verification
Threads serves actors at threads.net but their id field uses www.threads.net.
Extract apex_domain() helper and fall back to apex comparison when the
strict verify_domains_match check fails.
v0.1.6
2026-05-27 22:49:30 +02:00
13111c10b9 chore: bump version to 0.1.5 v0.1.5 2026-05-27 22:37:55 +02:00
2e3b6d5cd4 fix: accept optional outbox/followers/following and any AP actor type
Person struct now deserializes gracefully when outbox, followers, or
following are absent (Threads omits them for some actors). Accepts
Service/Application/Organization/Group in addition to Person.
manually_approves_followers defaults to false when absent.
2026-05-27 22:37:49 +02:00
bc857b2c08 feat: signed actor lookup and display_name on DbActor
Add display_name field to DbActor, populated from AP Person.name in
from_json. Expose LookedUpActor type and lookup_actor_by_handle method
on ActivityPubService — uses the existing signed webfinger_https path
so strict instances (Threads, etc.) return full actor data.
v0.1.4
2026-05-27 22:21:58 +02:00
7901b29f7c fix(actors): populate profile fields in read_from_id v0.1.3 2026-05-24 00:32:00 +02:00
Gabriel
a604e1bd40 docs: add README 2026-05-17 23:16:13 +02:00
Gabriel
f5374ec861 feat: add followers/following collection json methods 2026-05-17 22:58:30 +02:00
Gabriel
cc30582a1c feat: add broadcast_create_note, broadcast_update_note, base_url() accessor 2026-05-17 22:56:57 +02:00
Gabriel
f8dc20c026 gitignore 2026-05-17 22:54:03 +02:00
Gabriel
630cffe33f feat: k-ap public API, no ap_ports 2026-05-17 22:31:23 +02:00
Gabriel
cb84043ba3 feat: copy generic AP source files from thoughts 2026-05-17 22:30:25 +02:00
Gabriel
bf6ae50fcd init: k-ap crate scaffold 2026-05-17 22:23:52 +02:00