#!/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:-})" 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:-})" 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:-})" fi echo "" echo "=========================================" echo " RESULTS: $PASS passed, $FAIL failed" echo "=========================================" if [ "$FAIL" -gt 0 ]; then exit 1 fi exit 0