LibrePortal/scripts/app/auth_adapter.sh
librelad f9ec4cc986 refactor(auth): drop the unread AUTH_PROFILE key
Eleven app configs declared CFG_<APP>_AUTH_PROFILE as a "capability tier for the
WebUI auth tools". Nothing read it — not a shell script, not the frontend, and it
was never emitted into apps.json, so the WebUI could not have acted on it even in
principle.

The job it was meant to do is already done, and done better: authAdapterCanDo
tests `declare -F authAdapter_<app>_<method>`, so what an app can do is derived
from the functions it actually implements. A declared tier is a second source of
truth that can only drift — traefik declared single_password while its adapter
implements setPassword only, and linkding declared nothing at all while shipping
a full multi-user adapter, and neither mismatch had any effect.

Removed the key and its comment from all eleven configs, and replaced the stale
contract note in auth_adapter.sh with what the dispatcher really does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 23:13:53 +01:00

83 lines
3.1 KiB
Bash

#!/bin/bash
# Universal user/credential adapter for app tools.
#
# An app opts in by implementing adapter functions in
# containers/<app>/scripts/<app>_auth.sh:
# authAdapter_<app>_setPassword "$user" "$password"
# authAdapter_<app>_createUser "$user" "$password" "$email" "$isAdmin"
# authAdapter_<app>_listUsers
#
# What an app can do is discovered from which of those exist (authAdapterCanDo),
# not declared anywhere. There used to be a CFG_<APP>_AUTH_PROFILE key naming a
# capability tier, but nothing ever read it — it was a second source of truth
# that could only drift out of step with the functions actually implemented.
#
# Tool wrappers call authAdapterCall <app> <method> <args...>. The
# dispatcher checks the function exists, runs it, and refreshes apps.json
# via webuiPatchAppConfigJson so new admin creds surface in the WebUI.
authAdapterCanDo() {
local app="$1" method="$2"
declare -F "authAdapter_${app}_${method}" >/dev/null 2>&1
}
authAdapterCall() {
local app="$1" method="$2"
shift 2
local fn="authAdapter_${app}_${method}"
if ! declare -F "$fn" >/dev/null 2>&1; then
isError "Auth adapter for '$app' does not implement '$method'."
return 1
fi
"$fn" "$@"
local rc=$?
if (( rc == 0 )) && declare -F webuiPatchAppConfigJson >/dev/null 2>&1; then
webuiPatchAppConfigJson "$app" >/dev/null 2>&1 || true
fi
return $rc
}
# Persist a value to CFG_<APP>_<KEY> in the per-app config file.
#
# Adapters call with the bare name (ADMIN_PASSWORD), but a key whose value is
# generated carries a slot number (CFG_<APP>_ADMIN_PASSWORD_1). Resolve to the
# slot when the bare key isn't there, so an adapter never has to know how a
# credential is numbered — and so adding a slot to a config can't quietly
# disconnect the adapter that writes it.
#
# updateConfigOption only rewrites a key that already exists; handed a name that
# is absent it just prints a notice. That is a silent failure from the caller's
# side: the app's password really did change, but the config and the WebUI carry
# on showing the old one. Hence the explicit return 1 and warning below.
authPersistCfg() {
local app="$1" key="$2" value="$3"
local cfg="${containers_dir}${app}/${app}.config"
[[ ! -f "$cfg" ]] && cfg="${install_containers_dir}/${app}/${app}.config"
[[ ! -f "$cfg" ]] && return 1
local app_upper="${app^^}"
app_upper="${app_upper//-/_}"
local name="CFG_${app_upper}_${key}"
if ! grep -q "^${name}=" "$cfg" 2>/dev/null; then
local slotted
slotted=$(grep -oE "^CFG_${app_upper}_${key}_[0-9]+=" "$cfg" 2>/dev/null | head -1)
if [[ -n "$slotted" ]]; then
name="${slotted%=}"
else
isNotice "$app has no ${name} (or numbered slot) in $(basename "$cfg") — the new value was applied to the app but not recorded in its config."
return 1
fi
fi
updateConfigOption "$name" "$value" "$cfg"
}
# Read a tool-modal arg (pipe-encoded) and unescape pipes.
authToolArg() {
local v
v=$(toolArgsGet "$1" "$2") || true
printf '%s' "${v//%7C/|}"
}