#!/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 ""