fix: 重置密码踢线失败时回退断开并记 ok_kick_failed
This commit is contained in:
@@ -5,7 +5,9 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/http/httptest"
|
||||
@@ -369,6 +371,165 @@ func TestEndpointImportBOMAndCreate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndpointResetPasswordKickFailureFallsBack(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
db, err := store.Open(filepath.Join(dir, "data"), "FULL")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = db.Close() })
|
||||
|
||||
hash := auth.NewStubHashPool()
|
||||
if seedErr := admin.SeedAdminPassword(context.Background(), db, hash, testPassword); seedErr != nil {
|
||||
t.Fatal(seedErr)
|
||||
}
|
||||
kick := &kickRecorder{}
|
||||
var logBuf bytes.Buffer
|
||||
logger := slog.New(slog.NewTextHandler(&logBuf, nil))
|
||||
h := admin.New(admin.Deps{
|
||||
DB: db,
|
||||
Hash: hash,
|
||||
Tokens: admin.NewRandomAPITokens(),
|
||||
Locks: admin.NewMemoryLoginLocks(),
|
||||
Logger: logger,
|
||||
KickEndpoint: kick.Kick,
|
||||
PasswordResetKick: func(_ context.Context, _ string) (bool, error) {
|
||||
return false, errors.New("fatal kick failed")
|
||||
},
|
||||
})
|
||||
srv := httptest.NewServer(h)
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
jar, err := cookiejar.New(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client := &http.Client{Jar: jar}
|
||||
res := postJSON(t, client, srv.URL+"/api/admin/login",
|
||||
`{"username":"admin","password":"`+testPassword+`"}`, nil)
|
||||
env := decodeEnv(t, res)
|
||||
if res.StatusCode != http.StatusOK || !env.OK {
|
||||
t.Fatalf("login failed: %d %+v", res.StatusCode, env)
|
||||
}
|
||||
|
||||
res = postJSON(t, client, srv.URL+"/api/admin/endpoints",
|
||||
`{"id":"kick-fail","name":"踢失败","login_password":"oldpass12"}`,
|
||||
csrfHeaders())
|
||||
env = decodeEnv(t, res)
|
||||
if res.StatusCode != 200 || !env.OK {
|
||||
t.Fatalf("create: %d %+v", res.StatusCode, env)
|
||||
}
|
||||
|
||||
res = postJSON(t, client, srv.URL+"/api/admin/endpoints/kick-fail/reset-login-password",
|
||||
`{}`, csrfHeaders())
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
_ = res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
t.Fatalf("reset status=%d body=%s", res.StatusCode, body)
|
||||
}
|
||||
var resetEnv struct {
|
||||
OK bool `json:"ok"`
|
||||
Data struct {
|
||||
LoginPassword string `json:"login_password"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &resetEnv); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !resetEnv.OK || resetEnv.Data.LoginPassword == "" {
|
||||
t.Fatalf("want one-time password after kick failure, got %s", body)
|
||||
}
|
||||
if kick.count() != 1 {
|
||||
t.Fatalf("want KickEndpoint fallback once, got %d", kick.count())
|
||||
}
|
||||
logs := logBuf.String()
|
||||
if !strings.Contains(logs, "action=endpoint_reset_login_password") {
|
||||
t.Fatalf("want reset password audit, logs=%s", logs)
|
||||
}
|
||||
if !strings.Contains(logs, "result=ok_kick_failed") {
|
||||
t.Fatalf("audit want ok_kick_failed, logs=%s", logs)
|
||||
}
|
||||
if !strings.Contains(logs, "password reset kick failed") || !strings.Contains(logs, "fatal kick failed") {
|
||||
t.Fatalf("want error log for reset kick, logs=%s", logs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisableDeleteKickLogsError(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
db, err := store.Open(filepath.Join(dir, "data"), "FULL")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = db.Close() })
|
||||
|
||||
hash := auth.NewStubHashPool()
|
||||
if seedErr := admin.SeedAdminPassword(context.Background(), db, hash, testPassword); seedErr != nil {
|
||||
t.Fatal(seedErr)
|
||||
}
|
||||
kick := &kickRecorder{}
|
||||
var logBuf bytes.Buffer
|
||||
logger := slog.New(slog.NewTextHandler(&logBuf, nil))
|
||||
kickErr := errors.New("hook kick failed")
|
||||
h := admin.New(admin.Deps{
|
||||
DB: db,
|
||||
Hash: hash,
|
||||
Tokens: admin.NewRandomAPITokens(),
|
||||
Locks: admin.NewMemoryLoginLocks(),
|
||||
Logger: logger,
|
||||
KickEndpoint: kick.Kick,
|
||||
DisableKick: func(_ context.Context, _ string) (bool, error) {
|
||||
return false, kickErr
|
||||
},
|
||||
DeleteKick: func(_ context.Context, _ string) (bool, error) {
|
||||
return false, kickErr
|
||||
},
|
||||
})
|
||||
srv := httptest.NewServer(h)
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
jar, err := cookiejar.New(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client := &http.Client{Jar: jar}
|
||||
res := postJSON(t, client, srv.URL+"/api/admin/login",
|
||||
`{"username":"admin","password":"`+testPassword+`"}`, nil)
|
||||
env := decodeEnv(t, res)
|
||||
if res.StatusCode != http.StatusOK || !env.OK {
|
||||
t.Fatalf("login failed: %d %+v", res.StatusCode, env)
|
||||
}
|
||||
|
||||
for _, id := range []string{"dis-fail", "del-fail"} {
|
||||
res = postJSON(t, client, srv.URL+"/api/admin/endpoints",
|
||||
`{"id":"`+id+`","name":"`+id+`","login_password":"password1"}`,
|
||||
csrfHeaders())
|
||||
env = decodeEnv(t, res)
|
||||
if res.StatusCode != 200 || !env.OK {
|
||||
t.Fatalf("create %s: %d %+v", id, res.StatusCode, env)
|
||||
}
|
||||
}
|
||||
|
||||
res = postJSON(t, client, srv.URL+"/api/admin/endpoints/batch",
|
||||
`{"ids":["dis-fail"],"action":"disable"}`, csrfHeaders())
|
||||
env = decodeEnv(t, res)
|
||||
if res.StatusCode != 200 || !env.OK {
|
||||
t.Fatalf("disable: %d %+v", res.StatusCode, env)
|
||||
}
|
||||
|
||||
res = doReq(t, client, http.MethodDelete, srv.URL+"/api/admin/endpoints/del-fail", "", csrfHeaders())
|
||||
env = decodeEnv(t, res)
|
||||
if res.StatusCode != 200 || !env.OK {
|
||||
t.Fatalf("delete: %d %+v", res.StatusCode, env)
|
||||
}
|
||||
if kick.count() != 0 {
|
||||
t.Fatalf("disable/delete hook errors must not fall back to KickEndpoint, got %d", kick.count())
|
||||
}
|
||||
logs := logBuf.String()
|
||||
if !strings.Contains(logs, "disable kick failed") || !strings.Contains(logs, "delete kick failed") {
|
||||
t.Fatalf("want disable/delete kick error logs, logs=%s", logs)
|
||||
}
|
||||
}
|
||||
|
||||
func csrfHeaders() map[string]string {
|
||||
return map[string]string{"X-Nixmsg-Request": "1"}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user