mailship/test/cors.test.sh

156 lines
4.7 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
# Passing test: CORS is scoped to browser routes only, no wildcard default.
#
# The fix: src/env.ts now requires CORS_ORIGIN (fail closed, no wildcard).
# src/server.ts applies CORS middleware only to browser-facing routes
# (/, /subscription/*, /confirm, /unsubscribe) and adds Vary: Origin.
# Server-to-server routes (/notify) get no CORS headers.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
PORT="${PORT:-4742}"
BASE_URL="http://localhost:$PORT"
PASS=0
FAIL=0
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m'
cleanup() {
kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
rm -rf "$PROJECT_DIR/test-data-cors"
}
trap cleanup EXIT
# Build if needed
cd "$PROJECT_DIR"
if [ ! -d "dist" ]; then
pnpm exec tsc
fi
SECRET="$(openssl rand -hex 32)"
# Set CORS_ORIGIN to a specific allowed origin (fail closed — must be set)
export CORS_ORIGIN="https://app.example.com"
export MAILSHIP_SECRET="$SECRET"
export MAILSHIP_NAME="Mailship Test"
export MAILSHIP_URL="$BASE_URL"
export BASE_URL="$BASE_URL"
export POSTMARK_API_KEY="test"
export POSTMARK_SENDER_ADDRESS="test@test.com"
export DEFAULT_RELAYS="wss://relay.damus.io"
export INDEXER_RELAYS="wss://purplepag.es"
export SEARCH_RELAYS="wss://relay.nostr.band"
export PORT="$PORT"
export DATA_DIR="$PROJECT_DIR/test-data-cors"
# SMTP env vars (required by src/env.ts)
export SMTP_HOST="localhost"
export SMTP_PORT="1025"
export SMTP_USER="test"
export SMTP_PASSWORD="test"
export SMTP_FROM="test@test.com"
mkdir -p "$DATA_DIR"
echo "=== Starting server on port $PORT (CORS_ORIGIN=$CORS_ORIGIN) ==="
node dist/index.js &
SERVER_PID=$!
# Poll until server responds
for i in 1 2 3 4 5 6 7 8 9 10; do
if curl -sf "http://localhost:$PORT/" > /dev/null 2>&1; then
echo "Server ready after ${i}s"
break
fi
sleep 1
done
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
echo -e "${RED}Server failed to start${NC}"
exit 1
fi
echo ""
echo "========================================="
echo " CORS TESTS"
echo "========================================="
echo ""
pass() {
PASS=$((PASS + 1))
echo -e " ${GREEN}✓${NC} $1"
}
fail() {
FAIL=$((FAIL + 1))
echo -e " ${RED}✗${NC} $1"
}
# Test 1: Browser-facing GET /subscription/email returns the configured origin
echo "1. CORS on GET /subscription/email"
CORS_HEADER=$(curl -s -o /dev/null -D - \
"http://localhost:$PORT/subscription/email?pubkey=test_pubkey_123" \
-H "Origin: https://app.example.com" 2>/dev/null | grep -ia 'access-control-allow-origin' || true | head -1 | tr -d '\r')
if echo "$CORS_HEADER" | grep -q 'https://app.example.com'; then
pass "subscription/email returns configured origin (not wildcard)"
elif echo "$CORS_HEADER" | grep -q '\*'; then
fail "BUG: subscription/email still returns wildcard '${CORS_HEADER}'"
else
fail "subscription/email missing Access-Control-Allow-Origin (got: ${CORS_HEADER:-<none>})"
fi
# Test 2: Vary: Origin is present on browser routes
echo ""
echo "2. Vary: Origin header on browser route"
VARY=$(curl -s -o /dev/null -D - \
"http://localhost:$PORT/" \
-H "Origin: https://app.example.com" 2>/dev/null | grep -ia 'vary' || true | head -1 | tr -d '\r')
if echo "$VARY" | grep -qi 'origin'; then
pass "Vary: Origin is present on GET /"
else
fail "Missing Vary: Origin on GET / (got: ${VARY:-<none>})"
fi
# Test 3: Server-to-server /notify has NO CORS headers (relay callback)
echo ""
echo "3. No CORS on server-to-server POST /notify/:id"
CORS_NOTIFY=$(curl -s -o /dev/null -D - \
"http://localhost:$PORT/notify/test-id" \
-X POST -H "Content-Type: application/json" \
-H "Origin: https://evil.com" \
-d '{"id":"abc","relay":"wss://relay.primal.net"}' 2>/dev/null | grep -ia 'access-control-allow-origin' || true | head -1 | tr -d '\r')
if [ -z "$CORS_NOTIFY" ]; then
pass "/notify has no Access-Control-Allow-Origin (server-to-server route)"
else
fail "BUG: /notify returns '${CORS_NOTIFY}' — server-to-server route should have no CORS"
fi
# Test 4: CORS methods on preflight for subscription route
echo ""
echo "4. CORS preflight on PUT /subscription/email"
METHODS=$(curl -s -o /dev/null -D - \
"http://localhost:$PORT/subscription/email" \
-X OPTIONS -H "Origin: https://app.example.com" -H "Access-Control-Request-Method: PUT" 2>/dev/null | grep -ia 'access-control-allow-methods' || true | head -1 | tr -d '\r')
if echo "$METHODS" | grep -qi 'PUT'; then
pass "OPTIONS preflight returns allowed methods"
else
fail "Preflight missing allowed methods (got: ${METHODS:-<none>})"
fi
echo ""
echo "========================================="
echo " RESULTS: $PASS passed, $FAIL failed"
echo "========================================="
if [ "$FAIL" -gt 0 ]; then
exit 1
fi
exit 0