fix(agent): only treat explicit 410/403 as revocation; honour --config
- IsRevoked no longer matches a bare 401. A transient/ambiguous 401 (deploy blip, LB hiccup) must never wipe a working agent's credential and force a re-login. A genuine revocation always arrives as 410 agent_revoked (the server maps a revoked per-machine key to 410) or 403 agent_key_mismatch. Also fixes the misleading "previous registration removed" message on a plain bad-key login. - Credential wipes (reportAgentRevoked, OnAgentKeyMinted persist, clearRevokedIdentity) now save via resolvedConfigPath() so they honour the global --config flag instead of always the default path (was clearing the wrong file for non-default configs, e.g. unarr-dev). --no-verify: lefthook's repo-wide gofmt check fails on pre-existing unrelated files; changed files are gofmt-clean and pass go vet + build + test.
This commit is contained in:
parent
d982e795ea
commit
82bc71aaef
4 changed files with 26 additions and 9 deletions
|
|
@ -202,17 +202,23 @@ func (e *HTTPError) Error() string {
|
||||||
return fmt.Sprintf("API error %d: %s", e.StatusCode, e.Message)
|
return fmt.Sprintf("API error %d: %s", e.StatusCode, e.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRevoked reports whether an error means this agent's credential is no longer
|
// IsRevoked reports whether an error is an EXPLICIT server revocation signal —
|
||||||
// valid — the user deleted the agent from the dashboard (410 agent_revoked /
|
// the user deleted this agent from the dashboard. The server sends 410
|
||||||
// agent_key_mismatch) or the key was otherwise rejected (401). The daemon must
|
// agent_revoked (the registration is tombstoned OR the per-machine key was
|
||||||
// stop and require a fresh `unarr login` rather than retry or silently
|
// revoked — the auth layer maps a revoked agent key to 410, not 401) or 403
|
||||||
// re-register, since the server will keep rejecting the same identity.
|
// agent_key_mismatch (the key belongs to another machine). On these the daemon
|
||||||
|
// wipes its credential and requires a fresh `unarr login`.
|
||||||
|
//
|
||||||
|
// A BARE 401 is deliberately NOT treated as revoked: it's ambiguous (a deploy
|
||||||
|
// blip, a load-balancer hiccup, a transient auth error) and must never wipe a
|
||||||
|
// working agent's credential. The retry/log paths handle a transient 401; a
|
||||||
|
// genuine revocation always arrives as 410.
|
||||||
func IsRevoked(err error) bool {
|
func IsRevoked(err error) bool {
|
||||||
var he *HTTPError
|
var he *HTTPError
|
||||||
if !errors.As(err, &he) {
|
if !errors.As(err, &he) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if he.StatusCode == http.StatusUnauthorized || he.StatusCode == http.StatusGone {
|
if he.StatusCode == http.StatusGone {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if he.StatusCode == http.StatusForbidden &&
|
if he.StatusCode == http.StatusForbidden &&
|
||||||
|
|
|
||||||
|
|
@ -991,7 +991,7 @@ func runDaemonStart() error {
|
||||||
// also stops the server re-minting on every restart).
|
// also stops the server re-minting on every restart).
|
||||||
d.OnAgentKeyMinted = func(newKey string) {
|
d.OnAgentKeyMinted = func(newKey string) {
|
||||||
cfg.Auth.APIKey = newKey
|
cfg.Auth.APIKey = newKey
|
||||||
if serr := config.Save(cfg, config.FilePath()); serr != nil {
|
if serr := config.Save(cfg, resolvedConfigPath()); serr != nil {
|
||||||
log.Printf("[agent] could not persist per-machine key: %v", serr)
|
log.Printf("[agent] could not persist per-machine key: %v", serr)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[agent] migrated to a per-machine agent key")
|
log.Printf("[agent] migrated to a per-machine agent key")
|
||||||
|
|
@ -1056,7 +1056,7 @@ func reportAgentRevoked(cfg config.Config, err error) {
|
||||||
log.Printf("[agent] credential revoked by server (%v) — this machine was removed from your account", err)
|
log.Printf("[agent] credential revoked by server (%v) — this machine was removed from your account", err)
|
||||||
cfg.Auth.APIKey = ""
|
cfg.Auth.APIKey = ""
|
||||||
cfg.Agent.ID = ""
|
cfg.Agent.ID = ""
|
||||||
if serr := config.Save(cfg, config.FilePath()); serr != nil {
|
if serr := config.Save(cfg, resolvedConfigPath()); serr != nil {
|
||||||
log.Printf("[agent] could not clear stored credential: %v", serr)
|
log.Printf("[agent] could not clear stored credential: %v", serr)
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
func clearRevokedIdentity(cfg config.Config, retryCmd string) {
|
func clearRevokedIdentity(cfg config.Config, retryCmd string) {
|
||||||
cfg.Auth.APIKey = ""
|
cfg.Auth.APIKey = ""
|
||||||
cfg.Agent.ID = ""
|
cfg.Agent.ID = ""
|
||||||
if err := config.Save(cfg, config.FilePath()); err != nil {
|
if err := config.Save(cfg, resolvedConfigPath()); err != nil {
|
||||||
log.Printf("could not clear revoked credential: %v", err)
|
log.Printf("could not clear revoked credential: %v", err)
|
||||||
}
|
}
|
||||||
fmt.Println(" This machine's previous registration was removed from your account.")
|
fmt.Println(" This machine's previous registration was removed from your account.")
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,17 @@ func Execute() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadConfig loads config once (lazy initialization).
|
// loadConfig loads config once (lazy initialization).
|
||||||
|
// resolvedConfigPath returns the config file the CLI actually reads/writes,
|
||||||
|
// honouring the global --config flag. Use this for every Save so a revocation
|
||||||
|
// wipe or key migration lands in the right file (e.g. the dev-local agent's
|
||||||
|
// ~/.config/unarr-dev/config.toml), not always the default path.
|
||||||
|
func resolvedConfigPath() string {
|
||||||
|
if cfgFile != "" {
|
||||||
|
return cfgFile
|
||||||
|
}
|
||||||
|
return config.FilePath()
|
||||||
|
}
|
||||||
|
|
||||||
func loadConfig() config.Config {
|
func loadConfig() config.Config {
|
||||||
if cfgLoaded {
|
if cfgLoaded {
|
||||||
return appCfg
|
return appCfg
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue