mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 07:41:27 +00:00
feat: personal access tokens with scoped bearer auth
Add cnp_ personal access tokens so the CLI, MCP server, and external agents authenticate without a browser session — the agent-first spine from ROADMAP2 phase A. Modeled on agent_auth_tokens: raw token shown once, only SHA-256 hash stored. - user_tokens table (goose migration + sqlc), name/prefix/scope/expiry, owner-scoped revoke, last_used_at stamping - middleware: cnp_ bearer resolution, read scope gates mutations with 403 SCOPE_INSUFFICIENT, non-admin scopes cap is_admin even for admin owners — a token never outranks its scope or its owner - GET/POST/DELETE /api/v1/user/tokens + OpenAPI + generated types - Settings UI: create with scope/expiry pickers, one-time token display with copy, list with last-used/expiry, confirm-revoke - docs: OPERATIONS.md section, README feature line Verified end-to-end against live Postgres: create/list/revoke, read scope blocks PUT (403), write PAT on admin owner blocked from admin routes (403), admin scope works, revoked token returns 401. Authored By: TDvorak <[email protected]>
This commit is contained in:
1 parent
f9ae5b962c
commit
e881747388
17 files changed
+1141
-30
No files matched your search
@@ -152,6 +152,97 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/user/tokens:
|
||||
get:
|
||||
tags:
|
||||
- User
|
||||
summary: List personal access tokens
|
||||
description: Returns the caller's non-revoked personal access token metadata. Token values and hashes are never returned.
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
'200':
|
||||
description: Token list
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
tokens:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/UserToken'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
post:
|
||||
tags:
|
||||
- User
|
||||
summary: Create personal access token
|
||||
description: Issues a `cnp_` bearer token for CLI/MCP/agent authentication. The raw token is returned exactly once and never stored — only its SHA-256 hash persists. Admin-scope tokens require a platform admin account.
|
||||
security:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateUserTokenRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: Token created; raw value returned once
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UserTokenCreated'
|
||||
'400':
|
||||
description: Invalid input
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'403':
|
||||
description: Admin scope requires platform admin
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/user/tokens/{id}:
|
||||
delete:
|
||||
tags:
|
||||
- User
|
||||
summary: Revoke personal access token
|
||||
description: Marks one of the caller's tokens as revoked. Clients presenting it are rejected from then on.
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: Token revoked
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
'404':
|
||||
description: Token not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
# Projects
|
||||
/projects:
|
||||
get:
|
||||
@@ -6395,6 +6486,66 @@ components:
|
||||
type: string
|
||||
description: Human-readable label for the token (e.g. node name)
|
||||
|
||||
UserToken:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
key_prefix:
|
||||
type: string
|
||||
description: First 12 chars of the token, for identification in lists
|
||||
scope:
|
||||
type: string
|
||||
enum: [read, write, admin]
|
||||
expires_at:
|
||||
type: string
|
||||
format: date-time
|
||||
last_used_at:
|
||||
type: string
|
||||
format: date-time
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- key_prefix
|
||||
- scope
|
||||
- created_at
|
||||
|
||||
UserTokenCreated:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/UserToken'
|
||||
- type: object
|
||||
properties:
|
||||
token:
|
||||
type: string
|
||||
description: Raw cnp_ token value, returned exactly once at creation
|
||||
required:
|
||||
- token
|
||||
|
||||
CreateUserTokenRequest:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: Human-readable label (e.g. "laptop cli", "ci deploy")
|
||||
scope:
|
||||
type: string
|
||||
enum: [read, write, admin]
|
||||
default: write
|
||||
description: read = GET/HEAD only; write = all non-admin routes; admin = everything (requires admin account)
|
||||
expires_in_days:
|
||||
type: integer
|
||||
minimum: 0
|
||||
maximum: 3650
|
||||
description: 0 or omitted = never expires
|
||||
required:
|
||||
- name
|
||||
|
||||
AgentCapabilities:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Reference in new issue
Block a user