- 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
281 lines
8.1 KiB
Bash
Executable File
281 lines
8.1 KiB
Bash
Executable File
#!/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
|