mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-20 05:07:17 +00:00
211 lines
7.1 KiB
Bash
Executable file
211 lines
7.1 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
HOT_DEV_AUTH_LIB="${ROOT_DIR}/scripts/lib/hot-dev-auth.sh"
|
|
|
|
if [[ ! -f "${HOT_DEV_AUTH_LIB}" ]]; then
|
|
echo "hot-dev-auth.sh not found at ${HOT_DEV_AUTH_LIB}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
failures=0
|
|
temp_dirs=()
|
|
|
|
cleanup() {
|
|
local dir
|
|
for dir in "${temp_dirs[@]:-}"; do
|
|
rm -rf "${dir}" 2>/dev/null || true
|
|
done
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
assert_contains() {
|
|
local desc="$1"
|
|
local haystack="$2"
|
|
local needle="$3"
|
|
|
|
if [[ "${haystack}" == *"${needle}"* ]]; then
|
|
echo "[PASS] ${desc}"
|
|
else
|
|
echo "[FAIL] ${desc}" >&2
|
|
echo "Expected to find: ${needle}" >&2
|
|
((failures++))
|
|
fi
|
|
}
|
|
|
|
make_temp_dir() {
|
|
local dir
|
|
dir="$(mktemp -d)"
|
|
temp_dirs+=("${dir}")
|
|
printf "%s\n" "${dir}"
|
|
}
|
|
|
|
test_default_auth_contract() {
|
|
local output
|
|
output="$(
|
|
HOT_DEV_AUTH_LIB="${HOT_DEV_AUTH_LIB}" \
|
|
bash -lc '
|
|
source "${HOT_DEV_AUTH_LIB}"
|
|
printf "user=%s\n" "$(hot_dev_resolve_auth_user)"
|
|
printf "pass=%s\n" "$(hot_dev_resolve_auth_pass)"
|
|
printf "banner=%s\n" "$(hot_dev_auth_banner_line "$(hot_dev_resolve_auth_user)" "$(hot_dev_resolve_auth_pass)")"
|
|
'
|
|
)"
|
|
|
|
assert_contains "default auth username stays admin" "${output}" "user=admin"
|
|
assert_contains "default auth password stays adminadminadmin" "${output}" "banner=admin / adminadminadmin"
|
|
assert_contains "default auth uses canonical bcrypt hash" "${output}" "pass=${HOT_DEV_DEFAULT_AUTH_HASH}"
|
|
}
|
|
|
|
test_custom_auth_banner_contract() {
|
|
local output
|
|
output="$(
|
|
HOT_DEV_AUTH_LIB="${HOT_DEV_AUTH_LIB}" \
|
|
bash -lc '
|
|
source "${HOT_DEV_AUTH_LIB}"
|
|
HOT_DEV_AUTH_USER="custom-admin"
|
|
HOT_DEV_AUTH_PASS="custom-password"
|
|
printf "banner=%s\n" "$(hot_dev_auth_banner_line "$(hot_dev_resolve_auth_user)" "$(hot_dev_resolve_auth_pass)")"
|
|
'
|
|
)"
|
|
|
|
assert_contains "custom auth banner hides the raw password" "${output}" "banner=custom via HOT_DEV_AUTH_USER / HOT_DEV_AUTH_PASS"
|
|
}
|
|
|
|
test_sync_auth_env_file_preserves_non_auth_settings() {
|
|
local state_dir runtime_env output
|
|
state_dir="$(make_temp_dir)"
|
|
runtime_env="${state_dir}/.env"
|
|
|
|
cat > "${runtime_env}" <<'EOF'
|
|
# Auto-generated by Pulse Quick Security Setup
|
|
PULSE_AUTH_USER='stale-user'
|
|
PULSE_AUTH_PASS='stale-pass'
|
|
PULSE_AUDIT_LOG=true
|
|
PULSE_MOCK_MODE=false
|
|
EOF
|
|
|
|
output="$(
|
|
HOT_DEV_AUTH_LIB="${HOT_DEV_AUTH_LIB}" \
|
|
RUNTIME_ENV_PATH="${runtime_env}" \
|
|
bash -lc '
|
|
source "${HOT_DEV_AUTH_LIB}"
|
|
hot_dev_sync_auth_env_file "${RUNTIME_ENV_PATH}" "admin" "${HOT_DEV_DEFAULT_AUTH_HASH}"
|
|
cat "${RUNTIME_ENV_PATH}"
|
|
'
|
|
)"
|
|
|
|
assert_contains "sync writes managed auth header" "${output}" "# Managed by hot-dev.sh for deterministic dev auth"
|
|
assert_contains "sync rewrites auth user" "${output}" "PULSE_AUTH_USER='admin'"
|
|
assert_contains "sync rewrites auth password hash" "${output}" "PULSE_AUTH_PASS='${HOT_DEV_DEFAULT_AUTH_HASH}'"
|
|
assert_contains "sync preserves audit settings" "${output}" "PULSE_AUDIT_LOG=true"
|
|
assert_contains "sync preserves mock settings" "${output}" "PULSE_MOCK_MODE=false"
|
|
}
|
|
|
|
test_sync_auth_env_file_removes_stale_temp_files() {
|
|
local state_dir runtime_env output
|
|
state_dir="$(make_temp_dir)"
|
|
runtime_env="${state_dir}/.env"
|
|
|
|
touch "${runtime_env}.tmp.111" "${runtime_env}.audit.222"
|
|
|
|
output="$(
|
|
HOT_DEV_AUTH_LIB="${HOT_DEV_AUTH_LIB}" \
|
|
RUNTIME_ENV_PATH="${runtime_env}" \
|
|
bash -lc '
|
|
source "${HOT_DEV_AUTH_LIB}"
|
|
hot_dev_sync_auth_env_file "${RUNTIME_ENV_PATH}" "admin" "${HOT_DEV_DEFAULT_AUTH_HASH}"
|
|
if compgen -G "${RUNTIME_ENV_PATH}.tmp.*" >/dev/null || compgen -G "${RUNTIME_ENV_PATH}.audit.*" >/dev/null; then
|
|
printf "stale_temp_remaining=yes\n"
|
|
else
|
|
printf "stale_temp_remaining=no\n"
|
|
fi
|
|
'
|
|
)"
|
|
|
|
assert_contains "sync removes stale managed temp files" "${output}" "stale_temp_remaining=no"
|
|
}
|
|
|
|
test_sync_auth_env_file_handles_managed_only_env_under_errexit() {
|
|
local state_dir runtime_env output
|
|
state_dir="$(make_temp_dir)"
|
|
runtime_env="${state_dir}/.env"
|
|
|
|
cat > "${runtime_env}" <<'EOF'
|
|
# Managed by hot-dev.sh for deterministic dev auth
|
|
PULSE_AUTH_USER='admin'
|
|
PULSE_AUTH_PASS='stale-pass'
|
|
EOF
|
|
|
|
output="$(
|
|
HOT_DEV_AUTH_LIB="${HOT_DEV_AUTH_LIB}" \
|
|
RUNTIME_ENV_PATH="${runtime_env}" \
|
|
bash -lc '
|
|
set -euo pipefail
|
|
source "${HOT_DEV_AUTH_LIB}"
|
|
hot_dev_sync_auth_env_file "${RUNTIME_ENV_PATH}" "admin" "${HOT_DEV_DEFAULT_AUTH_HASH}"
|
|
printf "survived=yes\n"
|
|
cat "${RUNTIME_ENV_PATH}"
|
|
'
|
|
)"
|
|
|
|
assert_contains "managed-only env does not trip errexit" "${output}" "survived=yes"
|
|
assert_contains "managed-only sync keeps auth user" "${output}" "PULSE_AUTH_USER='admin'"
|
|
assert_contains "managed-only sync rewrites auth password hash" "${output}" "PULSE_AUTH_PASS='${HOT_DEV_DEFAULT_AUTH_HASH}'"
|
|
}
|
|
|
|
test_sync_audit_signing_env_file_restores_missing_key() {
|
|
local state_dir runtime_env output
|
|
state_dir="$(make_temp_dir)"
|
|
runtime_env="${state_dir}/.env"
|
|
|
|
cat > "${runtime_env}" <<'EOF'
|
|
# Managed by hot-dev.sh for deterministic dev auth
|
|
PULSE_AUTH_USER='admin'
|
|
PULSE_AUTH_PASS='hash'
|
|
EOF
|
|
|
|
output="$(
|
|
HOT_DEV_AUTH_LIB="${HOT_DEV_AUTH_LIB}" \
|
|
RUNTIME_ENV_PATH="${runtime_env}" \
|
|
DATA_DIR="${state_dir}" \
|
|
bash -lc '
|
|
source "${HOT_DEV_AUTH_LIB}"
|
|
hot_dev_sync_audit_signing_env_file "${RUNTIME_ENV_PATH}" "${DATA_DIR}"
|
|
key_line_count="$(grep -c "^PULSE_AUDIT_SIGNING_KEY=" "${RUNTIME_ENV_PATH}")"
|
|
key_value="$(grep "^PULSE_AUDIT_SIGNING_KEY=" "${RUNTIME_ENV_PATH}" | cut -d= -f2- | tr -d "\\047")"
|
|
key_file_value="$(cat "${DATA_DIR}/.audit-signing.key")"
|
|
mode="$(stat -c "%a" "${DATA_DIR}/.audit-signing.key" 2>/dev/null || stat -f "%Lp" "${DATA_DIR}/.audit-signing.key")"
|
|
hot_dev_sync_audit_signing_env_file "${RUNTIME_ENV_PATH}" "${DATA_DIR}"
|
|
key_line_count_after="$(grep -c "^PULSE_AUDIT_SIGNING_KEY=" "${RUNTIME_ENV_PATH}")"
|
|
printf "key_line_count=%s\n" "${key_line_count}"
|
|
printf "key_length=%s\n" "${#key_value}"
|
|
printf "key_matches_file=%s\n" "$([[ "${key_value}" == "${key_file_value}" ]] && printf yes || printf no)"
|
|
printf "key_line_count_after=%s\n" "${key_line_count_after}"
|
|
printf "mode=%s\n" "${mode}"
|
|
'
|
|
)"
|
|
|
|
assert_contains "audit signing sync appends one env key" "${output}" "key_line_count=1"
|
|
assert_contains "audit signing sync writes a 32-byte hex key" "${output}" "key_length=64"
|
|
assert_contains "audit signing sync persists the same key to file" "${output}" "key_matches_file=yes"
|
|
assert_contains "audit signing sync is idempotent" "${output}" "key_line_count_after=1"
|
|
assert_contains "audit signing key file is private" "${output}" "mode=600"
|
|
}
|
|
|
|
source "${HOT_DEV_AUTH_LIB}"
|
|
test_default_auth_contract
|
|
test_custom_auth_banner_contract
|
|
test_sync_auth_env_file_preserves_non_auth_settings
|
|
test_sync_auth_env_file_removes_stale_temp_files
|
|
test_sync_auth_env_file_handles_managed_only_env_under_errexit
|
|
test_sync_audit_signing_env_file_restores_missing_key
|
|
|
|
if (( failures > 0 )); then
|
|
echo "FAIL: ${failures} hot-dev auth assertions failed" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "PASS: hot-dev auth contract checks passed"
|