ZDDC/zddc/internal/handler/authcheck_test.go
ZDDC 6cc0d2ae27 feat(zddc-server): /.auth/admin forward_auth endpoint
A machine-only HTTP endpoint that returns 200 if the request's
X-Auth-Request-Email is in the root .zddc admins: list, 403 otherwise.
No body, no redirect — pure authorization decision intended to be
polled by an upstream proxy's forward_auth directive.

The motivating use case is gating /devshell/* (code-server) in the
dev-shell pod on root-admin status before the request ever reaches
code-server, which has no built-in ACL of its own. zddc-server's
own routes keep the existing .zddc cascade ACL and don't go through
this endpoint.

Reuses zddc.IsAdmin (one cached map lookup) so the check is cheap
enough to call on every request. Edits to /srv/.zddc propagate via
the existing fsnotify watcher's policy-cache invalidation.

Tests cover empty email, non-admin, admin, and the bootstrap state
where no root .zddc exists (deny everyone — the safe default).

Docs: zddc/README.md "Forward-auth target for upstream proxies"
section + AGENTS.md notes bullet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 21:08:39 -05:00

64 lines
2.1 KiB
Go

package handler
import (
"net/http"
"net/http/httptest"
"testing"
)
// TestServeAuthAdmin pins the contract of the forward_auth endpoint
// used by upstream proxies (Caddy in the dev-shell pod) to gate
// admin-only routes:
//
// 200 → caller is in the root .zddc admins: list
// 403 → anonymous, OR not in admins:, OR no admins configured
//
// Reuses the profileTestRoot fixture which materializes a temp .zddc
// with the supplied admins, and the requestWithEmail helper that
// injects the email into request context the same way ACLMiddleware
// would in production.
func TestServeAuthAdmin(t *testing.T) {
cfg, _ := profileTestRoot(t, []string{"alice@example.com"})
cases := []struct {
name string
email string
wantStatus int
}{
{"empty email is denied", "", http.StatusForbidden},
{"non-admin is denied", "bob@example.com", http.StatusForbidden},
{"admin is allowed", "alice@example.com", http.StatusOK},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
req := requestWithEmail(http.MethodGet, AuthPathPrefix+"/admin", tc.email)
rec := httptest.NewRecorder()
ServeAuthAdmin(cfg, rec, req)
if rec.Code != tc.wantStatus {
t.Errorf("status = %d, want %d (body: %q)",
rec.Code, tc.wantStatus, rec.Body.String())
}
})
}
}
// TestServeAuthAdmin_NoZddcRootDeniesEverything covers the bootstrap-
// state behaviour: when no /srv/.zddc exists, IsAdmin returns false for
// everyone, which means /.auth/admin returns 403 universally. This is
// the desired safe-default before an operator drops a root .zddc onto
// the share.
func TestServeAuthAdmin_NoZddcRootDeniesEverything(t *testing.T) {
// profileTestRoot with nil admins skips writing the file entirely.
cfg, _ := profileTestRoot(t, nil)
for _, email := range []string{"", "alice@example.com", "anyone@example.com"} {
req := requestWithEmail(http.MethodGet, AuthPathPrefix+"/admin", email)
rec := httptest.NewRecorder()
ServeAuthAdmin(cfg, rec, req)
if rec.Code != http.StatusForbidden {
t.Errorf("email %q without .zddc: status %d, want 403",
email, rec.Code)
}
}
}