The unlock code and the boot lock code can each be a password or a 4-8 digit PIN. A PIN gets an on-screen number pad, so the lock works on a touch-only kiosk. The boot lock can use the unlock code or its own. These are the kiosk's own codes, set in Core Settings -> Password Protection & Lockout or on the Web UI's Lockout page, never the admin (sudo) password. The lock screen moves from an inline data: URL to lockscreen.html. The code is now hashed and compared in the main process, which also slows down guessing: after every 5 wrong codes in a row, input is refused for 30s, doubling up to 5 minutes. Existing configs behave as before: missing fields mean a password, and the same code at boot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cjz8a58eB5P9sUweqd3NHm
273 lines
9.5 KiB
Bash
273 lines
9.5 KiB
Bash
#!/bin/bash
|
|
################################################################################
|
|
# menus/lockout.sh - "Password Protection & Lockout" menu.
|
|
#
|
|
# The unlock code and the (optional, separate) boot code are each either
|
|
# a password or a 4-8 digit PIN - the kiosk's own codes, independent of
|
|
# the admin account's sudo password. A PIN gets an on-screen number pad
|
|
# on the lock screen, so it works on a touch-only kiosk.
|
|
#
|
|
# Fifth menu migrated. Back to config.json (like Display), but with a
|
|
# sensitive field: the lockout password is SHA-256 hashed before it's
|
|
# ever written to disk (matching the Electron app's comparison logic in
|
|
# main.js) - LOCKOUT_PASSWORD must never hold plaintext.
|
|
#
|
|
# Unlike the legacy configure_password_protection wizard (walk through
|
|
# every question once, then one final "save these changes? y/n"), this
|
|
# follows the same immediate-save pattern as every other migrated menu:
|
|
# each action is a complete, standalone change. Re-running "Enable" to
|
|
# change your mind is just as easy as the old "discard changes" path,
|
|
# and there's no separate confirm-at-the-end step to forget.
|
|
#
|
|
# LOCKOUT_ACTIVE_START/LOCKOUT_ACTIVE_END are intentionally never touched
|
|
# here - the app doesn't act on them (see Readme "Configuration Files"),
|
|
# so lib/config.sh just carries whatever is already in config.json
|
|
# through unchanged.
|
|
#
|
|
# Depends on: lib/menu.sh, lib/config.sh being sourced first.
|
|
################################################################################
|
|
|
|
lockout_status() {
|
|
if [[ "$ENABLE_PASSWORD_PROTECTION" == "true" ]]; then
|
|
echo "Password protection: ENABLED"
|
|
echo "Inactivity lockout: ${LOCKOUT_TIMEOUT} minutes$( [[ "$LOCKOUT_TIMEOUT" == "0" ]] && echo " (disabled - boot/wake only)")"
|
|
if [[ -n "$LOCKOUT_AT_TIME" ]]; then
|
|
echo "Daily lock time: $LOCKOUT_AT_TIME"
|
|
else
|
|
echo "Daily lock time: not set"
|
|
fi
|
|
echo "Unlock code: $(lockout_code_label "$LOCKOUT_CODE_TYPE")"
|
|
echo "Lock on boot: $(onoff "$REQUIRE_PASSWORD_ON_BOOT")"
|
|
if [[ "$REQUIRE_PASSWORD_ON_BOOT" == "true" ]]; then
|
|
echo "Boot code: $(lockout_boot_code_label)"
|
|
fi
|
|
else
|
|
echo "Password protection: disabled"
|
|
fi
|
|
}
|
|
|
|
lockout_menu_builder() {
|
|
if [[ "$ENABLE_PASSWORD_PROTECTION" == "true" ]]; then
|
|
MENU_LABELS=(
|
|
"Change unlock password/PIN (currently: $(lockout_code_label "$LOCKOUT_CODE_TYPE"))"
|
|
"Change inactivity lockout timeout (currently: ${LOCKOUT_TIMEOUT}m)"
|
|
"Set/clear daily lock time (currently: ${LOCKOUT_AT_TIME:-not set})"
|
|
"Toggle lock on boot (currently: $(onoff "$REQUIRE_PASSWORD_ON_BOOT"))"
|
|
"Set boot password/PIN (currently: $(lockout_boot_code_label))"
|
|
"Disable password protection"
|
|
)
|
|
MENU_HANDLERS=(
|
|
action_change_password
|
|
action_change_timeout
|
|
action_change_daily_lock
|
|
action_toggle_boot_password
|
|
action_set_boot_code
|
|
action_disable_protection
|
|
)
|
|
else
|
|
MENU_LABELS=("Enable password protection")
|
|
MENU_HANDLERS=(action_enable_protection)
|
|
fi
|
|
}
|
|
|
|
lockout_menu() {
|
|
load_existing_config
|
|
run_menu "PASSWORD PROTECTION & LOCKOUT" lockout_menu_builder lockout_status
|
|
}
|
|
|
|
################################################################################
|
|
# Shared helpers
|
|
################################################################################
|
|
|
|
# "PIN" / "password" for menus and status lines.
|
|
lockout_code_label() {
|
|
[[ "$1" == "pin" ]] && echo "PIN" || echo "password"
|
|
}
|
|
|
|
lockout_boot_code_label() {
|
|
if [[ -z "$BOOT_PASSWORD" ]]; then
|
|
echo "same as unlock"
|
|
else
|
|
echo "separate $(lockout_code_label "$BOOT_CODE_TYPE")"
|
|
fi
|
|
}
|
|
|
|
# True if $1 is a valid kiosk PIN: 4-8 digits.
|
|
lockout_valid_pin() {
|
|
[[ "$1" =~ ^[0-9]{4,8}$ ]]
|
|
}
|
|
|
|
# prompt_code WHAT - asks whether WHAT (e.g. "unlock") is a password or a
|
|
# 4-8 digit PIN, then for the code itself twice. Sets CODE_TYPE ("password"
|
|
# or "pin") and CODE_HASH (SHA-256 hex, matching the Electron app's check
|
|
# in main.js - never plaintext). These are the kiosk's own codes, not
|
|
# any Linux account's password.
|
|
prompt_code() {
|
|
local what="$1" choice
|
|
echo "How should the $what code be entered?"
|
|
echo " 1) Password (letters, numbers, anything - needs a keyboard)"
|
|
echo " 2) PIN (4-8 digits - on-screen number pad, works on a touch screen)"
|
|
while true; do
|
|
read -r -p "Choose [1-2]: " choice
|
|
case "$choice" in
|
|
1) CODE_TYPE="password"; break ;;
|
|
2) CODE_TYPE="pin"; break ;;
|
|
*) echo "❌ Enter 1 or 2" ;;
|
|
esac
|
|
done
|
|
|
|
local label pass1 pass2
|
|
label=$(lockout_code_label "$CODE_TYPE")
|
|
while true; do
|
|
read -r -s -p "Enter $what $label: " pass1
|
|
echo
|
|
if [[ -z "$pass1" ]]; then
|
|
echo "❌ The $label cannot be empty"
|
|
continue
|
|
fi
|
|
if [[ "$CODE_TYPE" == "pin" ]] && ! lockout_valid_pin "$pass1"; then
|
|
echo "❌ A PIN is 4-8 digits (0-9 only)"
|
|
continue
|
|
fi
|
|
read -r -s -p "Confirm $what $label: " pass2
|
|
echo
|
|
if [[ "$pass1" != "$pass2" ]]; then
|
|
echo "❌ They don't match, try again"
|
|
continue
|
|
fi
|
|
CODE_HASH=$(echo -n "$pass1" | sha256sum | cut -d' ' -f1)
|
|
return 0
|
|
done
|
|
}
|
|
|
|
# Prompts for the unlock code into LOCKOUT_PASSWORD/LOCKOUT_CODE_TYPE.
|
|
prompt_and_hash_password() {
|
|
prompt_code "unlock"
|
|
LOCKOUT_CODE_TYPE="$CODE_TYPE"
|
|
LOCKOUT_PASSWORD="$CODE_HASH"
|
|
}
|
|
|
|
################################################################################
|
|
# Actions
|
|
################################################################################
|
|
|
|
action_enable_protection() {
|
|
echo
|
|
echo "Add password protection with automatic lockout:"
|
|
echo " • Blank screen after an inactivity period"
|
|
echo " • Password or PIN required to unlock"
|
|
echo " • Required after display schedule wake-up"
|
|
echo " • Optional: lock at a specific time daily"
|
|
echo " • Optional: locked on system boot, with the same or its own code"
|
|
echo
|
|
echo "These are the kiosk's own codes - not your admin (sudo) password."
|
|
echo
|
|
|
|
prompt_and_hash_password
|
|
|
|
echo
|
|
echo "Session lockout time (minutes of inactivity)."
|
|
echo "Enter 0 to only require a password after display wake or boot."
|
|
LOCKOUT_TIMEOUT=$(ask_integer "Lockout timeout in minutes" "30" 0 1440)
|
|
|
|
echo
|
|
if ask_yes_no "Lock automatically at a specific time each day?" "n"; then
|
|
LOCKOUT_AT_TIME=$(ask_time "Time to lock (24-hour HH:MM)" "17:00")
|
|
else
|
|
LOCKOUT_AT_TIME=""
|
|
fi
|
|
|
|
echo
|
|
BOOT_PASSWORD=""
|
|
BOOT_CODE_TYPE="password"
|
|
if ask_yes_no "Lock the kiosk on system boot/power on?" "y"; then
|
|
REQUIRE_PASSWORD_ON_BOOT="true"
|
|
echo
|
|
if ask_yes_no "Use a different password/PIN at boot than for unlocking?" "n"; then
|
|
prompt_code "boot"
|
|
BOOT_CODE_TYPE="$CODE_TYPE"
|
|
BOOT_PASSWORD="$CODE_HASH"
|
|
fi
|
|
else
|
|
REQUIRE_PASSWORD_ON_BOOT="false"
|
|
fi
|
|
|
|
ENABLE_PASSWORD_PROTECTION="true"
|
|
log_success "Password protection enabled (lockout: ${LOCKOUT_TIMEOUT}m)"
|
|
save_config
|
|
}
|
|
|
|
action_disable_protection() {
|
|
ENABLE_PASSWORD_PROTECTION="false"
|
|
LOCKOUT_PASSWORD=""
|
|
LOCKOUT_TIMEOUT=0
|
|
LOCKOUT_AT_TIME=""
|
|
REQUIRE_PASSWORD_ON_BOOT="false"
|
|
LOCKOUT_CODE_TYPE="password"
|
|
BOOT_PASSWORD=""
|
|
BOOT_CODE_TYPE="password"
|
|
log_success "Password protection disabled"
|
|
save_config
|
|
}
|
|
|
|
action_change_password() {
|
|
echo
|
|
prompt_and_hash_password
|
|
log_success "Unlock $(lockout_code_label "$LOCKOUT_CODE_TYPE") updated"
|
|
save_config
|
|
}
|
|
|
|
action_change_timeout() {
|
|
echo
|
|
echo "Session lockout time (minutes of inactivity)."
|
|
echo "Enter 0 to only require a password after display wake or boot."
|
|
LOCKOUT_TIMEOUT=$(ask_integer "Lockout timeout in minutes" "$LOCKOUT_TIMEOUT" 0 1440)
|
|
log_success "Lockout timeout: ${LOCKOUT_TIMEOUT}m"
|
|
save_config
|
|
}
|
|
|
|
action_change_daily_lock() {
|
|
echo
|
|
local default_prompt
|
|
[[ -n "$LOCKOUT_AT_TIME" ]] && default_prompt="y" || default_prompt="n"
|
|
|
|
if ask_yes_no "Lock automatically at a specific time each day?" "$default_prompt"; then
|
|
LOCKOUT_AT_TIME=$(ask_time "Time to lock (24-hour HH:MM)" "${LOCKOUT_AT_TIME:-17:00}")
|
|
log_success "Will lock at ${LOCKOUT_AT_TIME} daily"
|
|
else
|
|
LOCKOUT_AT_TIME=""
|
|
log_success "Daily lock time cleared"
|
|
fi
|
|
save_config
|
|
}
|
|
|
|
action_toggle_boot_password() {
|
|
if [[ "$REQUIRE_PASSWORD_ON_BOOT" == "true" ]]; then
|
|
REQUIRE_PASSWORD_ON_BOOT="false"
|
|
log_warning "Lock on boot disabled"
|
|
else
|
|
REQUIRE_PASSWORD_ON_BOOT="true"
|
|
log_success "Lock on boot enabled (boot code: $(lockout_boot_code_label))"
|
|
fi
|
|
save_config
|
|
}
|
|
|
|
action_set_boot_code() {
|
|
echo
|
|
echo "The boot lock screen can use its own password/PIN, or the unlock one."
|
|
echo "Currently: $(lockout_boot_code_label)"
|
|
echo
|
|
if ask_yes_no "Use a separate password/PIN at boot?" "y"; then
|
|
prompt_code "boot"
|
|
BOOT_CODE_TYPE="$CODE_TYPE"
|
|
BOOT_PASSWORD="$CODE_HASH"
|
|
REQUIRE_PASSWORD_ON_BOOT="true"
|
|
log_success "Boot $(lockout_code_label "$BOOT_CODE_TYPE") set (lock on boot: on)"
|
|
else
|
|
BOOT_PASSWORD=""
|
|
BOOT_CODE_TYPE="password"
|
|
log_success "Boot uses the unlock $(lockout_code_label "$LOCKOUT_CODE_TYPE")"
|
|
fi
|
|
save_config
|
|
}
|