docs(auth): address authentication guide review

This commit is contained in:
2026-08-18 03:33:00 +02:00
parent f4f38717e1
commit 91925d64bf
12 changed files with 406 additions and 142 deletions
+107 -6
View File
@@ -1,7 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)
script_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)
root=$script_root
if [[ $# -gt 0 ]]; then
[[ $# -eq 2 && $1 == "--root" && -d $2 ]] || {
echo "usage: auth-docs-smoke.sh [--root DIRECTORY]" >&2
exit 2
}
root=$(cd "$2" && pwd -P)
fi
docs=(
"$root/docs/architecture/authentication.md"
"$root/docs/install/authentication-local.md"
@@ -18,6 +26,7 @@ docs=(
"$root/docs/index.md"
"$root/README.md"
"$root/PROJECT_STATE.md"
"$root/mkdocs.yml"
)
for path in "${docs[@]}"; do
@@ -36,21 +45,113 @@ required=(
"THT_AUTHENTIK_API_TOKEN"
"Remember me"
"oidc_mapped_group_missing"
"oidc_callback_failed"
"session.read_all"
"workspace.secrets.manage"
"auth.diagnostics.read"
)
for term in "${required[@]}"; do
rg -Fq "$term" "$corpus" || { echo "auth docs smoke: missing required term: $term" >&2; exit 1; }
done
if rg -n -i 'thothii-admin|thothctl[[:space:]]+auth' "$corpus"; then
canonical_compose='docker compose --env-file deploy/env/local.env -f compose.yaml -f deploy/compose.local.yaml up --build -d'
rg -Fxq "$canonical_compose" "$root/docs/install/local.md" || {
echo "auth docs smoke: missing canonical local Compose command" >&2
exit 1
}
if rg -n -F 'docker compose --env-file deploy/env/local.env +' "$corpus"; then
echo "auth docs smoke: noncanonical local Compose command" >&2
exit 1
fi
nav_count=$(awk 'index($0, "architecture/authentication.md") { count++ } END { print count + 0 }' "$root/mkdocs.yml")
[[ $nav_count == 1 ]] || {
echo "auth docs smoke: authentication navigation must appear exactly once" >&2
exit 1
}
python3 - "$root" <<'PY'
import pathlib
import re
import sys
root = pathlib.Path(sys.argv[1])
architecture = (root / "docs/architecture/authentication.md").read_text()
user_row = "| `user` | `session.use` |"
admin_row = (
"| `admin` | `session.use`, `session.read_all`, `session.manage_all`, `settings.manage`, "
"`workspace.manage`, `workspace.secrets.manage`, `pi.manage`, `auth.diagnostics.read` |"
)
if user_row not in architecture or admin_row not in architecture:
raise SystemExit("auth docs smoke: role-to-permission map is not exact")
diagnostic_heading = "## Diagnostics and ordering"
diagnostic_start = architecture.find(diagnostic_heading)
diagnostic_end = architecture.find("\n## ", diagnostic_start + len(diagnostic_heading))
diagnostic_section = architecture[diagnostic_start:diagnostic_end if diagnostic_end >= 0 else None]
match = re.search(r"```text\n([\s\S]*?)```", diagnostic_section)
expected_codes = [
"auth_ready",
"auth_config_incomplete",
"auth_config_invalid",
"auth_session_store_invalid",
"local_user_registry_invalid",
"local_admin_missing",
"oidc_secret_missing",
"oidc_discovery_unreachable",
"oidc_issuer_mismatch",
"oidc_jwks_unreachable",
"oidc_group_catalog_unreachable",
"oidc_group_catalog_unauthorized",
"oidc_mapped_group_missing",
"oidc_mapped_group_ambiguous",
"oidc_groups_claim_invalid",
"oidc_device_flow_unavailable",
]
actual_codes = [] if match is None else [line for line in match.group(1).splitlines() if line]
if actual_codes != expected_codes:
raise SystemExit("auth docs smoke: diagnostic code union is not exact")
for relative, language, forbidden, required in [
("docs/install/reverse-proxy-caddy.md", "caddyfile", "forward_auth", "forward_auth"),
("docs/install/reverse-proxy-nginx.md", "nginx", "auth_request", "auth_request"),
]:
source = (root / relative).read_text()
direct_start = source.find("## Direct ThothII-managed OIDC")
deprecated_start = source.find("## Deprecated upstream migration mode")
if direct_start < 0 or deprecated_start <= direct_start:
raise SystemExit(f"auth docs smoke: {relative} does not split direct and deprecated modes")
direct = source[direct_start:deprecated_start]
deprecated_end = source.find("\n## ", deprecated_start + 4)
deprecated = source[deprecated_start:deprecated_end if deprecated_end >= 0 else None]
blocks = re.findall(rf"```{language}\n([\s\S]*?)```", direct)
direct_code = "\n".join(blocks)
if "/api/auth/oidc/login" not in direct or "/api/auth/oidc/callback" not in direct:
raise SystemExit(f"auth docs smoke: {relative} omits unchanged public OIDC paths")
if re.search(rf"(?m)^\s*{forbidden}\b", direct_code):
raise SystemExit(f"auth docs smoke: {relative} applies external auth in direct OIDC mode")
deprecated_code = "\n".join(
re.findall(rf"```{language}\n([\s\S]*?)```", deprecated)
)
if not re.search(rf"(?m)^\s*{required}\b", deprecated_code):
raise SystemExit(f"auth docs smoke: {relative} omits scoped deprecated upstream auth")
PY
if rg -n -i --pcre2 '\bthothii-admin\b|\bthothctl\b[^\r\n]{0,256}\bauth\b' "$corpus"; then
echo "auth docs smoke: forbidden authentication CLI wording" >&2
exit 1
fi
if rg -n -i 'password[[:space:]]*[:=][[:space:]]*(?!<|YOUR|REPLACE|CHANGE|FILE|PROMPT)' --pcre2 "$corpus"; then
echo "auth docs smoke: plaintext password example" >&2
if rg -n -i --pcre2 -- '--password(?!-file)\b(?:[[:space:]]+|=)\S+' "$corpus"; then
echo "auth docs smoke: plaintext password option" >&2
exit 1
fi
if rg -n -i 'unmapped[^.]{0,100}(generate|produce|emit|cause)[^.]{0,100}(warning|warn)|unmapped[^.]{0,100}warning(s)?[[:space:]]+(are|is)[[:space:]]+emitted' "$corpus"; then
echo "auth docs smoke: misleading warning claim for unmapped groups" >&2
if rg -n -i --pcre2 '(?:^|[,{[:space:]])password[[:space:]]*:[[:space:]]*\S+|"password"[[:space:]]*:[[:space:]]*(?:"[^"]+"|[^,}[:space:]]+)' "$corpus"; then
echo "auth docs smoke: plaintext password field" >&2
exit 1
fi
if rg -n -i --pcre2 '(?:unmapped|additional|extra)[^.\r\n]{0,160}groups?[^.\r\n]{0,160}(?:generate|produce|emit|cause|trigger|raise|create|result)[^.\r\n]{0,160}(?:warnings?|alerts?|advisory|advisories|notices?|notifications?|noise)|(?:warnings?|alerts?|advisory|advisories|notices?|notifications?|noise)[^.\r\n]{0,160}(?:generate|produce|emit|cause|trigger|raise|create|result)[^.\r\n]{0,160}(?:unmapped|additional|extra)[^.\r\n]{0,160}groups?' "$corpus"; then
echo "auth docs smoke: misleading noise claim for unmapped groups" >&2
exit 1
fi
+94
View File
@@ -0,0 +1,94 @@
#!/usr/bin/env bash
set -euo pipefail
root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)
tmp=$(mktemp -d "${TMPDIR:-/tmp}/thoth-auth-docs.XXXXXX")
trap 'rm -rf "$tmp"' EXIT
files=(
docs/architecture/authentication.md
docs/install/authentication-local.md
docs/install/authentication-oidc.md
docs/install/authentik.md
docs/testing/authentication-manual-acceptance.md
docs/architecture/overview.md
docs/install/local.md
docs/install/server.md
docs/install/psd-workspace-setup.md
docs/install/reverse-proxy-caddy.md
docs/install/reverse-proxy-nginx.md
docs/guida-utente.md
docs/index.md
README.md
PROJECT_STATE.md
mkdocs.yml
)
make_fixture() {
local name=${1:?fixture name required}
local fixture="$tmp/$name"
mkdir -p "$fixture"
for relative in "${files[@]}"; do
mkdir -p "$fixture/$(dirname "$relative")"
cp "$root/$relative" "$fixture/$relative"
done
printf '%s\n' "$fixture"
}
expect_rejected() {
local name=${1:?fixture name required}
local fixture_text=${2:?fixture text required}
local expected=${3:?expected error required}
local fixture output
fixture=$(make_fixture "$name")
printf '%s\n' "$fixture_text" >>"$fixture/README.md"
output="$tmp/$name.output"
if "$root/scripts/auth-docs-smoke.sh" --root "$fixture" >"$output" 2>&1; then
echo "auth docs fixture unexpectedly passed: $name" >&2
exit 1
fi
rg -Fq "$expected" "$output" || {
echo "auth docs fixture failed for the wrong reason: $name" >&2
sed -n '1,20p' "$output" >&2
exit 1
}
echo "auth docs negative fixture rejected: $name"
}
positive=$(make_fixture positive)
printf '%s\n' \
'Use --password-file <file>; never pass a password value.' \
'Additional unmapped groups are silently ignored without warnings, alerts, or advisories.' \
>>"$positive/README.md"
"$root/scripts/auth-docs-smoke.sh" --root "$positive" >/dev/null
echo "auth docs positive fixture passed"
expect_rejected thothctl-intervening \
'Run thothctl --installation <descriptor> --json auth check.' \
'forbidden authentication CLI wording'
expect_rejected alternate-admin \
'Run thothii-admin users list.' \
'forbidden authentication CLI wording'
expect_rejected password-option \
'Run tht auth user add demo --password example-value.' \
'plaintext password option'
expect_rejected yaml-password \
'password: example-value' \
'plaintext password field'
expect_rejected json-password \
'{"password": "example-value"}' \
'plaintext password field'
expect_rejected unmapped-warning \
'Unmapped OIDC groups generate warnings.' \
'misleading noise claim for unmapped groups'
expect_rejected unmapped-alert \
'Unmapped provider groups trigger operator alerts.' \
'misleading noise claim for unmapped groups'
expect_rejected unmapped-advisory \
'An advisory is emitted for every unmapped group.' \
'misleading noise claim for unmapped groups'
expect_rejected compose-plus \
'docker compose --env-file deploy/env/local.env + -f compose.yaml -f deploy/compose.local.yaml up --build -d' \
'noncanonical local Compose command'
echo "auth docs smoke fixture suite passed"