pf_app/install.sh
Paul Trowbridge c2e6fc8e77 Put the app behind a login
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>
2026-09-14 22:20:25 -04:00

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 ""