Issue machine tokens with limited permissions and an expiry
A token acts as the person who made it, within the permissions it was given and those they still hold, checked on every request. Permission checks now go through one gate rule, since spatie's answered first and never asked the token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,9 +16,8 @@ info:
|
||||
## Authentication
|
||||
|
||||
Every endpoint under `/v1` except `POST /v1/auth/token` requires a bearer token issued
|
||||
by the **Pireon identity server** (`https://oauth.pireon.dev`). This
|
||||
application does not issue tokens of its own; it validates the ones the
|
||||
identity server issued.
|
||||
by the **Pireon identity server** (`https://oauth.pireon.dev`), which this
|
||||
application validates — or a **machine token** of its own.
|
||||
|
||||
Obtain a token through `POST /v1/auth/token`, which proxies the identity
|
||||
server's password grant. The panel's own sign-in form uses the same grant,
|
||||
@@ -26,6 +25,13 @@ info:
|
||||
against `https://oauth.pireon.dev/oauth/authorize` remains available for
|
||||
clients that prefer never to handle a password.
|
||||
|
||||
Machine tokens (`pmt_…`) are for scripts and integrations: a person makes
|
||||
one with `POST /v1/tokens`, naming the permissions it gets — never more
|
||||
than their own — and when it expires. It acts as that person within
|
||||
those permissions, checked on every request, so taking a role away from
|
||||
them narrows their tokens at once. It is checked here and never sent to
|
||||
the identity server.
|
||||
|
||||
## Authorisation
|
||||
|
||||
The identity server establishes *who* the caller is. What they may *do* is
|
||||
@@ -1029,6 +1035,71 @@ paths:
|
||||
data: { $ref: "#/components/schemas/User" }
|
||||
"401": { $ref: "#/components/responses/Unauthorized" }
|
||||
|
||||
/v1/tokens:
|
||||
get:
|
||||
tags: [Auth]
|
||||
summary: List my machine tokens
|
||||
description: Newest first, revoked and expired ones included. A super admin may ask for everyone's with `all`. Not available to a machine token.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/PerPage"
|
||||
- name: all
|
||||
in: query
|
||||
description: "Everyone's tokens. Super admins only."
|
||||
schema: { type: boolean }
|
||||
responses:
|
||||
"200":
|
||||
description: Paginated tokens
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/ApiTokenCollection" }
|
||||
"403": { $ref: "#/components/responses/Forbidden" }
|
||||
post:
|
||||
tags: [Auth]
|
||||
summary: Make a machine token
|
||||
description: |
|
||||
`abilities` are permission names — `pages.update` — or a whole
|
||||
resource — `pages.*` — and only ones the caller holds. It expires
|
||||
after 90 days unless `expires_at` says otherwise, a year at most. The
|
||||
token itself is in the answer's `token`, and nowhere else ever again.
|
||||
Not available to a machine token.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [name, abilities]
|
||||
properties:
|
||||
name: { type: string, maxLength: 120, example: Despliegue continuo }
|
||||
abilities: { type: array, items: { type: string }, example: ["technologies.*", "media.view_any"] }
|
||||
expires_at: { type: string, format: date-time }
|
||||
responses:
|
||||
"201":
|
||||
description: Token made
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
data: { $ref: "#/components/schemas/ApiToken" }
|
||||
token: { type: string, description: The token. Shown once. }
|
||||
"403": { $ref: "#/components/responses/Forbidden" }
|
||||
"422": { $ref: "#/components/responses/ValidationError" }
|
||||
|
||||
/v1/tokens/{token}:
|
||||
delete:
|
||||
tags: [Auth]
|
||||
summary: Revoke a machine token
|
||||
description: It stops working at once and stays listed as revoked. Your own, or anyone's for a super admin.
|
||||
parameters:
|
||||
- name: token
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: integer }
|
||||
responses:
|
||||
"204": { description: Revoked }
|
||||
"403": { $ref: "#/components/responses/Forbidden" }
|
||||
|
||||
/v1/pages:
|
||||
get:
|
||||
tags: [Pages]
|
||||
@@ -4046,9 +4117,9 @@ components:
|
||||
type: http
|
||||
scheme: bearer
|
||||
description: |
|
||||
An access token issued by https://oauth.pireon.dev. Validated on each
|
||||
request against the identity server's `GET /api/user`, with the result
|
||||
cached briefly.
|
||||
An access token issued by https://oauth.pireon.dev, validated on each
|
||||
request against the identity server's `GET /api/user` with the result
|
||||
cached briefly — or a machine token (`pmt_…`) from `POST /v1/tokens`.
|
||||
|
||||
ingestToken:
|
||||
type: apiKey
|
||||
@@ -5542,6 +5613,33 @@ components:
|
||||
failed: { type: integer }
|
||||
dry_run: { type: boolean, nullable: true }
|
||||
|
||||
ApiToken:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: integer }
|
||||
name: { type: string }
|
||||
prefix: { type: string, description: "The first characters, to tell tokens apart." }
|
||||
abilities: { type: array, items: { type: string } }
|
||||
owner:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: integer }
|
||||
name: { type: string, nullable: true }
|
||||
status: { type: string, enum: [active, expired, revoked] }
|
||||
expires_at: { type: string, format: date-time }
|
||||
last_used_at: { type: string, format: date-time, nullable: true }
|
||||
revoked_at: { type: string, format: date-time, nullable: true }
|
||||
created_at: { type: string, format: date-time }
|
||||
|
||||
ApiTokenCollection:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/ApiToken" }
|
||||
links: { $ref: "#/components/schemas/PaginationLinks" }
|
||||
meta: { $ref: "#/components/schemas/PaginationMeta" }
|
||||
|
||||
MediaFor:
|
||||
type: string
|
||||
enum: [pages, technologies, projects, members, clients, testimonials]
|
||||
|
||||
Reference in New Issue
Block a user