feat(12.1-01): cabana.ForbiddenError answers a refused write with 403
- hooks and bulk, record, toolbar and widget actions may return it - 403 forbidden with the localized message and field details; the write's transaction is rolled back; other errors stay the opaque 500 - form shows a refused save as a persistent banner and keeps the values; a refused delete is a toast - smoke tests, OpenAPI notes, dist, README, docs
This commit is contained in:
@@ -221,6 +221,20 @@ A hook or scope that has to read the database during a write should use the tran
|
||||
|
||||
Scope reads and writes with `pact.ListExtendQuery` and `pact.FormExtendQuery` rather than checking in a hook: the scope then applies to every route, including relation and action routes.
|
||||
|
||||
## Refusing a write
|
||||
|
||||
A hook or an action stops a write by returning an error. Which error decides what the administrator sees:
|
||||
|
||||
| Error | Answer |
|
||||
|-------|--------|
|
||||
| `cabana.ValidationError` | 422 `validation_failed`, with its `Details` as messages per field. |
|
||||
| `cabana.ForbiddenError` | 403 `forbidden`, with its `Message` and its `Details`. |
|
||||
| any other error | The opaque 500. The error is logged on the server and its text never reaches the client. |
|
||||
|
||||
Return a `cabana.ForbiddenError` when the signed-in administrator may open the screen but may not make this particular change, for example editing a record that needs a higher permission. `Message` is a translation key or text; cabana translates it, and every `Details` message, in the request locale. `Details` maps a field name to a list of messages, as a `cabana.ValidationError` does, and may be left out. With an empty `Message` the admin shows its own text.
|
||||
|
||||
Every one of these errors rolls the write's transaction back, so a refused write changes nothing: a bulk action that refuses on its third record leaves the first two untouched. The admin SPA shows a refused save as a banner above the form and keeps what the administrator typed; a refused delete or action is a toast. A `cabana.ForbiddenError` works the same from the form hooks, the relation hooks, and bulk, record, toolbar and widget actions.
|
||||
|
||||
## Toolbar actions
|
||||
|
||||
`toolbar.buttons` in `config_list.yaml` lists the built-in `create` and `delete` and any action the controller registers through `pact.HasAdminActions`. The declarations are enforced by the server, not only shown by the SPA. `POST /{controller}` needs a `config_form.yaml` and `create` in `toolbar.buttons`; `PUT` and `DELETE /{controller}/{id}` need a form (the form screen carries the delete button, as in WinterCMS); `POST /{controller}/bulk-delete` needs `delete` in `toolbar.buttons`, which in turn needs `showCheckboxes: true`. A write the controller does not declare answers 403 `forbidden`. See [Partials and widgets](partials-and-widgets.md) for actions and the rest of the extension points.
|
||||
|
||||
Reference in New Issue
Block a user