#!/usr/bin/env bash

# Managing a LOCAL user account (see online-accounts for the other kind).
#
# Managing an account must not leak the credential it sets, and must not let
# someone lock themselves out of their own machine.
#
# Nothing here creates, deletes, or modifies a real account. It reads the
# account state, which is safe, and exercises the refusals, which are the part
# that has to hold.

set -uo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
helper="$repo_dir/config/dot/quickshell/scripts/panama-users"
service="$repo_dir/config/dot/quickshell/services/UserAccounts.qml"
page="$repo_dir/config/dot/quickshell/modules/settings/UsersPage.qml"

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

for path in "$helper" "$service" "$page"; do
    [[ -r "$path" ]] || fail "missing $path"
done
[[ -x "$helper" ]] || fail 'panama-users is not executable'

# ── A new password never reaches a command line ─────────────────────────────
# argv is world-readable through /proc, so a password passed as an argument is
# published to every process on the machine. It is read from stdin, and the
# hashing step reads ITS stdin too, so the cleartext exists only inside these
# two processes.
password_body="$(sed -n '/^def set_password/,/^def /p' "$helper")"
[[ -n "$password_body" ]] || fail 'set_password is missing'
grep -q 'sys.stdin.buffer.read()' <<<"$password_body" \
    || fail 'the new password is not read from stdin'
grep -q 'input=secret' <<<"$password_body" \
    || fail 'the password is not handed to the hashing tool on stdin'
grep -qE '"openssl", "passwd"[^]]*secret' <<<"$password_body" \
    && fail 'the password appears in the hashing command line'
grep -qE '^\s*print\((secret|hashed)' <<<"$password_body" \
    && fail 'the password or its hash is printed'

# The service must not hold one either, beyond the moment it hands it over.
grep -q 'stdinEnabled' "$service" \
    || fail 'the service does not write the password over stdin'
grep -qE 'command:.*set-password.*password' "$service" \
    && fail 'the service puts the password in the command line'
grep -q 'root.pendingPassword = ""' "$service" \
    || fail 'the service never clears the password it was holding'

# ── Refusals that keep a machine administrable ──────────────────────────────
delete_body="$(sed -n '/^def delete_user/,/^def /p' "$helper")"
grep -q 'You cannot delete the account you are signed in to' <<<"$delete_body" \
    || fail 'the helper would delete the account running it'
grep -q 'only administrator' <<<"$delete_body" \
    || fail 'the helper would remove the last administrator, leaving nobody able to administer the machine'

# The page must not offer to change the type of the only administrator either.
grep -q 'administratorCount <= 1' "$page" \
    || fail 'the page offers to demote the only administrator'

# ── Deleting is confirmed, and says what it destroys ────────────────────────
grep -q 'confirmingRemoval' "$page" \
    || fail 'the page deletes an account without a confirmation step'
grep -q 'This cannot be undone' "$page" \
    || fail 'the page does not say that deleting an account destroys their files'

# ── The snapshot is real, and reports no secrets ────────────────────────────
command -v jq >/dev/null 2>&1 || { printf 'user accounts contract: SKIP (no jq)\n'; exit 0; }
snapshot="$("$helper" snapshot 2>/dev/null)" || fail 'snapshot failed'
jq -e '.users | type == "array" and length > 0' <<<"$snapshot" >/dev/null \
    || fail 'no accounts were reported'
jq -e '[.users[] | (.userName | length > 0)] | all' <<<"$snapshot" >/dev/null \
    || fail 'an account has no user name'
jq -e '.currentUser | length > 0' <<<"$snapshot" >/dev/null \
    || fail 'the snapshot does not say which account is signed in'

offenders="$(jq -r '[paths | map(tostring) | join(".")] | map(select(test("(password|secret|hash)$";"i"))) | join(", ")' <<<"$snapshot")"
[[ -z "$offenders" ]] || fail "the snapshot carries credential-shaped fields: $offenders"

# System accounts are not people and must not be offered for management.
jq -e '[.users[] | .uid >= 1000] | all' <<<"$snapshot" >/dev/null \
    || fail 'a system account is listed as a manageable user'

# ── Input validation ────────────────────────────────────────────────────────
for bad in "root; rm -rf /" "../escape" "UPPER" ""; do
    result="$("$helper" set-real-name "$bad" "Test" 2>/dev/null | jq -r '.error // ""')"
    [[ -n "$result" ]] || fail "the helper accepted \"$bad\" as a user name"
done

printf 'user accounts contract: PASS (%d account(s), credentials never on a command line)\n' \
    "$(jq '.users | length' <<<"$snapshot")"
