mirror of
https://github.com/BaaRai/ARTEX.git
synced 2026-11-13 11:40:53 +08:00
Comments, doc comments, UI labels, log and error messages, LLM prompt strings, markdown docs, test data and assertions. Identifiers, enum keys, routes, placeholders and code structure are unchanged. Notes: - docs/漏洞流量证据.md renamed to docs/vulnerability-traffic-evidence.md - tests that exercised CJK specific handling (GBK zip names, Chinese full text search) now use ASCII equivalents; go build passes and the test suite matches the pre translation baseline (remaining failures need a PostgreSQL DSN and fail identically on the original) - frontend mention syntax now uses English kinds (finding, asset, ...) matching the backend, chat mentions tests pass
85 lines
2.8 KiB
Go
85 lines
2.8 KiB
Go
package server
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// GetSetting distinguishes "key absent" from "read failed" only through its error:
|
|
// both return an empty value. If a password handler mistakes the error for "no
|
|
// password set yet", a database blip opens the init door — authInit would let an
|
|
// unauthenticated request overwrite the admin password, and authStatus ships the frontend to /setup to do the same.
|
|
//
|
|
// Both tests close the handlers' connection pool to force a read failure and assert both fail closed.
|
|
func TestAuthStatusFailsClosedWhenDataSourceUnavailable(t *testing.T) {
|
|
m, err := NewManager(t.TempDir(), "")
|
|
if err != nil {
|
|
t.Skipf("postgres unavailable (%v)", err)
|
|
}
|
|
defer m.Close()
|
|
// Close the pool so subsequent GetSetting returns an error instead of sql.ErrNoRows.
|
|
if err := m.pg.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
s := &Server{m: m}
|
|
w := httptest.NewRecorder()
|
|
s.authStatus(w, httptest.NewRequest("GET", "/api/auth/status", nil))
|
|
|
|
if w.Code != 503 {
|
|
t.Fatalf("status=%d want 503 (treating a failed read as uninitialized sends the user to /setup to overwrite the password); body=%s", w.Code, w.Body.String())
|
|
}
|
|
var payload struct {
|
|
Initialized *bool `json:"initialized"`
|
|
}
|
|
if err := json.Unmarshal(w.Body.Bytes(), &payload); err == nil && payload.Initialized != nil {
|
|
t.Fatalf("a failed read must not answer initialized, got %v", *payload.Initialized)
|
|
}
|
|
}
|
|
|
|
func TestAuthInitFailsClosedWhenDataSourceUnavailable(t *testing.T) {
|
|
m, err := NewManager(t.TempDir(), "")
|
|
if err != nil {
|
|
t.Skipf("postgres unavailable (%v)", err)
|
|
}
|
|
defer m.Close()
|
|
if err := m.pg.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
s := &Server{m: m}
|
|
w := httptest.NewRecorder()
|
|
body := strings.NewReader(`{"password":"correct horse battery"}`)
|
|
s.authInit(w, httptest.NewRequest("POST", "/api/auth/init", body))
|
|
|
|
if w.Code != 503 {
|
|
t.Fatalf("status=%d want 503 (allowing through on a failed read lets an unauthenticated request overwrite the existing password); body=%s", w.Code, w.Body.String())
|
|
}
|
|
if strings.Contains(w.Body.String(), "token") {
|
|
t.Fatalf("a failed read must not issue a token: %s", w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestValidatePassword(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name, pw string
|
|
wantErr bool
|
|
}{
|
|
{"empty", "", true},
|
|
{"seven chars", "1234567", true},
|
|
{"eight chars", "12345678", false},
|
|
{"eight multibyte chars counted as runes not bytes", strings.Repeat("€", 8), false},
|
|
{"three chars reach 9 bytes but only 3 runes", strings.Repeat("€", 3), true},
|
|
{"72 bytes", strings.Repeat("a", 72), false},
|
|
{"73 bytes exceeds the bcrypt cap", strings.Repeat("a", 73), true},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := validatePassword(tc.pw); (got != "") != tc.wantErr {
|
|
t.Fatalf("validatePassword(%q)=%q, wantErr=%v", tc.pw, got, tc.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|