Make credential setup keyboard-safe and verifiable
This commit is contained in:
@@ -5,7 +5,21 @@ import QtQuick
|
||||
import qs.services
|
||||
|
||||
ShellRoot {
|
||||
Component.onCompleted: HomeAssistant.fixtureMode = true
|
||||
id: root
|
||||
|
||||
property int refreshCalls: 0
|
||||
|
||||
FileView {
|
||||
id: tokenFile
|
||||
path: Quickshell.env("PANAMA_TEST_TOKEN_FILE")
|
||||
blockLoading: true
|
||||
printErrors: false
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
HomeAssistant.fixtureMode = true;
|
||||
HomeAssistantConfig.refreshHomeAssistant = function() { root.refreshCalls++; };
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "home-assistant-config-test"
|
||||
@@ -14,6 +28,10 @@ ShellRoot {
|
||||
return HomeAssistantConfig.save(url, entities, "");
|
||||
}
|
||||
|
||||
function saveWithToken(url: string, entities: string): bool {
|
||||
return HomeAssistantConfig.save(url, entities, tokenFile.text());
|
||||
}
|
||||
|
||||
function clearToken(): bool {
|
||||
return HomeAssistantConfig.clearToken();
|
||||
}
|
||||
@@ -30,7 +48,8 @@ ShellRoot {
|
||||
configured: HomeAssistantConfig.configured,
|
||||
busy: HomeAssistantConfig.busy,
|
||||
lastError: HomeAssistantConfig.lastError,
|
||||
pendingPayloadEmpty: HomeAssistantConfig.pendingPayload === ""
|
||||
pendingPayloadEmpty: HomeAssistantConfig.pendingPayload === "",
|
||||
refreshCalls: root.refreshCalls
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
helper="$repo_dir/config/dot/quickshell/scripts/panama-home-assistant-config"
|
||||
service="$repo_dir/config/dot/quickshell/services/HomeAssistantConfig.qml"
|
||||
page="$repo_dir/config/dot/quickshell/modules/settings/HomePhonePage.qml"
|
||||
password_field="$repo_dir/config/dot/quickshell/modules/settings/PasswordField.qml"
|
||||
harness_fixture="$repo_dir/tests/quickshell/HomeAssistantConfigHarness.qml"
|
||||
work="$(mktemp -d /tmp/panama-ha-config.XXXXXX)"
|
||||
env_file="$work/env"
|
||||
@@ -34,8 +35,13 @@ if rg -q 'command:.*(token|pendingPayload)' "$service"; then
|
||||
fail 'credential data can reach a process command line'
|
||||
fi
|
||||
rg -Fq 'PasswordField {' "$page" || fail 'Home Assistant token is not entered through the masked field'
|
||||
rg -Fq 'activeFocusOnTab: true' "$password_field" || fail 'masked credential field is not keyboard reachable'
|
||||
rg -Fq 'HomeAssistantConfig.save(' "$page" || fail 'Home Assistant configuration cannot be saved from Settings'
|
||||
rg -Fq 'HomeAssistantConfig.clearToken()' "$page" || fail 'stored Home Assistant token cannot be cleared'
|
||||
rg -Fq 'id: clearTokenButton' "$page" || fail 'clear-token action has no keyboard control identity'
|
||||
rg -Fq 'id: saveHomeConfigButton' "$page" || fail 'save action has no keyboard control identity'
|
||||
rg -Fq 'activeFocusOnTab: enabled' "$page" || fail 'credential actions are not in tab order'
|
||||
rg -Fq 'Keys.onReturnPressed:' "$page" || fail 'credential actions have no keyboard activation'
|
||||
|
||||
cat >"$env_file" <<'EOF'
|
||||
# Existing private shell settings must survive byte-for-byte.
|
||||
@@ -124,12 +130,16 @@ cp -a "$repo_dir/config/dot/quickshell" "$config_path"
|
||||
cp "$harness_fixture" "$harness"
|
||||
printf '%s\n' \
|
||||
"export PANAMA_HOME_ASSISTANT_URL='https://qml-old.example.test'" \
|
||||
"export PANAMA_HOME_ASSISTANT_TOKEN='qml-private-token'" \
|
||||
"export PANAMA_HOME_ASSISTANT_TOKEN=''" \
|
||||
"export PANAMA_HOME_ASSISTANT_ENTITIES='light.old'" >"$env_file"
|
||||
chmod 0600 "$env_file"
|
||||
qml_token_file="$work/qml-token"
|
||||
printf '%s' 'qml-private-token' >"$qml_token_file"
|
||||
chmod 0600 "$qml_token_file"
|
||||
|
||||
qs_for_test() {
|
||||
PANAMA_HOME_ASSISTANT_ENV_FILE="$env_file" \
|
||||
PANAMA_TEST_TOKEN_FILE="$qml_token_file" \
|
||||
XDG_CONFIG_HOME="$work/config" XDG_STATE_HOME="$work/state" \
|
||||
qs -p "$harness" "$@"
|
||||
}
|
||||
@@ -151,16 +161,35 @@ for _ in $(seq 1 60); do
|
||||
jq -e '.busy == false and .url == "https://qml-old.example.test"' <<<"$qml_status" >/dev/null && break
|
||||
sleep 0.1
|
||||
done
|
||||
jq -e '.configured == true and .tokenConfigured == true and .pendingPayloadEmpty == true' \
|
||||
jq -e '.configured == false and .tokenConfigured == false and .pendingPayloadEmpty == true' \
|
||||
<<<"$qml_status" >/dev/null || fail "QML service did not load redacted state: $qml_status"
|
||||
|
||||
qs_for_test ipc call home-assistant-config-test save \
|
||||
qs_for_test ipc call home-assistant-config-test saveWithToken \
|
||||
https://qml-new.example.test 'light.office,light.hall' >/dev/null \
|
||||
|| fail 'QML service refused a private token-file update'
|
||||
for _ in $(seq 1 60); do
|
||||
qml_status="$(qs_for_test ipc call home-assistant-config-test status)"
|
||||
jq -e '.busy == false and .configured == true and .tokenConfigured == true
|
||||
and .refreshCalls > 0' <<<"$qml_status" >/dev/null && break
|
||||
sleep 0.1
|
||||
done
|
||||
jq -e '.configured == true and .tokenConfigured == true
|
||||
and .url == "https://qml-new.example.test"
|
||||
and .entities == ["light.office", "light.hall"]
|
||||
and .pendingPayloadEmpty == true and .lastError == "" and .refreshCalls > 0' \
|
||||
<<<"$qml_status" >/dev/null || fail "QML secret stdin save did not settle safely: $qml_status"
|
||||
rg -Fq 'qml-private-token' "$env_file" || fail 'QML secret stdin save did not store the token'
|
||||
if ps -o args= -p "$(qs_for_test list | awk '/Process ID:/ {print $3; exit}')" | rg -Fq 'qml-private-token'; then
|
||||
fail 'QML token appeared in the shell process arguments'
|
||||
fi
|
||||
|
||||
qs_for_test ipc call home-assistant-config-test save \
|
||||
https://qml-final.example.test 'light.bedroom,light.hall' >/dev/null \
|
||||
|| fail 'QML service refused a non-secret update'
|
||||
for _ in $(seq 1 60); do
|
||||
qml_status="$(qs_for_test ipc call home-assistant-config-test status)"
|
||||
jq -e '.busy == false and .url == "https://qml-new.example.test"
|
||||
and .entities == ["light.office", "light.hall"]' <<<"$qml_status" >/dev/null && break
|
||||
jq -e '.busy == false and .url == "https://qml-final.example.test"
|
||||
and .entities == ["light.bedroom", "light.hall"]' <<<"$qml_status" >/dev/null && break
|
||||
sleep 0.1
|
||||
done
|
||||
jq -e '.configured == true and .tokenConfigured == true
|
||||
|
||||
Reference in New Issue
Block a user