fix(api-token): keep a token's scope when -getApiToken regenerates it, add -tokenScope (#6700)

* fix(api-token): keep a token's scope when the CLI regenerates it

RecreateByName deleted the named row and created a new one without a Scope,
so the insert took the column default of admin. Since -tokenName lets the CLI
regenerate any token, rotating a monitor or node-sync token silently turned it
into a full-access one.

The replacement now takes the scope of the row it replaces, and a new name
still gets admin as before. A stored scope this build does not know, as after
a downgrade, fails the rotation and leaves the row alone instead of guessing.

Assisted-by: Claude Code:claude-opus-5-5 (mostly)

* feat(cli): let -getApiToken choose the scope of the token it issues

-tokenScope sets the scope on both branches of -getApiToken: the token minted
on a fresh panel and the one regenerated on a populated panel. Without the flag
a regenerated token keeps its scope and a new one gets admin, so every existing
invocation, install.sh included, behaves as before.

An unknown scope is refused before anything is deleted, so a typo cannot
revoke the token it meant to rotate.

Assisted-by: Claude Code:claude-opus-5-5 (mostly)

* fix(api-token): keep a token's expiry when the CLI regenerates it

RecreateByName built the replacement row with ExpiresAt 0, so running
`x-ui setting -getApiToken -tokenName <name>` on a token issued through
the API with a deadline handed back one that never expires, and said
nothing about it - the same silent widening this branch fixed for scope.

The replacement now carries the replaced row's ExpiresAt. A token whose
deadline has already passed is refused instead of rotated, since keeping
the deadline would mint a dead token and dropping it would revive an
expired credential without limit; the expired row is left untouched.

---------

Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
ilyusha
2026-10-02 20:05:27 +03:00
committed by GitHub
parent 721de5adde
commit 05a083eaef
4 changed files with 229 additions and 19 deletions
+6 -4
View File
@@ -498,7 +498,7 @@ func GetListenIP(getListen bool) {
}
}
func GetApiToken(getApiToken bool, tokenName string) {
func GetApiToken(getApiToken bool, tokenName, tokenScope string) {
if !getApiToken {
return
}
@@ -526,7 +526,7 @@ func GetApiToken(getApiToken bool, tokenName string) {
if rotated == "" {
rotated = cliFallbackTokenName
}
created, err := apiTokenService.RecreateByName(rotated)
created, err := apiTokenService.RecreateByName(rotated, tokenScope)
if err != nil {
fmt.Println("Failed to create a fallback API token:", err)
return
@@ -538,7 +538,7 @@ func GetApiToken(getApiToken bool, tokenName string) {
if name == "" {
name = installTokenName
}
created, err := apiTokenService.Create(name, "", 0)
created, err := apiTokenService.Create(name, tokenScope, 0)
if err != nil {
fmt.Println("create apiToken failed, error info:", err)
return
@@ -621,6 +621,7 @@ func main() {
var getCert bool
var getApiToken bool
var tokenName string
var tokenScope string
var resetTwoFactor bool
settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
settingCmd.BoolVar(&show, "show", false, "Display current settings")
@@ -634,6 +635,7 @@ func main() {
settingCmd.BoolVar(&getCert, "getCert", false, "Display current certificate settings")
settingCmd.BoolVar(&getApiToken, "getApiToken", false, "Print an API token for CLI use, regenerating it and invalidating the previous one; on a panel with no tokens yet it mints one instead")
settingCmd.StringVar(&tokenName, "tokenName", "", "Name of the token -getApiToken acts on (default: "+cliFallbackTokenName+", or "+installTokenName+" on a panel with no tokens)")
settingCmd.StringVar(&tokenScope, "tokenScope", "", "Scope of the token -getApiToken issues: admin, monitor or node-sync (default: the scope of the token it replaces, or admin for a new one)")
settingCmd.StringVar(&webCertFile, "webCert", "", "Set path to public key file for panel")
settingCmd.StringVar(&webKeyFile, "webCertKey", "", "Set path to private key file for panel")
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "Set token for Telegram bot")
@@ -732,7 +734,7 @@ func main() {
GetCertificate(getCert)
}
if getApiToken {
GetApiToken(getApiToken, tokenName)
GetApiToken(getApiToken, tokenName, tokenScope)
}
if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") {
updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)