mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-12 19:57:37 +00:00
fix(api-tokens): harden with security fixes, design compliance, and test coverage (#567)
Security: add JWT-level expiry ceiling (400d), per-user token count limit (25), and token name uniqueness enforcement. Fix async clipboard copy. Design: migrate Select to Combobox, apply card bevel styling, fix icon strokeWidth, add tabular-nums to timestamps, fix destructive button pattern. Tests: expand from ~20 to 47 test cases covering creation validation, token limits, name uniqueness, last_used_at tracking, ownership constraints, delete edge cases, and registry blocked endpoints. Docs: update API Tokens docs with token limits, name uniqueness, registry restrictions, and JWT expiry ceiling. Update OpenAPI spec with 409 response.
This commit is contained in:
@@ -33,6 +33,7 @@ Regardless of scope, **all** API tokens are blocked from:
|
||||
| **Node management** | Adding, updating, or deleting remote nodes |
|
||||
| **License management** | Activating or deactivating license keys |
|
||||
| **Token management** | Creating, listing, or revoking API tokens |
|
||||
| **Registry management** | Viewing, creating, updating, deleting, or testing registry credentials |
|
||||
| **Console access** | Generating console session tokens for interactive terminals |
|
||||
|
||||
These restrictions ensure that API tokens cannot escalate privileges or modify the identity and infrastructure configuration of your Sencho instance. These operations require a human user session (browser login).
|
||||
@@ -47,6 +48,10 @@ These restrictions ensure that API tokens cannot escalate privileges or modify t
|
||||
- **Expiration**: Choose 30 days, 60 days, 90 days, 1 year, or no expiration. Tokens without an expiration must be revoked manually.
|
||||
4. Click **Create**. A green banner appears with the raw token value. Copy it immediately using the copy button.
|
||||
|
||||
<Note>
|
||||
Each user can have up to **25 active API tokens**. Token names must be unique among your active tokens. If you need to reuse a name, revoke the existing token first.
|
||||
</Note>
|
||||
|
||||
<Frame>
|
||||
<img src="/images/api-tokens/api-tokens-overview.png" alt="API Tokens management view in Settings Hub showing the token list and create form" />
|
||||
</Frame>
|
||||
@@ -101,7 +106,9 @@ Click the trash icon next to any token in the API Tokens settings tab. A confirm
|
||||
## Security model
|
||||
|
||||
- **Hashed storage**: Only a SHA-256 hash of the token is stored in the database. The raw token is never persisted.
|
||||
- **JWT-level expiry ceiling**: Every token includes a built-in expiry at the JWT level as defense-in-depth, independent of the user-configured expiration. The database-level expiry is always the tighter constraint.
|
||||
- **Audit trail**: All actions performed via API tokens are recorded in the [Audit Log](/features/audit-log) under the creating user's username.
|
||||
- **Optional expiry**: Tokens can be created with an expiration period (30 days, 60 days, 90 days, or 1 year). Tokens without an expiry must be revoked manually when no longer needed.
|
||||
- **Per-user limits**: Each user can create up to 25 active tokens. Token names must be unique among active tokens for the same user.
|
||||
- **Usage tracking**: Each token tracks when it was last used, visible in the token list.
|
||||
- **Scope enforcement**: Permission checks happen at the middleware level before any route handler executes, ensuring consistent enforcement across all endpoints.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 38 KiB |
+9
-3
@@ -1303,7 +1303,7 @@ paths:
|
||||
**Note:** API tokens cannot create other API tokens.
|
||||
responses:
|
||||
"201":
|
||||
description: Token created. The `token` field contains the full JWT — save it now, it won't be shown again.
|
||||
description: Token created. The `token` field contains the full JWT. Save it now; it will not be shown again.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
@@ -1316,13 +1316,19 @@ paths:
|
||||
type: string
|
||||
description: Full JWT token. Store securely — this is the only time it's returned.
|
||||
"400":
|
||||
description: Validation error.
|
||||
description: Validation error (missing/invalid fields, or maximum of 25 active tokens reached).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"409":
|
||||
description: An active token with this name already exists.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
requestBody:
|
||||
@@ -1335,7 +1341,7 @@ paths:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: Human-readable token name.
|
||||
description: Human-readable token name. Must be unique among the user's active tokens.
|
||||
maxLength: 100
|
||||
example: CI/CD Deploy Token
|
||||
scope:
|
||||
|
||||
Reference in New Issue
Block a user