Project-Image-Uploader/scripts/create_admin_user.sh
matthias.lotz 6332b82c6a Feature Request: admin session security
- replace bearer auth with session+CSRF flow and add admin user directory

- update frontend moderation flow, force password change gate, and new CLI

- refresh changelog/docs/feature plan + ensure swagger dev experience
2025-11-23 21:18:42 +01:00

281 lines
8.1 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage: ./scripts/create_admin_user.sh --username <name> --password <pass> [options]
Erstellt per HTTP-API entweder den initialen Admin (wenn noch keiner existiert)
oder legt nach Login mit bestehenden Admin-Zugangsdaten zusätzliche Admins an.
Benötigte Tools: curl, jq
Optionen:
--server <url> Backend-Basis-URL (Standard: http://localhost:5000)
--username <name> Neuer Admin-Benutzername (Pflicht)
--password <pass> Neues Admin-Passwort (Pflicht, min. 10 Zeichen)
--role <role> Rolle für den Benutzer (Standard: admin)
--require-password-change Markiert den Benutzer für Passwort-Änderung beim Login
--admin-user <name> Bestehender Admin (für zusätzliche Benutzer erforderlich)
--admin-password <pass> Passwort des bestehenden Admins
--insecure TLS-Zertifikatsprüfung deaktivieren (z. B. bei Self-Signed)
-h, --help Diese Hilfe anzeigen
Hinweis: Wenn bereits ein Admin existiert, müssen --admin-user und --admin-password
angegeben werden, damit sich das Skript anmelden und den neuen Benutzer anlegen kann.
EOF
}
require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "ERROR: Benötigtes Tool '$1' wurde nicht gefunden." >&2
exit 1
fi
}
# Standardwerte
SERVER_URL="${SERVER_URL:-${SERVER:-http://localhost:5000}}"
TARGET_USERNAME=""
TARGET_PASSWORD=""
TARGET_ROLE="admin"
REQUIRE_PASSWORD_CHANGE=false
ADMIN_USERNAME="${ADMIN_USERNAME:-}"
ADMIN_PASSWORD="${ADMIN_PASSWORD:-}"
CURL_INSECURE=0
while [[ $# -gt 0 ]]; do
case "$1" in
--server)
SERVER_URL="$2"
shift 2
;;
--username)
TARGET_USERNAME="$2"
shift 2
;;
--password)
TARGET_PASSWORD="$2"
shift 2
;;
--role)
TARGET_ROLE="$2"
shift 2
;;
--require-password-change)
REQUIRE_PASSWORD_CHANGE=true
shift
;;
--admin-user)
ADMIN_USERNAME="$2"
shift 2
;;
--admin-password)
ADMIN_PASSWORD="$2"
shift 2
;;
--insecure)
CURL_INSECURE=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unbekannte Option: $1" >&2
usage
exit 1
;;
esac
done
if [[ -z "$TARGET_USERNAME" || -z "$TARGET_PASSWORD" ]]; then
echo "ERROR: --username und --password sind erforderlich." >&2
usage
exit 1
fi
require_cmd curl
require_cmd jq
BASE_URL="${SERVER_URL%/}"
COOKIE_JAR="$(mktemp)"
trap 'rm -f "$COOKIE_JAR"' EXIT
CURL_EXTRA=()
if [[ "$CURL_INSECURE" -eq 1 ]]; then
CURL_EXTRA+=(-k)
fi
HTTP_STATUS=0
HTTP_BODY=""
api_request() {
local method="$1"
local url="$2"
local data="$3"
shift 3
local tmp
tmp="$(mktemp)"
local args=(-sS -o "$tmp" -w '%{http_code}' -X "$method" -H 'Accept: application/json' -b "$COOKIE_JAR" -c "$COOKIE_JAR")
if [[ -n "$data" ]]; then
args+=(-H 'Content-Type: application/json' -d "$data")
fi
if [[ ${#CURL_EXTRA[@]} -gt 0 ]]; then
args+=("${CURL_EXTRA[@]}")
fi
if [[ $# -gt 0 ]]; then
args+=("$@")
fi
local status
if ! status=$(curl "${args[@]}" "$url"); then
rm -f "$tmp"
echo "ERROR: HTTP-Anfrage fehlgeschlagen (${method} ${url})" >&2
exit 1
fi
HTTP_STATUS="$status"
HTTP_BODY="$(cat "$tmp")"
rm -f "$tmp"
}
assert_status() {
local expected="$1"
shift || true
local matched=0
IFS=',' read -ra codes <<< "$expected"
for code in "${codes[@]}"; do
if [[ "$HTTP_STATUS" == "$code" ]]; then
matched=1
break
fi
done
if [[ $matched -eq 0 ]]; then
local msg
msg=$(echo "$HTTP_BODY" | jq -r '.error // .message // empty' 2>/dev/null || true)
if [[ -z "$msg" ]]; then
msg="$HTTP_BODY"
fi
echo "ERROR: Anfrage fehlgeschlagen (${HTTP_STATUS}): $msg" >&2
exit 1
fi
}
json_bool() {
if [[ "$1" == "true" ]]; then
echo "true"
else
echo "false"
fi
}
jq_payload() {
jq -n "$@"
}
echo "INFO: Prüfe Setup-Status am ${BASE_URL}..."
api_request "GET" "${BASE_URL}/auth/setup/status" ""
assert_status "200"
needs_setup=$(echo "$HTTP_BODY" | jq -r '.needsSetup // false')
if [[ "$needs_setup" == "true" ]]; then
echo "INFO: Kein Admin vorhanden erstelle initialen Benutzer..."
payload=$(jq_payload --arg username "$TARGET_USERNAME" --arg password "$TARGET_PASSWORD" '{username:$username,password:$password}')
api_request "POST" "${BASE_URL}/auth/setup/initial-admin" "$payload"
assert_status "201"
user=$(echo "$HTTP_BODY" | jq -r '.user.username')
echo "SUCCESS: Initialer Admin '$user' erstellt."
exit 0
fi
if [[ -z "$ADMIN_USERNAME" || -z "$ADMIN_PASSWORD" ]]; then
echo "ERROR: Bestehender Admin benötigt: bitte --admin-user und --admin-password angeben." >&2
exit 1
fi
echo "INFO: Melde bestehenden Admin an..."
login_payload=$(jq_payload --arg username "$ADMIN_USERNAME" --arg password "$ADMIN_PASSWORD" '{username:$username,password:$password}')
api_request "POST" "${BASE_URL}/auth/login" "$login_payload"
assert_status "200"
api_request "GET" "${BASE_URL}/auth/csrf-token" ""
assert_status "200"
csrf_token=$(echo "$HTTP_BODY" | jq -r '.csrfToken // empty')
if [[ -z "$csrf_token" ]]; then
echo "ERROR: Konnte keinen CSRF-Token abrufen." >&2
exit 1
fi
require_flag=$(json_bool "$REQUIRE_PASSWORD_CHANGE")
create_payload=$(jq_payload --arg username "$TARGET_USERNAME" --arg password "$TARGET_PASSWORD" --arg role "$TARGET_ROLE" --argjson requirePasswordChange "$require_flag" '{username:$username,password:$password,role:($role // "admin"),requirePasswordChange:$requirePasswordChange}')
echo "INFO: Lege zusätzlichen Admin an..."
api_request "POST" "${BASE_URL}/api/admin/users" "$create_payload" -H "X-CSRF-Token: $csrf_token"
assert_status "201"
created_user=$(echo "$HTTP_BODY" | jq -r '.user.username')
created_role=$(echo "$HTTP_BODY" | jq -r '.user.role')
echo "SUCCESS: Neuer Admin '${created_user}' (Rolle: ${created_role}) wurde erstellt."
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
BACKEND_DIR="${REPO_ROOT}/backend"
NODE_SCRIPT="${BACKEND_DIR}/src/scripts/createAdminUser.js"
STAMP_FILE="${BACKEND_DIR}/.create_admin_user_deps.stamp"
usage() {
cat <<EOF
Usage: ./scripts/create_admin_user.sh --username <name> --password <pass> [--role <role>] [--require-password-change]
Beispiele:
./scripts/create_admin_user.sh --username admin2 --password 'SehrSicher123!'
./scripts/create_admin_user.sh --username audit --password 'NochSicherer123!' --role auditor --require-password-change
EOF
}
if [[ $# -eq 0 ]]; then
usage
exit 1
fi
case "${1:-}" in
-h|--help)
usage
exit 0
;;
esac
if [[ ! -f "${NODE_SCRIPT}" ]]; then
echo "createAdminUser.js nicht gefunden (erwartet unter ${NODE_SCRIPT})." >&2
exit 1
fi
if ! command -v node >/dev/null 2>&1; then
echo "Node.js wird benötigt, bitte installieren." >&2
exit 1
fi
ensure_node_modules() {
if [[ ! -d "${BACKEND_DIR}/node_modules" ]]; then
echo "📦 Installiere Backend-Abhängigkeiten (npm install)..."
(cd "${BACKEND_DIR}" && npm install)
touch "${STAMP_FILE}"
return
fi
if [[ -f "${BACKEND_DIR}/package-lock.json" ]] && [[ ! -f "${STAMP_FILE}" || "${BACKEND_DIR}/package-lock.json" -nt "${STAMP_FILE}" ]]; then
echo "📦 Aktualisiere Backend-Abhängigkeiten (npm install)..."
(cd "${BACKEND_DIR}" && npm install)
touch "${STAMP_FILE}"
fi
}
ensure_node_modules
pushd "${BACKEND_DIR}" >/dev/null
# Standardmäßig teueres Preview-Rendering überspringen
export SKIP_PREVIEW_GENERATION="${SKIP_PREVIEW_GENERATION:-1}"
node "${NODE_SCRIPT}" "$@"
popd >/dev/null