The server had no authentication: every /api route was open, CORS allowed any origin, and the identity written to the audit log came from the request body — the UI sent a hardcoded pf_user: 'admin', which any client could have set to anything it liked. Accounts live in pf.app_user with scrypt hashes from node's own crypto, so there is no native build step and the parameters travel with each hash. Sessions are express-session over connect-pg-simple in pf.session: a restart no longer signs everyone out, and a session can be revoked by deleting its row, which is how disable-user cuts off access immediately rather than at cookie expiry. Everything under /api except login/logout/me now requires a session, and the React app is mounted only once there is one — its load effects call the API on mount, so a logged-out mount would just fire a burst of 401s. A session that expires while the app is open lands back on the login screen: auth.jsx wraps fetch once rather than teaching every call site to check. Identity is now read from the session for pf_user, created_by and closed_by, and the body values are ignored. Hardened for an internet-facing deployment: trust proxy so req.ip and secure-cookie detection are right behind TLS termination, httpOnly + SameSite=Lax + Secure cookies, ten login failures per IP per fifteen minutes, one error message for unknown, wrong and disabled alike, and a fresh session id on success. CORS is off entirely unless CORS_ORIGIN names an origin — a wildcard alongside a session cookie would be CSRF by construction. The server refuses to boot without SESSION_SECRET rather than falling back to a guessable default. pf.sh grows add-user, passwd, list-users, disable-user and enable-user; passwords are read on stdin and hashed before they reach psql, so no plaintext in argv or shell history. install.sh generates the secret, applies 02_auth.sql, and creates the first account. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
83 lines
2.8 KiB
Bash
Executable File
83 lines
2.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -e
|
|
|
|
echo ""
|
|
echo "========================================"
|
|
echo " Pivot Forecast — Install"
|
|
echo "========================================"
|
|
echo ""
|
|
|
|
# ── DB connection ─────────────────────────────────────────────
|
|
read -p "DB host [192.168.1.110]: " DB_HOST
|
|
DB_HOST=${DB_HOST:-192.168.1.110}
|
|
|
|
read -p "DB port [5432]: " DB_PORT
|
|
DB_PORT=${DB_PORT:-5432}
|
|
|
|
read -p "DB name [ubm]: " DB_NAME
|
|
DB_NAME=${DB_NAME:-ubm}
|
|
|
|
read -p "DB user [ptrowbridge]: " DB_USER
|
|
DB_USER=${DB_USER:-ptrowbridge}
|
|
|
|
read -s -p "DB password: " DB_PASSWORD
|
|
echo ""
|
|
|
|
read -p "App port [3030]: " PORT
|
|
PORT=${PORT:-3030}
|
|
|
|
# Session cookies are signed with this; the server refuses to start without it.
|
|
SESSION_SECRET=$(node -e 'console.log(require("crypto").randomBytes(32).toString("hex"))')
|
|
|
|
read -p "Send session cookie over HTTPS only? [Y/n]: " SECURE_ANS
|
|
case "${SECURE_ANS:-y}" in [Nn]*) COOKIE_SECURE=false ;; *) COOKIE_SECURE=true ;; esac
|
|
|
|
# ── Write .env ────────────────────────────────────────────────
|
|
cat > .env <<EOF
|
|
DB_HOST=${DB_HOST}
|
|
DB_PORT=${DB_PORT}
|
|
DB_NAME=${DB_NAME}
|
|
DB_USER=${DB_USER}
|
|
DB_PASSWORD=${DB_PASSWORD}
|
|
PORT=${PORT}
|
|
SESSION_SECRET=${SESSION_SECRET}
|
|
COOKIE_SECURE=${COOKIE_SECURE}
|
|
EOF
|
|
chmod 600 .env
|
|
echo "✓ .env written"
|
|
|
|
# ── npm install ───────────────────────────────────────────────
|
|
echo ""
|
|
echo "Installing node dependencies..."
|
|
npm install --silent
|
|
echo "✓ dependencies installed"
|
|
|
|
# ── schema install ────────────────────────────────────────────
|
|
echo ""
|
|
echo "Installing pf schema into ${DB_NAME}..."
|
|
PGPASSWORD=${DB_PASSWORD} psql \
|
|
-h "${DB_HOST}" \
|
|
-p "${DB_PORT}" \
|
|
-U "${DB_USER}" \
|
|
-d "${DB_NAME}" \
|
|
-v ON_ERROR_STOP=1 \
|
|
-f setup_sql/01_schema.sql \
|
|
-f setup_sql/02_auth.sql
|
|
|
|
echo "✓ schema installed"
|
|
|
|
# ── first account ─────────────────────────────────────────────
|
|
echo ""
|
|
echo "The app is behind a login. Create the first account now:"
|
|
./pf.sh add-user
|
|
|
|
# ── done ─────────────────────────────────────────────────────
|
|
echo ""
|
|
echo "========================================"
|
|
echo " Install complete"
|
|
echo " Start with: npm run dev
|
|
More accounts: ./pf.sh add-user"
|
|
echo " Open: http://$(hostname -I | awk '{print $1}'):${PORT}"
|
|
echo "========================================"
|
|
echo ""
|