Improve e2e tests with OAuth flow validation

Add state parameter round-trip validation to prevent CSRF attacks.
Document each OAuth step including PKCE generation and token exchange.
Kill existing servers before starting new ones to avoid conflicts.
This commit is contained in:
Basil Hosmer 2025-09-15 02:11:23 -04:00
commit 4a081bc816
No known key found for this signature in database
2 changed files with 111 additions and 30 deletions

View file

@ -5,6 +5,12 @@ echo "=================================================="
echo "End-to-End Test - Integrated Mode"
echo "=================================================="
# Kill any existing servers
echo "🛑 Cleaning up existing servers..."
pkill -f "node.*dist/src/index" || true
pkill -f "tsx watch.*src/index" || true
sleep 2
# Use environment variables if available, otherwise defaults
MCP_SERVER="${BASE_URI:-http://localhost:3232}"
USER_ID="e2e-test-$(date +%s)"
@ -33,34 +39,68 @@ if [ "${AUTH_MODE:-integrated}" != "integrated" ]; then
echo " Or use: ./scripts/test-separate-e2e.sh"
fi
# Start MCP server in integrated mode
echo "🚀 Starting MCP server in integrated mode..."
AUTH_MODE=integrated npm start &
MCP_PID=$!
sleep 5
# Check MCP server
if ! curl -s -f "$MCP_SERVER/" > /dev/null; then
echo "❌ MCP server not running at $MCP_SERVER"
echo " Required setup:"
echo " 1. Start Redis: docker compose up -d"
echo " 2. Start MCP server: npm run dev:integrated"
echo " 3. Or set up environment:"
echo " cp .env.integrated .env && npm run dev"
echo "❌ MCP server failed to start at $MCP_SERVER"
kill $MCP_PID 2>/dev/null || true
exit 1
fi
echo "✅ MCP server is running"
echo "✅ MCP server is running (PID: $MCP_PID)"
# Clean up on exit
trap "kill $MCP_PID 2>/dev/null || true" EXIT
echo "🔐 PHASE 1: OAuth Authentication"
echo "================================"
# OAuth flow (abbreviated for clarity)
# OAuth Step 1: Client Registration
# Register a new OAuth client application with the authorization server
# This would typically be done once during app setup, not for each user
CLIENT_RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" -d '{"client_name":"e2e-fixed","redirect_uris":["http://localhost:3000/callback"]}' "$MCP_SERVER/register")
CLIENT_ID=$(echo "$CLIENT_RESPONSE" | jq -r .client_id)
CLIENT_SECRET=$(echo "$CLIENT_RESPONSE" | jq -r .client_secret)
# OAuth Step 2: Generate PKCE (Proof Key for Code Exchange) parameters
# PKCE adds security to the OAuth flow by preventing authorization code interception attacks
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-43)
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -binary -sha256 | base64 | tr "+/" "-_" | tr -d "=")
AUTH_PAGE=$(curl -s "$MCP_SERVER/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=http://localhost:3000/callback&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256&state=e2e-state")
# OAuth Step 3: Authorization Request
# Direct the user to the authorization server's /authorize endpoint
# Include state parameter for CSRF protection
STATE_PARAM="e2e-state-$(date +%s)"
AUTH_PAGE=$(curl -s "$MCP_SERVER/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=http://localhost:3000/callback&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256&state=$STATE_PARAM")
# Extract the authorization code from the HTML response (normally would be in redirect URL)
AUTH_CODE=$(echo "$AUTH_PAGE" | grep -o 'state=[^"&]*' | cut -d= -f2)
curl -s "$MCP_SERVER/fakeupstreamauth/callback?state=$AUTH_CODE&code=fakecode&userId=$USER_ID" > /dev/null
# OAuth Step 4: User Authentication & Authorization
# In a real flow, the user would authenticate with the auth server here
# For testing, we simulate this with the fake upstream auth endpoint
CALLBACK_RESPONSE=$(curl -s -i "$MCP_SERVER/fakeupstreamauth/callback?state=$AUTH_CODE&code=fakecode&userId=$USER_ID")
# OAuth Step 5: Authorization Code Redirect
# Verify the auth server redirects back to our redirect_uri with the code and state
# The state parameter MUST match what we sent to prevent CSRF attacks
LOCATION_HEADER=$(echo "$CALLBACK_RESPONSE" | grep -i "^location:" | tr -d '\r')
if echo "$LOCATION_HEADER" | grep -q "state=$STATE_PARAM"; then
echo "✅ State parameter verified in callback"
else
echo "❌ State parameter mismatch or missing in callback"
echo " Expected state: $STATE_PARAM"
echo " Location header: $LOCATION_HEADER"
exit 1
fi
# OAuth Step 6: Token Exchange
# Exchange the authorization code for access and refresh tokens
# Include the PKCE code_verifier to prove we initiated the flow
TOKEN_RESPONSE=$(curl -s -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=authorization_code&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&code=$AUTH_CODE&redirect_uri=http://localhost:3000/callback&code_verifier=$CODE_VERIFIER" "$MCP_SERVER/token")
ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r .access_token)

View file

@ -8,6 +8,14 @@ echo "This script tests the complete OAuth flow and MCP features"
echo "using separate auth server and MCP server."
echo ""
# Kill any existing servers
echo "🛑 Cleaning up existing servers..."
pkill -f "node.*dist/src/index" || true
pkill -f "node.*dist/auth-server/index" || true
pkill -f "tsx watch.*src/index" || true
pkill -f "tsx watch.*auth-server/index" || true
sleep 2
# Use environment variables if available, otherwise defaults
AUTH_SERVER="${AUTH_SERVER_URL:-http://localhost:3001}"
MCP_SERVER="${BASE_URI:-http://localhost:3232}"
@ -38,34 +46,45 @@ if [ "${AUTH_MODE}" = "integrated" ]; then
echo " Or use: ./scripts/test-integrated-e2e-fixed.sh"
fi
# Start auth server
echo "🚀 Starting auth server..."
npm run start:auth-server &
AUTH_PID=$!
sleep 5
# Check auth server
if ! curl -s -f "$AUTH_SERVER/health" > /dev/null; then
echo "❌ Auth server not running at $AUTH_SERVER"
echo " Required setup:"
echo " 1. Start Redis: docker compose up -d"
echo " 2. Start both servers: npm run dev:with-separate-auth"
echo " 3. Or start separately:"
echo " Terminal 1: npm run dev:auth-server"
echo " Terminal 2: AUTH_MODE=separate npm run dev"
echo " 4. Or set up environment:"
echo " cp .env.separate .env && npm run dev:with-separate-auth"
echo "❌ Auth server failed to start at $AUTH_SERVER"
kill $AUTH_PID 2>/dev/null || true
exit 1
fi
echo "✅ Auth server is running"
echo "✅ Auth server is running (PID: $AUTH_PID)"
# Start MCP server in separate mode
echo "🚀 Starting MCP server in separate mode..."
AUTH_MODE=separate npm start &
MCP_PID=$!
sleep 5
# Check MCP server
if ! curl -s -f "$MCP_SERVER/" > /dev/null; then
echo "❌ MCP server not running at $MCP_SERVER"
echo " See auth server setup instructions above"
echo "❌ MCP server failed to start at $MCP_SERVER"
kill $AUTH_PID 2>/dev/null || true
kill $MCP_PID 2>/dev/null || true
exit 1
fi
echo "✅ MCP server is running"
echo "✅ MCP server is running (PID: $MCP_PID)"
# Clean up on exit
trap "kill $AUTH_PID $MCP_PID 2>/dev/null || true" EXIT
echo ""
echo "🔐 PHASE 1: OAuth Authentication (with Auth Server)"
echo "================================================="
# Step 1: Register OAuth client with AUTH SERVER
# OAuth Step 1: Client Registration
# Register a new OAuth client application with the authorization server
# This would typically be done once during app setup, not for each user
echo "📝 Step 1: Register OAuth client with auth server"
CLIENT_RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" \
-d "{\"client_name\":\"e2e-separate-client\",\"redirect_uris\":[\"http://localhost:3000/callback\"]}" \
@ -75,19 +94,24 @@ CLIENT_ID=$(echo "$CLIENT_RESPONSE" | jq -r .client_id)
CLIENT_SECRET=$(echo "$CLIENT_RESPONSE" | jq -r .client_secret)
echo " Client ID: $CLIENT_ID"
# Step 2: Generate PKCE
# OAuth Step 2: Generate PKCE (Proof Key for Code Exchange) parameters
# PKCE adds security to the OAuth flow by preventing authorization code interception attacks
echo ""
echo "🔐 Step 2: Generate PKCE challenge"
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-43)
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -binary -sha256 | base64 | tr "+/" "-_" | tr -d "=")
echo " Code verifier generated"
# Step 3: Get authorization code from AUTH SERVER
# OAuth Step 3: Authorization Request
# Direct the user to the authorization server's /authorize endpoint
# Include state parameter for CSRF protection
echo ""
echo "🎫 Step 3: Get authorization code from auth server"
AUTH_URL="$AUTH_SERVER/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=http://localhost:3000/callback&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256&state=separate-test"
STATE_PARAM="separate-test-$(date +%s)"
AUTH_URL="$AUTH_SERVER/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=http://localhost:3000/callback&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256&state=$STATE_PARAM"
AUTH_PAGE=$(curl -s "$AUTH_URL")
# Extract the authorization code from the HTML response (normally would be in redirect URL)
AUTH_CODE=$(echo "$AUTH_PAGE" | grep -o 'state=[^"&]*' | cut -d= -f2 | head -1)
if [ -z "$AUTH_CODE" ]; then
@ -96,14 +120,31 @@ if [ -z "$AUTH_CODE" ]; then
fi
echo " Auth Code: ${AUTH_CODE:0:20}..."
# Step 4: Complete fake upstream auth with AUTH SERVER
# OAuth Step 4: User Authentication & Authorization
# In a real flow, the user would authenticate with the auth server here
# For testing, we simulate this with the fake upstream auth endpoint
echo ""
echo "🔄 Step 4: Complete fake upstream auth with auth server"
CALLBACK_URL="$AUTH_SERVER/fakeupstreamauth/callback?state=$AUTH_CODE&code=fakecode&userId=$USER_ID"
curl -s -L "$CALLBACK_URL" > /dev/null
CALLBACK_RESPONSE=$(curl -s -i "$CALLBACK_URL")
# OAuth Step 5: Authorization Code Redirect
# Verify the auth server redirects back to our redirect_uri with the code and state
# The state parameter MUST match what we sent to prevent CSRF attacks
LOCATION_HEADER=$(echo "$CALLBACK_RESPONSE" | grep -i "^location:" | tr -d '\r')
if echo "$LOCATION_HEADER" | grep -q "state=$STATE_PARAM"; then
echo " ✅ State parameter verified in callback"
else
echo " ❌ State parameter mismatch or missing in callback"
echo " Expected state: $STATE_PARAM"
echo " Location header: $LOCATION_HEADER"
exit 1
fi
echo " Fake upstream auth completed"
# Step 5: Exchange for tokens with AUTH SERVER
# OAuth Step 6: Token Exchange
# Exchange the authorization code for access and refresh tokens
# Include the PKCE code_verifier to prove we initiated the flow
echo ""
echo "🎟️ Step 5: Exchange code for access token with auth server"
TOKEN_RESPONSE=$(curl -s -X POST -H "Content-Type: application/x-www-form-urlencoded" \