From cb6e34d5ce485359b05f7c310c2712863edcd1cb Mon Sep 17 00:00:00 2001 From: olsch01 Date: Sat, 7 Mar 2026 12:19:22 -0500 Subject: [PATCH] feat: add password reset utility script Usage: ./scripts/reset-password.sh Generates bcrypt hash via bcryptjs in the backend container, updates the database, and verifies the hash matches. Co-Authored-By: Claude Opus 4.6 --- scripts/reset-password.sh | 150 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100755 scripts/reset-password.sh diff --git a/scripts/reset-password.sh b/scripts/reset-password.sh new file mode 100755 index 0000000..817660a --- /dev/null +++ b/scripts/reset-password.sh @@ -0,0 +1,150 @@ +#!/usr/bin/env bash +# --------------------------------------------------------------------------- +# reset-password.sh — Reset a user's password in HOA LedgerIQ +# +# Usage: +# ./scripts/reset-password.sh +# +# Examples: +# ./scripts/reset-password.sh admin@hoaledgeriq.com MyNewPassword123 +# ./scripts/reset-password.sh admin@sunrisevalley.org SecurePass! +# --------------------------------------------------------------------------- + +set -euo pipefail + +# ---- Defaults ---- +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +DB_USER="${POSTGRES_USER:-hoafinance}" +DB_NAME="${POSTGRES_DB:-hoafinance}" +COMPOSE_CMD="docker compose" + +# If running with the SSL override, detect it +if [ -f "$PROJECT_DIR/docker-compose.ssl.yml" ] && \ + docker compose -f "$PROJECT_DIR/docker-compose.yml" \ + -f "$PROJECT_DIR/docker-compose.ssl.yml" ps --quiet 2>/dev/null | head -1 | grep -q .; then + COMPOSE_CMD="docker compose -f $PROJECT_DIR/docker-compose.yml -f $PROJECT_DIR/docker-compose.ssl.yml" +fi + +# ---- Colors ---- +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m' + +info() { echo -e "${CYAN}[INFO]${NC} $*"; } +ok() { echo -e "${GREEN}[OK]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +err() { echo -e "${RED}[ERROR]${NC} $*" >&2; } +die() { err "$@"; exit 1; } + +# ---- Helpers ---- + +ensure_containers_running() { + if ! $COMPOSE_CMD ps postgres 2>/dev/null | grep -q "running\|Up"; then + die "PostgreSQL container is not running. Start it with: docker compose up -d postgres" + fi + if ! $COMPOSE_CMD ps backend 2>/dev/null | grep -q "running\|Up"; then + die "Backend container is not running. Start it with: docker compose up -d backend" + fi +} + +# ---- CLI ---- + +usage() { + cat < + +Examples: + $(basename "$0") admin@hoaledgeriq.com MyNewPassword123 + $(basename "$0") admin@sunrisevalley.org SecurePass! + +This script: + 1. Verifies the user exists in the database + 2. Generates a bcrypt hash using bcryptjs (same library the app uses) + 3. Updates the password in the database + 4. Verifies the new hash works + +EOF + exit 0 +} + +# Parse args +case "${1:-}" in + -h|--help|help|"") usage ;; +esac + +[ $# -lt 2 ] && die "Usage: $(basename "$0") " + +EMAIL="$1" +NEW_PASSWORD="$2" + +# Load .env if present +if [ -f "$PROJECT_DIR/.env" ]; then + set -a + # shellcheck disable=SC1091 + source "$PROJECT_DIR/.env" + set +a + DB_USER="${POSTGRES_USER:-hoafinance}" + DB_NAME="${POSTGRES_DB:-hoafinance}" +fi + +# Ensure containers are running +info "Checking containers ..." +ensure_containers_running + +# Verify user exists +info "Looking up user: ${EMAIL} ..." +USER_RECORD=$($COMPOSE_CMD exec -T postgres psql -U "$DB_USER" -d "$DB_NAME" \ + -t -A -c "SELECT id, email, first_name, last_name, is_superadmin FROM shared.users WHERE email = '${EMAIL}';" 2>/dev/null) + +if [ -z "$USER_RECORD" ]; then + die "No user found with email: ${EMAIL}" +fi + +# Parse user info for display +IFS='|' read -r USER_ID USER_EMAIL FIRST_NAME LAST_NAME IS_SUPER <<< "$USER_RECORD" +info "Found user: ${FIRST_NAME} ${LAST_NAME} (${USER_EMAIL})" +if [ "$IS_SUPER" = "t" ]; then + warn "This is a superadmin account" +fi + +# Generate bcrypt hash using bcryptjs inside the backend container +info "Generating bcrypt hash ..." +HASH=$($COMPOSE_CMD exec -T backend node -e " + const bcrypt = require('bcryptjs'); + bcrypt.hash(process.argv[1], 12).then(h => process.stdout.write(h)); +" "$NEW_PASSWORD" 2>/dev/null) + +if [ -z "$HASH" ] || [ ${#HASH} -lt 50 ]; then + die "Failed to generate bcrypt hash. Is the backend container running?" +fi + +# Update the password using a heredoc to avoid shell escaping issues with $ in hashes +info "Updating password ..." +UPDATE_RESULT=$($COMPOSE_CMD exec -T postgres psql -U "$DB_USER" -d "$DB_NAME" -t -A < process.stdout.write(String(r))); +" "$NEW_PASSWORD" "$HASH" 2>/dev/null) + +if [ "$VERIFY" != "true" ]; then + die "Verification failed — the hash does not match the password. Something went wrong." +fi + +echo "" +ok "Password reset successful!" +echo "" +info " User: ${FIRST_NAME} ${LAST_NAME} (${USER_EMAIL})" +info " Login: ${EMAIL}" +echo ""