Feature: OAuth C2S outbox POST — apps can drive the account (phase 1 done)
Second half of AP Client-to-Server: a bearer-authenticated POST to
/ap/users/:slug/outbox accepts activities and translates them onto the existing
delivery machinery (deliverReply / sendInteraction / followActor /
deliverCreate) rather than reimplementing federation.
- ingestOutboxActivity(site, user, activity) dispatches Create(Note) (reply ->
resolveRemoteNote + deliverReply; top-level -> a sanitized microblog post +
deliverCreate), Like, Announce (+upsertBoostedNote), Follow, and Undo of
Like/Announce/Follow. A bare Note is wrapped in a Create per AP section 6.
Client "source" (plain) is preferred over "content" (HTML) for replies;
top-level content is HtmlSanitizerService.sanitize()d. Unhandled verbs return
a clear 400 rather than a silent no-op.
- Route: bearer via OAuthService.verifyBearer; the token is scoped to one site,
so a slug mismatch is 403 and a readonly account is 403; no token is 401 with
WWW-Authenticate. 201+Location for created objects, 202 for side-effect verbs.
Declared after apJson (shared with the inbox handler) to avoid a TDZ on the
const.
Verified live end to end against a running server with a real OAuth token:
top-level Note -> 201 + Location, stored published + sanitized (script stripped)
+ served as a valid Note at /ap/notes/<id>; Like/Follow -> 202; unresolvable
reply -> honest 502; no-token 401, wrong-site 403, unsupported type 400. 7 new
unit tests for the deterministic dispatch paths (80 green).
Ivory and other Mastodon-API clients are NOT supported by this: they speak
Mastodon's REST API (/api/v1/apps, /api/v1/instance, secret-based OAuth), not AP
C2S. That's a separate track (klonkt-demo-mastapi). Delete/Update of arbitrary
objects deferred (klonkt-demo-c2sdel). Beads: klonkt-demo-1w4.
Co-Authored-By: Claude Opus 4.8 <noreply@…>