Keep an audit log of who did what, and how they came in

Written from model events, spatie's role events and panel sign-ins, only for
what a person did, with secrets masked; pruned after a year.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 10:08:01 +02:00
parent 9bdbf834f6
commit 1fe1e67773
18 changed files with 744 additions and 1 deletions

View File

@@ -146,6 +146,8 @@ tags:
description: Several operations in one, and importing records from JSON or CSV
- name: Webhooks
description: Telling other systems what happened here
- name: Audit
description: Who did what, when, and how they came in
- name: Users
description: Local user administration
- name: Media
@@ -3975,6 +3977,52 @@ paths:
data: { $ref: "#/components/schemas/WebhookDelivery" }
"403": { $ref: "#/components/responses/Forbidden" }
/v1/audit:
get:
tags: [Audit]
summary: The audit log
description: |
Who did what, when, and how they came in — the panel, the API, a
machine token. Every change a person makes to content, the inbox,
redirects, repositories, media, settings, users, tokens and webhooks;
role changes; signing in to the panel. What the application does by
itself (a sync, a scheduled publication) is not here; each record's
own history has it. Secrets are never written down. Kept for a year
by default. Requires `audit.view_any`.
parameters:
- $ref: "#/components/parameters/PerPage"
- name: user_id
in: query
schema: { type: integer }
- name: action
in: query
description: "Exact, e.g. `pages.updated`, or a prefix ending in a dot, e.g. `pages.`."
schema: { type: string }
- name: subject_type
in: query
description: "e.g. `pages`, `users`, `settings`."
schema: { type: string }
- name: subject_id
in: query
schema: { type: string }
- name: via
in: query
description: "`panel`, `api`, `token`, `web` or `console`."
schema: { type: string }
- name: from
in: query
schema: { type: string, format: date-time }
- name: to
in: query
schema: { type: string, format: date-time }
responses:
"200":
description: Paginated entries, newest first
content:
application/json:
schema: { $ref: "#/components/schemas/AuditLogCollection" }
"403": { $ref: "#/components/responses/Forbidden" }
/v1/media:
get:
tags: [Media]
@@ -5863,6 +5911,42 @@ components:
links: { $ref: "#/components/schemas/PaginationLinks" }
meta: { $ref: "#/components/schemas/PaginationMeta" }
AuditLog:
type: object
properties:
id: { type: integer }
action: { type: string, example: pages.updated }
user:
type: object
properties:
id: { type: integer, nullable: true }
name: { type: string, nullable: true }
via: { type: string, enum: [panel, api, token, web, console] }
api_token_id: { type: integer, nullable: true, description: The machine token used. }
ip: { type: string, nullable: true }
subject:
type: object
nullable: true
properties:
type: { type: string }
id: { type: string }
label: { type: string, nullable: true }
changes:
type: object
nullable: true
additionalProperties: true
description: "Field → `{old, new}` on an update; the values on a creation. Long values are cut short."
created_at: { type: string, format: date-time }
AuditLogCollection:
type: object
properties:
data:
type: array
items: { $ref: "#/components/schemas/AuditLog" }
links: { $ref: "#/components/schemas/PaginationLinks" }
meta: { $ref: "#/components/schemas/PaginationMeta" }
MediaFor:
type: string
enum: [pages, technologies, projects, members, clients, testimonials]