Persona separation is tested, the API is smoke-checked on deploy, OAuth rows are pruned
- PersonaSeparationTests: two personas of one login follow the same account and post; nothing the API maps for one contains the other's id, username or the root id. - tools/smoke/mastodon-api.sh checks what a client meets first (instance v1/v2, discovery, app registration, client credentials, an app token refused by a user endpoint, the public timeline, revocation) and, given a persona token, verify_credentials, home and notifications. deploy.yml runs it after every deploy. - OAuthPruner removes invalid tokens and authorizations older than two weeks, every six hours. - The consent page no longer sets form-action, which browsers apply to the redirect back to the client after the form is posted. CLAUDE.md gains the Mastodon API layout and invariants. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
This commit is contained in:
1 parent
70b4c45eb0
commit
d8f163b5ce
7 files changed
+157
-3
No files matched your search
@@ -90,7 +90,14 @@ PrivaPub/ ASP.NET Core Web API, net10.0
|
||||
Social/ FollowService (local in-process, remote Follow/Accept), Notifications
|
||||
Timelines/ Fanout (TimelineEntry rows, Mastodon's home rules), TimelineService
|
||||
Privacy/ VisibilityPolicy (IsPublic expression, CanSee)
|
||||
Web/Pages/ Razor: /@{user}, /@{user}/{id} (public posts only, strict CSP, noindex)
|
||||
Domain/Statuses/ StatusService: publish, edit, remove, favourite, reblog, for a persona (both client APIs use it)
|
||||
Api/Mastodon/
|
||||
Auth/ OpenIddict setup (keys in Mongo), MastodonScopes, TokenController, OAuthPruner
|
||||
Infrastructure/ MastodonController (avatar context, scopes, errors, Link), MastodonParams, MastodonJson, Page
|
||||
Entities/ Mappers/ Mastodon entities; MastodonMapper (Account, Status), AccountSearch
|
||||
Controllers/ apps, instance, accounts, statuses, timelines, notifications, search, stubs
|
||||
Web/Pages/ Razor: /@{user}, /@{user}/{id} (public posts only, strict CSP, noindex);
|
||||
OAuth/: /oauth/login (root password), /oauth/authorize (choose persona, consent)
|
||||
Services/ RootUsersService, GroupUsersService, PostsService, AppConfigurationService, …
|
||||
Models/ Mongo entities: User/, Group/, Post/, Federation/, Jobs/, AppConfiguration
|
||||
StaticServices/ DbEntities (Find<T> accessors), AuthTokenManager (JWT), PasswordHasher
|
||||
@@ -171,6 +178,18 @@ cd /var/www/privapub.thepra.dev && sudo -u www-data ASPNETCORE_ENVIRONMENT=Produ
|
||||
13. **Home timelines are written, not computed:** every stored or created post goes through `Fanout.Distribute`, and
|
||||
every delete removes its `TimelineEntry` rows. Local deletes are soft (content cleared, 410 Tombstone).
|
||||
|
||||
## Mastodon client API invariants
|
||||
|
||||
1. **A token is one persona.** Its subject is the avatar id; the root id lives only in the fifteen-minute `/oauth`
|
||||
cookie used while choosing the persona, and never in a token, an authorization or a response.
|
||||
2. `/api/*` authenticates with OpenIddict validation, everything else with the old JWT (`PrivaPub` policy scheme).
|
||||
Every `/api` request re-checks that the persona's root is neither banned nor deleted (`MastodonController`).
|
||||
3. Read parameters through `Params` (query, form and JSON merged Rails-style), never MVC binding. A value type read
|
||||
from a conditional must say `(int?)null`, not `default`: that bug once made every list one item long.
|
||||
4. Answer with `Json(...)` (snake_case, explicit nulls) or `Error(status, message)`; page lists with `Page` and `Link`.
|
||||
5. Unsupported features answer empty lists or 422 with a message, never 404 or 500, so clients degrade.
|
||||
6. Advertise `4.2.0 (compatible; PrivaPub)` until grouped notifications exist.
|
||||
|
||||
## Privacy invariants
|
||||
|
||||
- **No root id in federation output, NodeInfo or logs, no IP next to an identity in logs, and no `ex.Message` to a
|
||||
@@ -233,7 +252,8 @@ the owner's GoToSocial at social.arasaka.software. **Ask before acting from the
|
||||
- **CI/CD:** push to `master` runs `build.yml` (build + tests) on the instance-wide `build` runner. A `v*` tag runs
|
||||
`deploy.yml`: tests, self-contained linux-x64 publish, snapshot and `mongodump` to `/var/backups/privapub.thepra.dev`,
|
||||
stop → rsync → start, a `127.0.0.1:6970/build.json` health loop with rollback, then public checks (actor, NodeInfo,
|
||||
Swagger 404, inbox junk 400, unsigned 401).
|
||||
Swagger 404, inbox junk 400, unsigned 401) and `tools/smoke/mastodon-api.sh` (app registration, client credentials,
|
||||
discovery, instance, public timeline; pass a persona token as a second argument to check the signed-in side).
|
||||
- **The box:** Max (`nuvola.xyz`). Unit `privapub` runs as www-data from `/var/www/privapub.thepra.dev` with
|
||||
`ASPNETCORE_ENVIRONMENT=Production`.
|
||||
- **One-time root setup:** `deploy/max/setup.sh`, run through `../arasaka.software/tools/max/run.sh`.
|
||||
|
||||
Reference in new issue
Block a user