feat: Authelia auto-login with machine-ID-bound AES-256 encryption

Adds an Authelia Auto-Login addon (Addons menu → 5) that:
- Prompts for Authelia URL, username, and password
- Encrypts the password with AES-256-CBC keyed from /etc/machine-id
  via scrypt (the encrypted blob is machine-specific and useless elsewhere)
- Stores autheliaURL, autheliaUsername, autheliaEncryptedPassword in config.json

On every kiosk startup, main.js decrypts the password and calls
Authelia's /api/firstfactor with keepMeLoggedIn:true before any
BrowserViews are created. Electron's session.defaultSession handles
the Set-Cookie response automatically, so all sites load already
authenticated.

To set up credentials via SSH:
  ssh user@kiosk
  ./ubuntu-based-kiosk-v*.sh  →  Addons → 5. Authelia Auto-Login

https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
This commit is contained in:
Claude
2026-06-15 14:35:41 +00:00
parent 42aafff219
commit 2a5fb6188b
+119 -3
View File
@@ -4029,11 +4029,12 @@ NOISECFG
###################################################################
echo "[13/27] Installing Electron..."
sudo -u "$KIOSK_USER" tee "$KIOSK_DIR/main.js" > /dev/null <<'MAINJS'
const {app,BrowserWindow,BrowserView,globalShortcut,ipcMain,dialog}=require('electron');
const {app,BrowserWindow,BrowserView,globalShortcut,ipcMain,dialog,session}=require('electron');
const {exec}=require('child_process');
const fs=require('fs');
const path=require('path');
const os=require('os');
const crypto=require('crypto');
// Suppress EPIPE errors (happen when no terminal attached)
process.stdout.on('error',(e)=>{if(e.code!=='EPIPE')throw e;});
@@ -4100,6 +4101,11 @@ let lockoutActivityTime=Date.now();
let requirePasswordAfterDisplay=false;
let lastScheduledLockCheck=0;
// Authelia auto-login state
let autheliaURL='';
let autheliaUsername='';
let autheliaEncryptedPassword='';
function loadConfig(){
try{
if(!fs.existsSync(CONFIG_FILE)){
@@ -4123,6 +4129,9 @@ function loadConfig(){
lockoutActiveStart=config.lockoutActiveStart||"";
lockoutActiveEnd=config.lockoutActiveEnd||"";
requirePasswordOnBoot=(config.requirePasswordOnBoot===true);
autheliaURL=config.autheliaURL||'';
autheliaUsername=config.autheliaUsername||'';
autheliaEncryptedPassword=config.autheliaEncryptedPassword||'';
console.log('[CONFIG] ═════════════════════════════════');
console.log('[CONFIG] Home tab index:',homeTabIndex);
@@ -5276,8 +5285,36 @@ function showPowerMenu(){
}
}
function createWindow(){
async function autheliaAuthenticate(){
if(!autheliaURL||!autheliaUsername||!autheliaEncryptedPassword)return;
try{
const machineId=fs.readFileSync('/etc/machine-id','utf8').trim();
const key=crypto.scryptSync(machineId,'kiosk-authelia-v1',32);
const buf=Buffer.from(autheliaEncryptedPassword,'base64');
const iv=buf.subarray(0,16);
const enc=buf.subarray(16);
const decipher=crypto.createDecipheriv('aes-256-cbc',key,iv);
const password=Buffer.concat([decipher.update(enc),decipher.final()]).toString('utf8');
const res=await session.defaultSession.fetch(`${autheliaURL}/api/firstfactor`,{
method:'POST',
headers:{'Content-Type':'application/json','User-Agent':'kiosk/1.0'},
body:JSON.stringify({username:autheliaUsername,password,keepMeLoggedIn:true,requestMethod:'GET',targetURL:''})
});
const body=await res.json().catch(()=>({}));
if(res.ok&&body.status==='OK'){
console.log('[AUTHELIA] Authenticated as',autheliaUsername);
}else{
console.error('[AUTHELIA] Auth failed:',res.status,body.message||'');
}
}catch(e){
console.error('[AUTHELIA] Error:',e.message);
}
}
async function createWindow(){
tabs=loadConfig();
await autheliaAuthenticate();
mainWindow=new BrowserWindow({
fullscreen:true,
@@ -9903,6 +9940,83 @@ show_easy_asterisk_status() {
echo "────────────────────────────────────────────────────────────"
}
configure_authelia() {
clear
echo "════════════════════════════════════════════════════════════"
echo " Authelia Auto-Login Setup"
echo "════════════════════════════════════════════════════════════"
echo
echo "Stores encrypted Authelia credentials so the kiosk"
echo "authenticates automatically on every startup."
echo "Password is encrypted with this machine's unique ID —"
echo "the encrypted blob is useless on any other machine."
echo
local config_file="$KIOSK_DIR/config.json"
if [[ ! -f "$config_file" ]]; then
echo "✗ config.json not found — run a full install first."
read -r -p "Press Enter to return..." _; return
fi
# Show current status
local current_url current_user
current_url=$(jq -r '.autheliaURL // ""' "$config_file" 2>/dev/null)
current_user=$(jq -r '.autheliaUsername // ""' "$config_file" 2>/dev/null)
if [[ -n "$current_url" ]]; then
echo "Current config:"
echo " URL: $current_url"
echo " Username: $current_user"
echo
read -r -p "Overwrite existing Authelia config? [y/N]: " confirm
[[ "$confirm" != "y" && "$confirm" != "Y" ]] && return
echo
fi
read -r -p "Authelia URL (e.g. https://auth.yourdomain.com): " authelia_url
[[ -z "$authelia_url" ]] && echo "Cancelled." && read -r -p "Press Enter..." _; [[ -z "$authelia_url" ]] && return
read -r -p "Authelia username: " authelia_user
[[ -z "$authelia_user" ]] && echo "Cancelled." && read -r -p "Press Enter..." _; [[ -z "$authelia_user" ]] && return
read -r -s -p "Authelia password: " authelia_pass
echo
[[ -z "$authelia_pass" ]] && echo "Cancelled." && read -r -p "Press Enter..." _; [[ -z "$authelia_pass" ]] && return
echo "Encrypting with machine ID..."
local encrypted
encrypted=$(node -e "
const crypto=require('crypto'),fs=require('fs');
const id=fs.readFileSync('/etc/machine-id','utf8').trim();
const key=crypto.scryptSync(id,'kiosk-authelia-v1',32);
const iv=crypto.randomBytes(16);
const c=crypto.createCipheriv('aes-256-cbc',key,iv);
const enc=Buffer.concat([c.update(process.argv[1],'utf8'),c.final()]);
process.stdout.write(Buffer.concat([iv,enc]).toString('base64'));
" "$authelia_pass" 2>/dev/null)
if [[ -z "$encrypted" ]]; then
echo "✗ Encryption failed — is Node.js installed?"
read -r -p "Press Enter..." _; return
fi
local tmp
tmp=$(mktemp)
jq --arg url "$authelia_url" \
--arg user "$authelia_user" \
--arg enc "$encrypted" \
'. + {autheliaURL: $url, autheliaUsername: $user, autheliaEncryptedPassword: $enc}' \
"$config_file" > "$tmp" && mv "$tmp" "$config_file"
sudo chown "$KIOSK_USER:$KIOSK_USER" "$config_file"
echo
echo "✓ Authelia config saved."
echo " To clear it later, remove autheliaURL / autheliaUsername /"
echo " autheliaEncryptedPassword from $config_file"
echo
read -r -p "Restart kiosk display now? [y/N]: " restart
if [[ "$restart" == "y" || "$restart" == "Y" ]]; then
sudo systemctl restart lightdm
fi
}
# Main Easy Asterisk addon menu
addon_easy_asterisk_intercom() {
while true; do
@@ -11086,15 +11200,17 @@ addons_menu() {
echo " 2. CUPS Printing"
echo " 3. Remote Access (VNC/VPN)"
echo " 4. Easy Asterisk Intercom"
echo " 5. Authelia Auto-Login"
echo " 0. Return"
echo
read -r -p "Choose [0-4]: " choice
read -r -p "Choose [0-5]: " choice
case "$choice" in
1) addon_lms_squeezelite ;;
2) addon_cups ;;
3) remote_access_menu ;;
4) addon_easy_asterisk_intercom ;;
5) configure_authelia ;;
0) return ;;
esac
done