#!/usr/bin/env bash # # Auth smoke test for the routes closed in the security hotfix. # # For every route that was made non-public, asserts: # - no token -> 401 # - low-privilege token -> 403 # - super-admin token -> anything but 401/403 (the guard let it through) # # And asserts the genuinely-public routes still answer 200, plus that # GET /customers no longer leaks anything beyond the four login-lookup fields. # # Usage: API=http://localhost:3000/api bash scripts/auth-smoke.sh # Needs the API running and the dev seed applied (npx prisma db seed). set -uo pipefail API="${API:-http://localhost:3000/api}" SUPER_PHONE=09128081392; SUPER_PASS=09128081392 # ut 85, all rids LOW_PHONE=09123334444; LOW_PASS=admin1234 # ut 88, 5 unrelated rids pass=0; fail=0 ok() { printf ' \033[32mPASS\033[0m %s\n' "$1"; pass=$((pass+1)); } bad() { printf ' \033[31mFAIL\033[0m %s\n' "$1"; fail=$((fail+1)); } login() { curl -s -X POST "$API/admin/auth/login" -H 'Content-Type: application/json' \ -d "{\"phone\":\"$1\",\"password\":\"$2\"}" | sed -n 's/.*"api_token":"\([^"]*\)".*/\1/p' } code() { # method path [token] local m=$1 p=$2 t=${3:-} if [ -n "$t" ]; then curl -s -o /dev/null -w '%{http_code}' -X "$m" "$API/$p" \ -H "Authorization: Bearer $t" -H 'Content-Type: application/json' -d '{}' else curl -s -o /dev/null -w '%{http_code}' -X "$m" "$API/$p" \ -H 'Content-Type: application/json' -d '{}' fi } SUPER=$(login "$SUPER_PHONE" "$SUPER_PASS") LOW=$(login "$LOW_PHONE" "$LOW_PASS") [ -z "$SUPER" ] && { echo "super-admin login failed — is the seed applied?"; exit 1; } [ -z "$LOW" ] && { echo "low-priv login failed — is the seed applied?"; exit 1; } # route | required rid CLOSED=( "GET|accounting/customers/x/balance|wallet.list" "POST|accounting/customers/x/reconcile|wallet.charge" "POST|accounting/repayments/gold|document.create" "POST|accounting/repayments/rial|document.create" "POST|accounting/entries/x/confirm|document.list.confirm" "PATCH|orders/x/status|order.list.sts.submit" ) echo "Closed routes — no token must be 401:" for row in "${CLOSED[@]}"; do IFS='|' read -r m p _ <<<"$row" c=$(code "$m" "$p") [ "$c" = 401 ] && ok "$m $p -> 401" || bad "$m $p -> $c (want 401)" done echo "Closed routes — low-privilege token must be 403:" for row in "${CLOSED[@]}"; do IFS='|' read -r m p rid <<<"$row" c=$(code "$m" "$p" "$LOW") [ "$c" = 403 ] && ok "$m $p [$rid] -> 403" || bad "$m $p [$rid] -> $c (want 403)" done echo "Closed routes — super admin must get past the guard:" for row in "${CLOSED[@]}"; do IFS='|' read -r m p _ <<<"$row" c=$(code "$m" "$p" "$SUPER") case "$c" in 401|403) bad "$m $p -> $c (guard rejected a super admin)" ;; *) ok "$m $p -> $c (through the guard)" ;; esac done echo "Public routes must stay reachable:" for p in "catalog/products" "pricing/latest?karatPerMille=750" "home/slider" "customers"; do c=$(curl -s -o /dev/null -w '%{http_code}' "$API/$p") [ "$c" = 200 ] && ok "GET $p -> 200" || bad "GET $p -> $c (want 200)" done echo "GET /customers must expose only the login-lookup fields:" keys=$(curl -s "$API/customers" | sed -n 's/.*\({[^}]*}\).*/\1/p' | head -1 | grep -oE '"[a-zA-Z]+":' | tr -d '":' | sort -u | tr '\n' ' ') want="id isActive isArchived mobile " [ "$keys" = "$want" ] && ok "fields = $keys" || bad "fields = [$keys] want [$want]" echo echo "passed: $pass failed: $fail" [ "$fail" -eq 0 ]