Replaces the super-admin-only /.admin/ surface with a public-by-default /.profile/ page that layers admin tools server-side based on the caller's effective access: - Universal (everyone, anonymous included): identity card, effective access summary, theme picker, localStorage utilities (export / import / clear, landing-presets viewer). - Subtree admins additionally see: editable .zddc files list (linking to the existing form-based editor) and a "Create new project folder" form. - Super-admins additionally see: server config, log viewer, whoami headers (the old /.admin/ JSON endpoints, repointed under /.profile/). Project creation is gated on CanEditZddc(newDir) — the same strict- ancestor rule that already governs .zddc writes — so no new authority concept is introduced. ValidateProjectName mirrors the existing reserved-prefix policy (no leading '.' or '_', no path separators). /.admin/* is hard-cut: no redirect shim. Old URLs fall through to the existing dot-prefix guard and 404. Custom CSS file rename: prefer <root>/.profile.css, fall back to legacy <root>/.admin.css. Per-resource 404 leakage gates preserved on whoami / config / logs / zddc / projects so non-admin callers cannot detect the existence of admin-only sub-resources. Tree-wide gofmt -w applied as a side-effect. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
115 lines
2.5 KiB
Go
115 lines
2.5 KiB
Go
package zddc
|
|
|
|
import "testing"
|
|
|
|
func TestValidatePattern(t *testing.T) {
|
|
cases := []struct {
|
|
pattern string
|
|
ok bool
|
|
}{
|
|
{"alice@example.com", true},
|
|
{"*@example.com", true},
|
|
{"alice@*", true},
|
|
{"*", true},
|
|
{"", false},
|
|
{" alice@example.com", false},
|
|
{"alice@example.com ", false},
|
|
{"alice @example.com", false},
|
|
{"alice@ex ample.com", false},
|
|
{"alice@@example.com", false},
|
|
{"@example.com", false},
|
|
{"alice@", false},
|
|
{"@", false},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.pattern, func(t *testing.T) {
|
|
err := ValidatePattern(tc.pattern)
|
|
if tc.ok && err != nil {
|
|
t.Errorf("ValidatePattern(%q) = %v, want nil", tc.pattern, err)
|
|
}
|
|
if !tc.ok && err == nil {
|
|
t.Errorf("ValidatePattern(%q) = nil, want error", tc.pattern)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateFile(t *testing.T) {
|
|
zf := ZddcFile{
|
|
Title: "ok",
|
|
ACL: ACLRules{Allow: []string{"good@example.com", "@bad"}, Deny: []string{"two@@ats"}},
|
|
Admins: []string{"@nobody"},
|
|
}
|
|
errs := ValidateFile(zf)
|
|
// expect 3 errors
|
|
if len(errs) != 3 {
|
|
t.Fatalf("got %d errors, want 3: %+v", len(errs), errs)
|
|
}
|
|
wantFields := map[string]bool{
|
|
"acl.allow[1]": false,
|
|
"acl.deny[0]": false,
|
|
"admins[0]": false,
|
|
}
|
|
for _, e := range errs {
|
|
if _, ok := wantFields[e.Field]; !ok {
|
|
t.Errorf("unexpected error field: %q", e.Field)
|
|
continue
|
|
}
|
|
wantFields[e.Field] = true
|
|
}
|
|
for f, seen := range wantFields {
|
|
if !seen {
|
|
t.Errorf("missing error for field %q", f)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateProjectName(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
ok bool
|
|
}{
|
|
{"alpha", true},
|
|
{"Alpha", true},
|
|
{"a", true},
|
|
{"a1", true},
|
|
{"a-1", true},
|
|
{"a_b", true},
|
|
{"123-project", true},
|
|
{"Site-3", true},
|
|
{"", false},
|
|
{".hidden", false},
|
|
{"_template", false},
|
|
{"-leading-dash", false},
|
|
{"foo bar", false},
|
|
{"foo/bar", false},
|
|
{"foo\\bar", false},
|
|
{"foo.bar", false},
|
|
{"..", false},
|
|
{".", false},
|
|
{string(make([]byte, 65)), false},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := ValidateProjectName(tc.name)
|
|
if tc.ok && err != nil {
|
|
t.Errorf("ValidateProjectName(%q) = %v, want nil", tc.name, err)
|
|
}
|
|
if !tc.ok && err == nil {
|
|
t.Errorf("ValidateProjectName(%q) = nil, want error", tc.name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateFileTitleLength(t *testing.T) {
|
|
long := make([]byte, 201)
|
|
for i := range long {
|
|
long[i] = 'a'
|
|
}
|
|
zf := ZddcFile{Title: string(long)}
|
|
errs := ValidateFile(zf)
|
|
if len(errs) != 1 || errs[0].Field != "title" {
|
|
t.Fatalf("expected one title-length error, got %+v", errs)
|
|
}
|
|
}
|