feat(api): scoped, optionally expiring API tokens (#6201)

* security(api): add scoped expiring API tokens

* security(api): make scoped token lifecycle enforceable

---------

Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
This commit is contained in:
n0ctal
2026-08-15 18:31:49 +05:00
committed by GitHub
parent aecbad3ab1
commit 1230559e69
19 changed files with 785 additions and 94 deletions
+69 -7
View File
@@ -963,12 +963,19 @@
"enabled": {
"type": "boolean"
},
"expiresAt": {
"format": "int64",
"type": "integer"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"scope": {
"type": "string"
},
"token": {
"description": "SHA-256 hash; the plaintext is shown only once at creation",
"type": "string"
@@ -977,8 +984,10 @@
"required": [
"createdAt",
"enabled",
"expiresAt",
"id",
"name",
"scope",
"token"
],
"type": "object"
@@ -994,6 +1003,11 @@
"example": true,
"type": "boolean"
},
"expiresAt": {
"example": 0,
"format": "int64",
"type": "integer"
},
"id": {
"example": 2,
"type": "integer"
@@ -1002,6 +1016,10 @@
"example": "central-panel-a",
"type": "string"
},
"scope": {
"example": "admin",
"type": "string"
},
"token": {
"example": "new-token-string",
"type": "string"
@@ -1010,8 +1028,10 @@
"required": [
"createdAt",
"enabled",
"expiresAt",
"id",
"name"
"name",
"scope"
],
"type": "object"
},
@@ -2896,7 +2916,7 @@
},
{
"name": "API Tokens",
"description": "Manage Bearer tokens used for programmatic auth (bots, central panels acting on this node, CI). Each token has a unique name and an enabled flag — disable to revoke without deleting, delete to revoke permanently. Tokens are stored as SHA-256 hashes and the plaintext is returned only once, in the create response — it cannot be retrieved afterwards, so copy it then. Send one as <code>Authorization: Bearer &lt;token&gt;</code> on any /panel/api/* request — the token is a full-admin credential."
"description": "Manage scoped Bearer tokens for programmatic auth. Tokens grant admin, monitor, or node-sync access, may expire, and are stored as SHA-256 hashes. The plaintext is returned only once at creation."
},
{
"name": "Xray Settings",
@@ -10145,7 +10165,7 @@
"tags": [
"API Tokens"
],
"summary": "Mint a new API token. Name must be unique and 1-64 characters; the token string is server-generated and returned only in this response — it is stored hashed and cannot be retrieved later.",
"summary": "Mint a scoped API token. The server-generated plaintext is returned only once and stored as a hash.",
"operationId": "post_panel_api_setting_apiTokens_create",
"requestBody": {
"required": true,
@@ -10157,14 +10177,26 @@
"name": {
"type": "string",
"description": "Human-readable label, e.g. \"central-panel-a\"."
},
"scope": {
"type": "string",
"description": "admin (default), monitor, or node-sync."
},
"expiresAt": {
"type": "integer",
"description": "Future Unix milliseconds, or 0 for no expiry."
}
},
"required": [
"name"
"name",
"scope",
"expiresAt"
]
},
"example": {
"name": "central-panel-a"
"name": "central-panel-a",
"scope": "node-sync",
"expiresAt": 1798761600000
}
}
}
@@ -10193,8 +10225,10 @@
"obj": {
"createdAt": 1736000000,
"enabled": true,
"expiresAt": 0,
"id": 2,
"name": "central-panel-a",
"scope": "admin",
"token": "new-token-string"
}
}
@@ -10244,6 +10278,28 @@
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"expectedScope": {
"type": "string",
"description": "Stored scope expected by the operator."
}
},
"required": [
"expectedScope"
]
},
"example": {
"expectedScope": "node-sync"
}
}
}
},
"responses": {
"200": {
"description": "Successful response",
@@ -10298,14 +10354,20 @@
"enabled": {
"type": "boolean",
"description": "New enabled state."
},
"expectedScope": {
"type": "string",
"description": "Stored scope expected by the operator."
}
},
"required": [
"enabled"
"enabled",
"expectedScope"
]
},
"example": {
"enabled": false
"enabled": false,
"expectedScope": "node-sync"
}
}
}