- docs/: shiroa book with three example categories (thermo, security, homelab); pages declare access via '#let access-groups = (...)' - cmd/parser: scans docs/ and generates acl.json (public by default, skips book.typ/template.typ/partials) - internal/acl: loads acl.json, normalizes URLs to page keys so a page's renderer data (*.multi.sir.in) inherits its protection; fail closed on malformed input - internal/oidc: authorization-code flow client, verified ID tokens, groups claim from Authentik - internal/middleware: in-memory sessions, login/logout/callback handlers, CSRF states, access gate + security headers - cmd/server + embed.go: single binary embedding dist/ and acl.json; /searchindex.json blocked; graceful shutdown - Justfile (build/run/parse/check/clean/preview-docs), Gitea Actions workflow (shiroa v0.4.0-rc2), rootless Dockerfile, README - legacy docs moved to legacy_docs/; .idea untracked
51 lines
1.4 KiB
Go
51 lines
1.4 KiB
Go
package middleware
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/Klagarge/yggdrasil/internal/oidc"
|
|
)
|
|
|
|
func TestSanitizeTarget(t *testing.T) {
|
|
cases := map[string]string{
|
|
"": "/",
|
|
"/thermo/fluids.html": "/thermo/fluids.html",
|
|
"/a b/c.html?x=1": "/a b/c.html?x=1",
|
|
"//evil.example.com": "/",
|
|
"/\\evil.example.com": "/",
|
|
"https://evil.example.com": "/",
|
|
"relative.html": "/",
|
|
}
|
|
for in, want := range cases {
|
|
if got := sanitizeTarget(in); got != want {
|
|
t.Errorf("sanitizeTarget(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStateRoundTrip(t *testing.T) {
|
|
ac := NewAccessControl(nil, NewSessionStore(0), nil, false)
|
|
|
|
ac.saveState("s1", "/thermo/fluids.html")
|
|
target, ok := ac.takeState("s1")
|
|
if !ok || target != "/thermo/fluids.html" {
|
|
t.Fatalf("state round trip failed: %q %v", target, ok)
|
|
}
|
|
// States are single use.
|
|
if _, ok := ac.takeState("s1"); ok {
|
|
t.Error("state must be single use")
|
|
}
|
|
// Unknown states are rejected (CSRF guard).
|
|
if _, ok := ac.takeState("nope"); ok {
|
|
t.Error("unknown state must be rejected")
|
|
}
|
|
}
|
|
|
|
func TestSessionExpiry(t *testing.T) {
|
|
store := NewSessionStore(0) // zero TTL: expires immediately
|
|
id := store.Create(oidc.Profile{Subject: "sub", Email: "user@example.com", Groups: []string{"family"}})
|
|
if _, ok := store.Get(id); ok {
|
|
t.Error("expired session must not validate")
|
|
}
|
|
}
|