#!/usr/bin/env bash

# The authentication prompt asks for the password to everything, so the rules
# here are not style, they are the reason it was safe to write at all.
#
#   1. The agent process must never handle a password. It talks to polkitd and
#      hands the shell a request; the shell talks to the setuid helper.
#   2. The password reaches the helper on STDIN and nowhere else. argv is
#      world-readable through /proc, so an argument is a broadcast.
#   3. The prompt takes EXCLUSIVE keyboard focus. A password field that lets
#      keystrokes through to the window behind it is a keylogger with extra
#      steps.
#   4. The request file carries a one-time capability and is created 0600.
#   5. The stock agent stays installed. Only one agent may register per session,
#      so a broken replacement must have something to fall back to.
#
# Static, plus a live check of the runtime directory. It never authenticates.

set -uo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
agent="$repo_dir/config/dot/quickshell/scripts/panama-polkit-agent"
service="$repo_dir/config/dot/quickshell/services/Polkit.qml"
prompt="$repo_dir/config/dot/quickshell/modules/polkit/PolkitPrompt.qml"
autostart="$repo_dir/config/dot/hypr/autostart.lua"

fail() {
    printf 'polkit agent contract: %s\n' "$1" >&2
    exit 1
}

for path in "$agent" "$service" "$prompt" "$autostart"; do
    [[ -r "$path" ]] || fail "missing $path"
done
[[ -x "$agent" ]] || fail 'the agent is not executable'

# ── 1. The agent never touches a password ───────────────────────────────────
# Comments and docstrings are stripped first: this file EXPLAINS at length why
# it must not touch a password, and an earlier version of this check failed on
# the explanation rather than on any behaviour.
agent_code="$(python3 - "$agent" <<'STRIP'
import ast, sys
source = open(sys.argv[1], encoding="utf-8").read()
tree = ast.parse(source)
# Drop every docstring, then print what is left as code.
for node in ast.walk(tree):
    if isinstance(node, (ast.Module, ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
        if (node.body and isinstance(node.body[0], ast.Expr)
                and isinstance(node.body[0].value, ast.Constant)
                and isinstance(node.body[0].value.value, str)):
            node.body.pop(0)
print(ast.unparse(tree))
STRIP
)"
[[ -n "$agent_code" ]] || fail 'could not read the agent source'
grep -qE 'polkit-agent-helper|PAM_PROMPT|password' <<<"$agent_code" \
    && fail 'the agent process handles passwords; that belongs on the shell side only'

# ── 2. The password goes to the helper on stdin ─────────────────────────────
grep -q 'stdinEnabled: true' "$service" \
    || fail 'the helper is spawned without a writable stdin, so the password has nowhere to go'
grep -q 'helper.write(root.pendingSecret' "$service" \
    || fail 'the password is not written to the helper stdin'
# It must never appear in the argument list.
helper_command="$(grep -n 'helper.command' "$service" || true)"
grep -qE 'pendingSecret|password' <<<"$helper_command" \
    && fail 'the password appears in the helper command line'
grep -q 'root.pendingSecret = "";' "$service" \
    || fail 'the password is never cleared after being handed over'

# ── 3. The prompt owns the keyboard ─────────────────────────────────────────
grep -q 'WlrKeyboardFocus.Exclusive' "$prompt" \
    || fail 'the prompt does not take exclusive keyboard focus'
grep -q 'field.text = ""' "$prompt" \
    || fail 'the password field is not cleared when the prompt closes'

# ── 4. The request file is private ──────────────────────────────────────────
grep -q '0o600' "$agent" || fail 'the request file is not created 0600'
grep -q '0o700' "$agent" || fail 'the runtime directory is not private'
grep -qE 'O_CREAT \| os\.O_EXCL' "$agent" \
    || fail 'the request file is not created exclusively, so it could be pre-created by someone else'

# ── 5. A fallback exists ────────────────────────────────────────────────────
grep -q 'hyprpolkitagent' "$autostart" \
    || fail 'nothing records how to get the stock agent back if this one fails'
command -v rpm >/dev/null 2>&1 && {
    rpm -q hyprpolkitagent >/dev/null 2>&1 \
        || fail 'the stock agent is no longer installed, so there is nothing to fall back to'
}

# Only one agent may be started by the session. Lua comments are excluded: the
# file documents how to restore the stock agent, and that sentence is not a
# command the session runs.
started="$(grep -vE '^\s*--' "$autostart" | grep -E 'systemctl --user start .*polkit' | head -1)"
[[ -n "$started" ]] || fail 'the session starts no polkit agent at all'
grep -q 'panama-polkit-agent' <<<"$started" \
    || fail 'the session does not start the Panama agent'
grep -q 'hyprpolkitagent.service' <<<"$started" \
    && fail 'the session starts both agents; the second to register will fail'

# ── Live: the runtime directory is private, if it exists ────────────────────
runtime="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/panama-polkit"
if [[ -d "$runtime" ]]; then
    mode="$(stat -c '%a' "$runtime")"
    [[ "$mode" == "700" ]] || fail "the runtime directory is mode $mode, not 700"
    leaked="$(find "$runtime" -type f ! -perm 600 2>/dev/null | head -1)"
    [[ -z "$leaked" ]] || fail "a request file is readable beyond its owner: $leaked"
fi

printf 'polkit agent contract: PASS (password never leaves the shell, prompt owns the keyboard, fallback intact)\n'
