Repository navigation
Conversation
A client can send the normal create (POST /api/{type}) or update
(PATCH /api/{type}/{id}) request with `Precognition: true` to have it
authorized and validated without being run. `Precognition-Validate-Only:
name,email` limits validation to the listed fields; an empty list, or one
that names no field of the resource, validates every field.
Responses carry `Precognition: true` and `Vary: Precognition`:
- 204 with `Precognition-Success: true` when the request passes;
- 422 with the same body the normal request would get;
- 401/403/404 whenever the normal request would be refused, before
validation runs.
A 204 means the fields are valid and the endpoint is visible to the actor,
not that the write would succeed: the endpoint's before hooks, the
resource's creating/updating/saving hooks and the field setters don't run,
and neither does any authorization that lives in them. Nothing is saved
and no events are dispatched.
Precognitive requests skip the endpoints' own throttlers and are limited
by `flarum.api.precognition_throttler` instead: 60 per minute per user,
or per IP address for guests. CSRF protection still applies.
A `Precognition` header on any other route (index, show, delete, non-
resource routes), or with any value other than `true`, fails with a 400
(`precognition_not_supported`) and the request is not run.
|
Note Automated review for @novacuum. Multiple AI models with Flarum-specific knowledge reviewed this pull request for me, and a further agent verified their findings; I haven't read it myself yet. I'll look at this pull request personally as soon as I can. Warning The security review found one or more security issues while reviewing this pull request. I have been informed of the details securely and will follow up. Reviewed commit
|
Adds Precognition-style validate-only requests to the JSON:API. A client sends the normal create or update request with
Precognition: true, and the endpoint authorizes and validates it as usual but doesn't save it. Frontends can then validate a form field by field against the server's real rules, includinguniquechecks and rules added by extensions, without copying them into JavaScript. The admin resource forms from #5324 will use this to check rules only the server can evaluate, once both are merged.Request
POST /api/{type}orPATCH /api/{type}/{id}(or any otherCreate/Updateendpoint of a resource, whatever its name) withPrecognition: true(case-insensitive; a duplicatedtrue, trueis accepted).Precognition-Validate-Only: name,emaillimits validation to those fields. If the list is empty or names no field of the resource, every field is validated, so a typo can't produce a false pass.Response (always with
Precognition: trueandVary: Precognition)204withPrecognition-Success: trueif the request is valid.422with the same body the normal request would return.401/403/404wherever the normal request would be refused. The model lookup, endpoint authorization and field writability are checked before validation, so the answer doesn't reveal anything about records the actor can't reach.400(precognition_not_supported) for aPrecognitionheader on any other route (index, show, delete,/api/token,/api/settings, ...) or with any value other thantrue. The request isn't run, because running it normally would perform the write the client only wanted to validate.429from the precognition limiter (below).A
204means the fields are valid and the endpoint is visible to the actor. It doesn't mean the write would succeed. The endpoint's action is never invoked, so before/after hooks, the resource'screating/updating/savinghooks, field setters and events don't run, and neither does any authorization inside them (e.g. theeditCredentialscheck inUserResource's email setter). This also holds when an extension replaces the action.Throttling
Precognitive requests skip the endpoints' write throttlers (flood control, email timeouts) and use their own limiter instead,
flarum.api.precognition_throttler. It uses Laravel'sRateLimiterand allows 60 requests a minute per user, or per IP for guests, souniquerules can't be used to enumerate usernames and emails. The guest key comes from the resolvedipAddressattribute, not fromX-Forwarded-Forand similar headers. Rebind the key to change the limit.Implementation
HandlePrecognitiveRequestsruns afterCheckCsrfTokenand beforeThrottleApi. It is the only code that reads the header, and it marks supported requests with a request attribute. Support is decided by matching the complete route name ([prefix.]{type}.{endpoint}) against the resources' endpoints, so endpoint names containing dots work.CreateandUpdateuse a newHandlesPrecognitiontrait. For a precognitive request, it does the same preparation asEndpoint::handle(), then the endpoint's steps up toassertDataValid().assertDataValid()keeps its signature, so endpoint subclasses that override it still load; it narrows the rules to the Validate-Only fields itself, through aprecognitiveFields()hook. The internal API client excludes the middleware.Testing
46 integration tests in
framework/core/tests/integration/api/precognition/. They cover the response codes and headers, Validate-Only and its fallbacks, refusals before validation on users, discussions, posts, groups and access tokens, malformed headers and unsupported routes (400, nothing written), CSRF, and identical 422 bodies. They also cover the limiter (per IP for guests, not bypassable through forwarding headers, per user across IPs and tokens, and a counter expiring mid-request still expires), custom endpoints (dotted names, subclasses overridingassertDataValid()) and a side-effects test showing that valid precognitive writes on core resources change no rows and dispatch no write events. Run on SQLite and on MariaDB with a table prefix.