• Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
ianmenethil

ianmenethil

ZenServer

Unlisted
Like
ZenServer
Home
Code
12
.cursor
docs
8
src
10
tasks
tests
.gitignore
.vtignore
ISSUES.md
README.md
deno.json
H
main.ts
openapi.json
Branches
1
Pull requests
Remixes
History
Environment variables
22
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
ISSUES.md
Code
/
ISSUES.md
Search
7/7/2025
Viewing readonly version of main branch: v204
View latest version
ISSUES.md

ZenServer API Test Issues - Comprehensive Analysis

Date: 2025-07-06
Test Suite: tests/api_endpoints_test.ts
Status: 7 Failed Tests / 22 Total Tests
Analysis Phase: In-Depth Root Cause Investigation
Environment: Using .env.example values

Critical Issues Summary

IssueEndpointExpectedActualSeverityStatus
1GET /api/v1/tour/400404HIGHOpen
2POST /api/v1/create-nonce (missing fields)400403HIGHOpen
3POST /api/v1/create-nonce (happy path)200403CRITICALOpen
4POST /api/v1/payment/exchange-nonce (happy)200401CRITICALOpen
5POST /api/v1/admin/generate-pay-token (missing body)400401MEDIUMOpen
6POST /api/v1/admin/generate-pay-token (happy)200401HIGHOpen
7GET /api/v1/booking/{token} (happy)200TypeErrorHIGHOpen

COMPREHENSIVE ANALYSIS FINDINGS

Environment Configuration Status

✅ Current Environment: Server running with .env.example values
✅ TURNSTILE_SECRET_KEY: Configured (0x4AAAAAABjPDChnTzNbiXh-p50UU_uolno)
❌ ADMIN_API_KEY: Missing from environment (root cause of Issues 5-7)
✅ Payment Credentials: Configured (PAYMENT_USERNAME/PASSWORD)
✅ VALTOWN_TOKEN: Configured

Cookie Format Analysis

Actual Set-Cookie Headers (from curl test):

set-cookie: SESSION=f3e256d...8c5e; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=1800
set-cookie: XSRF-TOKEN=47a3e85b...730f; Path=/; Secure; SameSite=Strict; Max-Age=1800

Test Parsing Logic Issue:

// ❌ INCORRECT parsing in test const cookies = seed.headers.get('set-cookie')!; const xsrf = cookies.split(/; */)[1]; // Gets "Path=/" instead of XSRF-TOKEN const session = cookies.split(/; */)[0]; // Gets full SESSION cookie string

Turnstile Validation Behavior

✅ Service Running: TurnstileService properly configured
✅ Secret Key Present: Environment variable correctly set
❌ Test Token Issue: Using "test_token" instead of valid Turnstile response
📋 Expected: Tests need mock Turnstile tokens or service bypass for testing

Security-First Validation Order Confirmed

Current Flow (CORRECT):

  1. CSRF validation (403 if X-XSRF-TOKEN invalid)
  2. Body parsing (400 if JSON malformed)
  3. Field validation (400 if required fields missing)
  4. Turnstile validation (403 if bot detection fails)
  5. Business logic processing

Test Expectations (INCORRECT):

  • Tests expect field validation errors (400) before security validation (403)
  • This violates security-first principle where authentication/authorization comes before business logic

Issue 1: Tour Endpoint Path Validation - DETAILED ANALYSIS

Test: GET /api/v1/tour/ (missing id) => 400

Problem:

  • Expected: 400 Bad Request for missing tour ID
  • Actual: 404 Not Found

Detailed Root Cause Analysis:

  1. OpenAPI Route Matching: The Hono framework with OpenAPI integration treats /api/v1/tour/ and /api/v1/tour/{id} as completely different routes
  2. Path Resolution: When /api/v1/tour/ is requested, it doesn't match the parameterized route /api/v1/tour/{id}
  3. Framework Behavior: Hono returns 404 for unmatched routes before any handler code executes

Code Analysis - tourHandler.ts:36-49:

// Validation exists but never reached for /api/v1/tour/ if (!tourId || tourId === '' || tourId === 'tour') { logger.warn("Validation failed in getTourDetails", { path: url.pathname, received: { tourId }, errors: ["id: Required"] }); return addCorsHeaders( new Response(JSON.stringify({ error: "Validation failed", details: ["id: Required"] }), { status: 400, headers: { "Content-Type": "application/json" } }), origin ); }

Technical Details:

  • Handler has correct validation logic
  • Validation never executes because route doesn't match
  • Framework-level 404 occurs before application-level validation

Test Verification:

curl -s -w "Status: %{http_code}\n" http://localhost:5000/api/v1/tour/ # Returns: 404 Not Found curl -s -w "Status: %{http_code}\n" http://localhost:5000/api/v1/tour/tour-paris-explorer # Returns: 200 OK (works correctly)

Fix Strategy Options:

  1. Route Addition: Add specific route for /api/v1/tour/ that returns 400
  2. Test Update: Change test expectation from 400 to 404 (framework behavior)
  3. Middleware Solution: Add catch-all middleware to handle missing parameters

Recommendation: Option 2 (Test Update) - Framework 404 is correct behavior for unmatched routes


Issue 2: Create Nonce CSRF Handling Priority - DETAILED ANALYSIS

Test: POST /api/v1/create-nonce missing fields => 400

Problem:

  • Expected: 400 Bad Request for missing required fields
  • Actual: 403 Forbidden due to CSRF validation

Detailed Root Cause Analysis:

Code Flow Analysis - publicPayFlowHandler.ts:27-31:

// 1. CSRF Protection (validates X-XSRF-TOKEN header against SESSION cookie) const csrfError = csrfProtection(request); if (csrfError) { return addCorsHeaders(csrfError, origin); // ← Returns 403 immediately }

Security-First Validation Order (CORRECT BEHAVIOR):

  1. CSRF validation (Line 28) - Prevents cross-site attacks
  2. Body parsing (Line 35) - Only after authentication passes
  3. Field validation (Line 91) - Business logic validation last
  4. Turnstile validation (Line 105) - Bot protection
  5. Business processing - Only after all security checks pass

Test Logic Issue:

// Test sends empty body {} but expects 400 body: JSON.stringify({}) // Missing tourId, cfToken, email_verify // Test expects field validation error (400) assertEquals(res.status, 400); // ❌ WRONG - security comes first // Actual behavior: CSRF fails before field validation // Returns 403: "CSRF validation failed", "details": "Missing X-XSRF-TOKEN header"

CSRF Validation Details - csrf.ts:72-80:

export function validateCSRFToken(request: Request): { valid: boolean; error?: string; sessionId?: string } { const xsrfHeader = request.headers.get('X-XSRF-TOKEN'); const cookieHeader = request.headers.get('Cookie'); if (!xsrfHeader) { return { valid: false, error: 'Missing X-XSRF-TOKEN header' }; // ← Test fails here }

Why This Is Correct Security Design:

  • Defense in Depth: Security validation before business logic prevents attacks
  • Early Rejection: Malicious requests blocked before processing sensitive data
  • Performance: Avoid expensive validation on unauthenticated requests
  • Audit Trail: Security failures logged separately from business validation failures

Test Verification:

# Without CSRF token - gets 403 (correct) curl -X POST http://localhost:5000/api/v1/create-nonce -d '{}' # Response: {"error":"CSRF validation failed","details":"Missing X-XSRF-TOKEN header"} # With CSRF but missing fields - would get 400 (if CSRF passes) curl -X POST http://localhost:5000/api/v1/create-nonce \ -H "X-XSRF-TOKEN: valid_token" -H "Cookie: SESSION=valid_session" -d '{}'

Fix Strategy Options:

  1. Test Update (RECOMMENDED): Change expectation from 400 to 403
  2. Add Pre-CSRF Field Check: Validate fields before CSRF (NOT RECOMMENDED - security risk)
  3. Separate Test: Create test that properly sets up CSRF first

Recommendation: Option 1 - Tests should reflect correct security-first behavior


Issue 3: Create Nonce Happy Path Failure - DETAILED ANALYSIS

Test: POST /api/v1/create-nonce happy => 200 + RateLimit headers

Problem:

  • Expected: 200 OK with nonce and rate limit headers
  • Actual: 403 Forbidden

Detailed Root Cause Analysis:

Cookie Parsing Bug in Test - api_endpoints_test.ts:96-98:

const seed = await fetch(`${BASE_URL}/api/v1/tour/${TOURS[0].tourId}`); const cookies = seed.headers.get('set-cookie')!; const xsrf = cookies.split(/; */)[1]; // ❌ CRITICAL BUG const session = cookies.split(/; */)[0];

Actual Set-Cookie Header Format (from server):

set-cookie: SESSION=f3e256d209f24e2481900c006bb4ceef71b88f282eb6421941fa0278fac28c5e; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=1800 set-cookie: XSRF-TOKEN=47a3e85baa275256c84f83195704dba73f538a403612b5f982d7f9c36962730f; Path=/; Secure; SameSite=Strict; Max-Age=1800

What The Bug Does:

  1. cookies.split(/; */)[0] = "SESSION=f3e256d...8c5e"
  2. cookies.split(/; */)[1] = "Path=/" ← WRONG! Should be XSRF-TOKEN

Actual Headers.get() Behavior (CORRECTION):

// headers.get('set-cookie') returns BOTH cookies comma-separated: "SESSION=be4b6f46...ab870; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=1800, XSRF-TOKEN=53a80779...208838; Path=/; Secure; SameSite=Strict; Max-Age=1800"

Test Parsing Bug Analysis:

const cookies = seed.headers.get('set-cookie')!; // cookies = "SESSION=xxx; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=1800, XSRF-TOKEN=yyy; Path=/; Secure; SameSite=Strict; Max-Age=1800" const xsrf = cookies.split(/; */)[1]; // xsrf = "Path=/" ← WRONG! Should split on comma first const session = cookies.split(/; */)[0]; // session = "SESSION=xxx" ← This part is correct

CSRF Validation Failure:

// Test extracts wrong value 'X-XSRF-TOKEN': xsrf.split('=')[1], // "Path=/" → Results in: "/" // Server expects actual XSRF token // Expected: "53a80779eff69e251c75167d2e9c81040dc4cc6e6d6b1a96e67b75c522208838"

Correct Cookie Parsing Logic (Option 1 - getSetCookie):

// ✅ CORRECT approach - use getSetCookie() method const response = await fetch(url); const allCookies = response.headers.getSetCookie(); // Returns array of individual cookies let sessionValue = ''; let xsrfValue = ''; allCookies.forEach(cookie => { if (cookie.startsWith('SESSION=')) { sessionValue = cookie.split(';')[0]; // "SESSION=actual_value" } else if (cookie.startsWith('XSRF-TOKEN=')) { xsrfValue = cookie.split(';')[0].split('=')[1]; // Extract token value only } });

Correct Cookie Parsing Logic (Option 2 - Manual parsing):

// ✅ CORRECT approach - parse comma-separated cookies properly const cookieHeader = seed.headers.get('set-cookie')!; const individualCookies = cookieHeader.split(', XSRF-TOKEN='); // Split on known separator const sessionCookie = individualCookies[0]; // "SESSION=xxx; Path=/; ..." const xsrfCookie = 'XSRF-TOKEN=' + individualCookies[1]; // "XSRF-TOKEN=yyy; Path=/; ..." const sessionValue = sessionCookie.split(';')[0]; // "SESSION=xxx" const xsrfValue = xsrfCookie.split(';')[0].split('=')[1]; // "yyy"

Corrected Test Code:

// Fix for api_endpoints_test.ts const seed = await fetch(`${BASE_URL}/api/v1/tour/${TOURS[0].tourId}`); const allCookies = seed.headers.getSetCookie(); let sessionCookie = ''; let xsrfToken = ''; allCookies.forEach(cookie => { if (cookie.startsWith('SESSION=')) { sessionCookie = cookie.split(';')[0]; } else if (cookie.startsWith('XSRF-TOKEN=')) { xsrfToken = cookie.split(';')[0].split('=')[1]; } }); // Use in request const res = await fetch(`${BASE_URL}/api/v1/create-nonce`, { method: 'POST', headers: { 'Cookie': sessionCookie + '; XSRF-TOKEN=' + xsrfToken, 'X-XSRF-TOKEN': xsrfToken, // ✅ Now contains actual token 'content-type': 'application/json' }, body: JSON.stringify({ tourId: TOURS[0].tourId, cfToken: 'test_cf', email_verify: '🍯' }) });

Test Verification:

# Check what test is actually sending echo "X-XSRF-TOKEN: /" # vs what server expects echo "X-XSRF-TOKEN: 47a3e85baa275256c84f83195704dba73f538a403612b5f982d7f9c36962730f"

Secondary Issue: Turnstile Validation Even if CSRF passes, test uses cfToken: 'test_cf' which fails Turnstile validation:

// From test body: JSON.stringify({ tourId: TOURS[0].tourId, cfToken: 'test_cf', email_verify: '🍯' }) // Turnstile validation failure {"error":"Bot detection failed","details":"Turnstile challenge failed"}

Complete Fix Requirements:

  1. Fix cookie parsing to handle multiple Set-Cookie headers correctly
  2. Mock Turnstile service or use valid test tokens
  3. Verify CSRF token matching between cookie and header

Recommendation: Implement proper multi-cookie parsing and Turnstile test mocking


Issue 4: Exchange Nonce Authorization Failure

Test: POST /api/v1/payment/exchange-nonce happy => 200 + fingerprint

Problem:

  • Expected: 200 OK with payment fingerprint
  • Actual: 401 Unauthorized

Root Cause: Test depends on nonce from Issue 3. Since create-nonce is failing (Issue 3), no valid nonce is available for exchange-nonce test.

Dependency Chain:

  1. create-nonce test fails → no valid nonce stored
  2. exchange-nonce test uses invalid/undefined nonce → 401 Unauthorized

Fix Required: Resolve Issue 3 first, then retest this endpoint.


Issue 5: Admin API Key Missing - DETAILED ANALYSIS

Test: POST /api/v1/admin/generate-pay-token missing body => 400

Problem:

  • Expected: 400 Bad Request for missing request body
  • Actual: 401 Unauthorized

Detailed Root Cause Analysis:

Environment Configuration Issue:

# Current .env.example (ADMIN_API_KEY missing) PAYMENT_USERNAME=your_payment_username PAYMENT_PASSWORD=your_payment_password VALTOWN_TOKEN=your_val_town_api_token TURNSTILE_SECRET_KEY=your_cloudflare_turnstile_secret # ADMIN_API_KEY=??? ← NOT PRESENT

Code Analysis - adminHandler.ts:38-51:

const providedKey = authHeader.substring(7); // Remove "Bearer " const expectedKey = Deno.env.get("ADMIN_API_KEY"); // ← Returns undefined if (!expectedKey || providedKey !== expectedKey) { // ← !undefined = true logger.warn("Invalid admin API key", { ip: request.headers.get("cf-connecting-ip") || "unknown" }); return addCorsHeaders( new Response(JSON.stringify({ error: "Invalid API key" }), { status: 401, // ← Always returns 401 when ADMIN_API_KEY undefined headers: { "Content-Type": "application/json" } }), origin ); }

Test Logic vs Implementation:

// Test sends valid Authorization header format headers: { 'Authorization': `Bearer ${ADMIN_API_KEY}` } // But ADMIN_API_KEY from test environment is empty string "" // Code comparison fails: providedKey = "" // From test environment expectedKey = undefined // From missing env var // Result: 401 Unauthorized (correct behavior)

Security-First Validation Order (CORRECT):

  1. Authentication check (API key validation) - Line 38-51
  2. Body parsing (JSON validation) - Line 54-65
  3. Field validation (bookingId required) - Line 69-77
  4. Business logic (booking exists check) - Line 80-94

Environment Verification:

# Test what environment variables are available echo $ADMIN_API_KEY # Returns empty deno eval "console.log(Deno.env.get('ADMIN_API_KEY'))" # Returns undefined

Fix Requirements:

  1. Add to .env.example: ADMIN_API_KEY=admin_test_key_123
  2. Update test environment: Ensure ADMIN_API_KEY is loaded
  3. Test configuration: Use consistent key value across test and server

Recommendation: Add ADMIN_API_KEY to environment configuration - this is proper security behavior


Issue 6: Admin Generate Pay Token Happy Path

Test: POST /api/v1/admin/generate-pay-token happy => 200 + shareableUrl

Problem:

  • Expected: 200 OK with shareable URL
  • Actual: 401 Unauthorized

Root Cause: Same as Issue 5 - missing ADMIN_API_KEY environment variable.

Fix Required: Add ADMIN_API_KEY to environment configuration.


Issue 7: Booking Token Test Dependency

Test: GET /api/v1/booking/{token} happy => 200 + cookies

Problem:

  • Expected: 200 OK with SESSION and XSRF-TOKEN cookies
  • Actual: TypeError: Invalid URL: 'undefined'

Root Cause: Test depends on Issue 6 success. The tokenUrl is undefined because generate-pay-token test failed:

// Line 217 in test const url = new URL((t as any).tokenUrl); // tokenUrl is undefined

Dependency Chain:

  1. generate-pay-token test fails → no tokenUrl stored
  2. booking token test tries to parse undefined URL → TypeError

Fix Required: Resolve Issue 6 first, then retest this endpoint.


Issue Resolution Priority

CRITICAL (Blocks Core Payment Flow)

  1. Issue 3: Fix create-nonce CSRF token parsing
  2. Issue 4: Fix exchange-nonce (dependency on Issue 3)

HIGH (Important API Functionality)

  1. Issue 5 & 6: Add ADMIN_API_KEY to environment
  2. Issue 7: Fix booking token test (dependency on Issue 6)
  3. Issue 1: Add tour path validation

MEDIUM (Test Accuracy)

  1. Issue 2: Update test expectations for security-first validation

Environment Configuration Fix

Required Addition to .env.dev:

ADMIN_API_KEY=your_admin_key_here

Example:

PAYMENT_USERNAME=X PAYMENT_PASSWORD=X VALTOWN_TOKEN=vtwn_3MCBkFj8vFx5xgLymxZS3mnQFhfU TURNSTILE_SECRET_KEY=0x4AAAAAABjPDChnTzNbiXh-p50UU_uolno ADMIN_API_KEY=admin_test_key_123 DEBUG_MODE=true

Test Suite Improvements Needed

1. Cookie Parsing Utility

Create a helper function for proper Set-Cookie header parsing:

function parseCookies(setCookieHeader: string): Record<string, string> { const cookies: Record<string, string> = {}; setCookieHeader.split(', ').forEach(cookie => { const [nameValue] = cookie.split(';'); const [name, value] = nameValue.split('='); cookies[name.trim()] = value.trim(); }); return cookies; }

2. Test Dependencies

Break test dependencies by making each test self-contained or using proper test setup/teardown.

3. Environment Validation

Add environment validation at test startup to ensure all required variables are present.


Security Validation Behavior (Expected)

The current 403 responses for Issues 2 and 3 are actually correct security behavior:

  1. CSRF validation first - Prevents CSRF attacks before processing business logic
  2. Authentication before authorization - API key validation before body parsing
  3. Security-first approach - Block malicious requests early in the pipeline

Recommendation: Update test expectations to match security-first validation order rather than changing the security behavior.


COMPREHENSIVE FIX STRATEGY

Phase 1: Environment Configuration (CRITICAL)

  1. Add ADMIN_API_KEY to .env.example
    ADMIN_API_KEY=admin_test_key_123
  2. Update test environment to load all required variables
  3. Verify environment loading in test setup

Phase 2: Test Suite Fixes (HIGH PRIORITY)

  1. Fix cookie parsing logic using response.headers.getSetCookie()
  2. Add Turnstile test mocking or bypass for testing
  3. Update test expectations to match security-first validation:
    • Issue 1: 404 → 400 (change test expectation)
    • Issue 2: 400 → 403 (change test expectation)

Phase 3: Test Infrastructure (MEDIUM PRIORITY)

  1. Create cookie parsing utility for reusable test code
  2. Add test environment validation to ensure all vars present
  3. Implement proper test dependencies to avoid cascade failures

Implementation Order (NO CODE CHANGES - ANALYSIS ONLY)

  1. FIRST: Environment configuration (Issues 5, 6, 7)
  2. SECOND: Cookie parsing fix (Issues 3, 4)
  3. THIRD: Test expectation updates (Issues 1, 2)
  4. FOURTH: Turnstile mocking implementation

Validation Strategy

  1. Environment test: Verify all required variables present
  2. Cookie test: Verify CSRF tokens extracted correctly
  3. Integration test: Full payment flow with proper setup
  4. Admin test: All admin endpoints with valid API key

ANALYSIS COMPLETE - READY FOR SYSTEMATIC RESOLUTION

FeaturesVersion controlCode intelligenceCLIMCP
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
© 2026 Val Town, Inc.