| package auth |
|
|
| import ( |
| "crypto/hmac" |
| "crypto/sha256" |
| "encoding/json" |
| "net/http" |
| "net/http/httptest" |
| "os" |
| "strings" |
| "testing" |
|
|
| "ds2api/internal/config" |
| ) |
|
|
| |
| |
| |
| type stubStore struct { |
| passwordHash string |
| expireHours int |
| validAfter int64 |
| } |
|
|
| func (s stubStore) AdminPasswordHash() string { return s.passwordHash } |
| func (s stubStore) AdminJWTExpireHours() int { return s.expireHours } |
| func (s stubStore) AdminJWTValidAfterUnix() int64 { return s.validAfter } |
|
|
| func TestEffectiveAdminKeyNeverReturnsHardcodedDefault(t *testing.T) { |
| |
| |
| |
| |
| t.Setenv("DS2API_ADMIN_KEY", "") |
| t.Setenv("DS2API_CONFIG_JSON", "") |
|
|
| stub := stubStore{} |
| if got := effectiveAdminKey(stub); got != "" { |
| t.Fatalf("effectiveAdminKey with no env and no password hash must be empty, got %q", got) |
| } |
| if got := AdminKey(); got != "" { |
| t.Fatalf("AdminKey() with no env must be empty, got %q", got) |
| } |
| } |
|
|
| func TestVerifyAdminCredentialRejectsEmptyAndDefaultAdmin(t *testing.T) { |
| |
| |
| t.Setenv("DS2API_ADMIN_KEY", "") |
| t.Setenv("DS2API_CONFIG_JSON", "") |
|
|
| stub := stubStore{} |
| for _, candidate := range []string{"", "admin", "password", "123456", "default"} { |
| if VerifyAdminCredential(candidate, stub) { |
| t.Fatalf("VerifyAdminCredential must reject %q when no credentials configured", candidate) |
| } |
| } |
| } |
|
|
| func TestVerifyAdminCredentialRejectsKnownDefaultAdminWithEnvSet(t *testing.T) { |
| |
| |
| t.Setenv("DS2API_ADMIN_KEY", "a-strong-and-random-key-12345") |
| t.Setenv("DS2API_CONFIG_JSON", "") |
|
|
| stub := stubStore{} |
| if VerifyAdminCredential("admin", stub) { |
| t.Fatal("VerifyAdminCredential must reject literal \"admin\" when env key is set to something else") |
| } |
| if !VerifyAdminCredential("a-strong-and-random-key-12345", stub) { |
| t.Fatal("VerifyAdminCredential must accept the configured env key") |
| } |
| } |
|
|
| func TestJWTSecretNotHardcodedAdminWhenUnconfigured(t *testing.T) { |
| |
| |
| |
| |
| t.Setenv("DS2API_ADMIN_KEY", "") |
| t.Setenv("DS2API_JWT_SECRET", "") |
| t.Setenv("DS2API_CONFIG_JSON", "") |
|
|
| secret := jwtSecret(nil) |
| if secret == "admin" { |
| t.Fatal("jwtSecret must not fall back to hardcoded \"admin\"") |
| } |
| if secret == "" { |
| t.Fatal("jwtSecret must be non-empty so token signing is non-trivial") |
| } |
| |
| if secret != jwtSecret(nil) { |
| t.Fatal("jwtSecret fallback must be process-stable so minted tokens verify") |
| } |
| } |
|
|
| func TestJWTForgedWithAdminSecretFailsVerification(t *testing.T) { |
| |
| |
| |
| t.Setenv("DS2API_ADMIN_KEY", "") |
| t.Setenv("DS2API_JWT_SECRET", "") |
|
|
| |
| header := map[string]any{"alg": "HS256", "typ": "JWT"} |
| payload := map[string]any{ |
| "iat": 0, |
| "exp": 9999999999, |
| "role": "admin", |
| } |
| h, _ := json.Marshal(header) |
| p, _ := json.Marshal(payload) |
| msg := rawB64Encode(h) + "." + rawB64Encode(p) |
|
|
| mac := hmac.New(sha256.New, []byte("admin")) |
| _, _ = mac.Write([]byte(msg)) |
| forgedSig := mac.Sum(nil) |
| forged := msg + "." + rawB64Encode(forgedSig) |
|
|
| if _, err := VerifyJWT(forged); err == nil { |
| t.Fatal("forged token signed with hardcoded \"admin\" secret must NOT verify") |
| } |
| } |
|
|
| func TestAdminLoginDisabledWithoutCredentials(t *testing.T) { |
| |
| |
| |
| t.Setenv("DS2API_ADMIN_KEY", "") |
| t.Setenv("DS2API_CONFIG_JSON", "") |
|
|
| stub := stubStore{} |
| for _, token := range []string{"admin", "", "Bearer admin", "anything"} { |
| req := httptest.NewRequest(http.MethodGet, "/admin/config", nil) |
| req.Header.Set("Authorization", "Bearer "+token) |
| if err := VerifyAdminRequestWithStore(req, stub); err == nil { |
| t.Fatalf("VerifyAdminRequestWithStore must reject token %q when no credentials configured", token) |
| } |
| } |
| } |
|
|
| func TestAdminLoginWithConfiguredPasswordHash(t *testing.T) { |
| |
| |
| t.Setenv("DS2API_ADMIN_KEY", "") |
| t.Setenv("DS2API_JWT_SECRET", "") |
|
|
| stub := stubStore{passwordHash: HashAdminPassword("super-secret-pw")} |
| if !VerifyAdminCredential("super-secret-pw", stub) { |
| t.Fatal("VerifyAdminCredential must accept the configured password") |
| } |
| if VerifyAdminCredential("wrong-password", stub) { |
| t.Fatal("VerifyAdminCredential must reject wrong password") |
| } |
| |
| secret := jwtSecret(stub) |
| if secret != strings.TrimSpace(stub.passwordHash) { |
| t.Fatalf("jwtSecret must be the password hash when no env secret set, got %q", secret) |
| } |
| } |
|
|
| func TestAdminLoginWithEnvKey(t *testing.T) { |
| |
| t.Setenv("DS2API_ADMIN_KEY", "env-key-12345") |
| t.Setenv("DS2API_JWT_SECRET", "") |
|
|
| stub := stubStore{} |
| if !VerifyAdminCredential("env-key-12345", stub) { |
| t.Fatal("VerifyAdminCredential must accept the configured env key") |
| } |
| if VerifyAdminCredential("env-key-99999", stub) { |
| t.Fatal("VerifyAdminCredential must reject wrong env key") |
| } |
| } |
|
|
| func TestAdminLoginWithJWTSecretEnv(t *testing.T) { |
| |
| |
| t.Setenv("DS2API_ADMIN_KEY", "env-key-12345") |
| t.Setenv("DS2API_JWT_SECRET", "explicit-jwt-secret") |
|
|
| stub := stubStore{} |
| if jwtSecret(stub) != "explicit-jwt-secret" { |
| t.Fatalf("jwtSecret must use explicit env value, got %q", jwtSecret(stub)) |
| } |
|
|
| token, err := CreateJWTWithStore(1, stub) |
| if err != nil { |
| t.Fatalf("CreateJWTWithStore failed: %v", err) |
| } |
| if _, err := VerifyJWTWithStore(token, stub); err != nil { |
| t.Fatalf("VerifyJWTWithStore failed: %v", err) |
| } |
| } |
|
|
| |
| |
| func TestUsingRealStoreNoCredentials(t *testing.T) { |
| if os.Getenv("DS2API_RUN_REAL_STORE_TEST") == "" { |
| t.Skip("skipping real-store test; set DS2API_RUN_REAL_STORE_TEST=1 to run") |
| } |
| t.Setenv("DS2API_ADMIN_KEY", "") |
| t.Setenv("DS2API_JWT_SECRET", "") |
| t.Setenv("DS2API_CONFIG_JSON", "{}") |
| store := config.LoadStore() |
| if !UsingDefaultAdminKey(store) { |
| t.Fatal("UsingDefaultAdminKey must be true when nothing is configured") |
| } |
| if VerifyAdminCredential("admin", store) { |
| t.Fatal("VerifyAdminCredential must reject \"admin\" with no credentials configured") |
| } |
| } |
|
|