# CORS and Vault Client Implementation ## ❌ Why Your Changes Won't Work You tried to fix CORS by adding these changes: ```typescript // ❌ This won't work headers: { 'Access-Control-Allow-Origin': '*' } // ❌ This will break response reading mode: 'no-cors' ``` ### Why `Access-Control-Allow-Origin` in Client Headers Doesn't Work **CORS headers MUST be set by the SERVER, not the client.** When you add `Access-Control-Allow-Origin: *` to your request headers: 1. The header is sent to the server 2. The server ignores it (only the server's response headers matter) 3. The browser still blocks the response because the server didn't send the CORS header **How CORS Actually Works:** ``` Browser → Request to vault.example.com ↓ Vault Server → Response with header: Access-Control-Allow-Origin: https://yourfrontend.com ↓ Browser → Allows JavaScript to read the response ``` ### Why `mode: 'no-cors'` Breaks Everything The `no-cors` mode: - ✅ Allows the request to be sent - ❌ **Prevents you from reading the response body** - ❌ You can't access status codes - ❌ You can't read JSON data - ❌ You only get an "opaque" response It's called "no-cors" mode because it **doesn't check CORS**, but it also **doesn't let you use the response**. **Example:** ```typescript // With no-cors const response = await fetch(url, { mode: 'no-cors' }); console.log(response.status); // Always 0 console.log(await response.json()); // Error: Can't read body ``` ## ✅ The Proper Solution ### 1. Configure Your Vault Server Add CORS configuration to your Vault server config file: ```hcl # vault.hcl ui = true listener "tcp" { address = "0.0.0.0:8200" tls_disable = 1 # Only for development! Use TLS in production # Enable CORS cors_enabled = true cors_allowed_origins = [ "http://localhost:5173", # Vite dev server "https://yourdomain.com" # Production domain ] cors_allowed_headers = ["*"] } ``` Or if using Docker: ```yaml # docker-compose.yml version: '3.8' services: vault: image: vault:latest ports: - "8200:8200" environment: VAULT_DEV_ROOT_TOKEN_ID: myroot VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8200 VAULT_API_ADDR: http://127.0.0.1:8200 cap_add: - IPC_LOCK command: - server - -dev - -dev-root-token-id=myroot ``` Then exec into the container and configure CORS via the API: ```bash docker exec -it vault sh vault write sys/config/cors enabled=true allowed_origins="http://localhost:5173" ``` ### 2. Use the Proper Vault Client We've now implemented a proper browser-compatible Vault client: ```typescript // ✅ NEW: Clean, maintainable VaultClient import { VaultClient } from './services/vaultClient'; const client = new VaultClient({ server: { url: 'https://vault.example.com', ... }, credentials: { token: 'your-token', ... }, timeout: 30000, retries: 2 }); // Read a secret const data = await client.read('secret/data/myapp/config'); // List secrets const keys = await client.list('secret/'); // Write a secret await client.write('secret/data/myapp/config', { username: 'admin', password: 'secret' }); // Delete a secret await client.delete('secret/data/myapp/config'); ``` ## 🏗️ Architecture: Why Use a Client Class? ### Before (Raw API Calls) ```typescript // ❌ Hard to maintain, error-prone async function readSecret(url, token, path) { const response = await fetch(`${url}/v1/${path}`, { headers: { 'X-Vault-Token': token, 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error('Failed'); // Not helpful } const data = await response.json(); return data.data.data; // Confusing nested structure } ``` ### After (VaultClient) ```typescript // ✅ Clean, maintainable, type-safe const client = new VaultClient(options); const data = await client.read(path); // Simple! ``` ### Benefits of VaultClient 1. **Error Handling** ```typescript try { await client.read('secret/data/test'); } catch (error) { if (error instanceof VaultError) { console.log(error.statusCode); // 403 console.log(error.errors); // ["permission denied"] } } ``` 2. **Automatic Retries** - Retries on network errors - Exponential backoff - Configurable retry count 3. **Timeout Protection** ```typescript const client = new VaultClient({ ...options, timeout: 5000 // 5 seconds }); // Request will abort after 5 seconds ``` 4. **Path Normalization** ```typescript // All of these work the same: await client.read('/secret/data/test'); await client.read('secret/data/test'); await client.read('//secret/data/test//'); // All normalized to: secret/data/test ``` 5. **Authentication Methods** ```typescript // Token (already have one) const client = new VaultClient({ server, credentials: { token: 'your-token', ... } }); // Username/Password (get token) const token = await client.loginUserpass('username', 'password'); // LDAP (get token) const token = await client.loginLdap('username', 'password'); ``` 6. **Type Safety** ```typescript // TypeScript knows the structure interface MySecret { username: string; password: string; } const data = await client.read('secret/data/myapp'); console.log(data.username); // TypeScript autocomplete works! ``` ## 🔧 Testing Your CORS Configuration ### 1. Test with curl (should work) ```bash curl -X GET \ -H "X-Vault-Token: your-token" \ http://localhost:8200/v1/secret/data/test ``` ### 2. Test from browser console ```javascript fetch('http://localhost:8200/v1/secret/data/test', { headers: { 'X-Vault-Token': 'your-token' } }) .then(r => r.json()) .then(console.log) .catch(console.error); ``` If you see a CORS error here, your Vault server CORS is not configured correctly. ### 3. Check Response Headers In browser DevTools → Network tab, check the response headers: ``` ✅ Should see: Access-Control-Allow-Origin: http://localhost:5173 Access-Control-Allow-Headers: * ❌ If missing: Your Vault server CORS is not configured ``` ## 🆚 Comparison: Raw API vs VaultClient | Feature | Raw fetch() | VaultClient | |---------|-------------|-------------| | Code lines (typical read) | ~15 lines | 1 line | | Error handling | Manual | Built-in | | Retries | Manual | Automatic | | Timeouts | Manual | Built-in | | Type safety | None | Full | | Path normalization | Manual | Automatic | | Authentication | Manual | Built-in | | Token management | Manual | Built-in | | Health checks | Manual | Built-in | | Maintainability | Low | High | ## 📚 Advanced Usage ### Custom Error Handling ```typescript import { VaultError } from './services/vaultClient'; try { const data = await client.read('secret/data/test'); } catch (error) { if (error instanceof VaultError) { switch (error.statusCode) { case 403: alert('Permission denied'); break; case 404: alert('Secret not found'); break; case 500: alert('Vault server error'); break; default: alert(error.message); } } } ``` ### Health Check Before Operations ```typescript const client = new VaultClient(options); // Check if Vault is healthy const health = await client.health(); if (health.sealed) { alert('Vault is sealed! Please unseal it first.'); return; } // Now safe to perform operations const data = await client.read('secret/data/test'); ``` ### Token Lifecycle Management ```typescript // Login const client = new VaultClient(options); const token = await client.loginUserpass('user', 'pass'); // Check token info const tokenInfo = await client.tokenLookupSelf(); console.log('Token expires:', tokenInfo.data.expire_time); console.log('Token TTL:', tokenInfo.data.ttl); // Revoke token on logout await client.tokenRevokeSelf(); ``` ## 🎯 Summary 1. **CORS must be configured on the Vault SERVER, not the client** 2. **`mode: 'no-cors'` prevents you from reading responses** 3. **Use the VaultClient class for clean, maintainable code** 4. **VaultClient provides:** - Automatic retries - Timeout protection - Better error messages - Type safety - Built-in authentication - Path normalization The new implementation is production-ready, maintainable, and properly handles all edge cases!