# Quickstart: OIDC Proxy Script Authentication **Feature**: 001-oidc-proxy-script **Date**: 2025-07-17 --- ## Prerequisites - Node.js 18.0.0 or later (`node --version`) - Access to a KME OIDC token service endpoint - Project dependencies installed (`npm install`) --- ## Step 1 — Create `adapter_settings.json` ```bash cd /path/to/kme-content-adapter cp src/globalVariables/adapter_settings.json.example \ src/globalVariables/adapter_settings.json ``` Edit `src/globalVariables/adapter_settings.json` with your real credentials: ```json { "tokenUrl": "https://auth.kme.example.com/protocol/openid-connect/token", "username": "your-service-account@example.com", "password": "your-password", "clientId": "your-client-id", "scope": "openid tags content_entitlements" } ``` > **Security**: `adapter_settings.json` is in `.gitignore`. Never commit credentials. > The `.example` file (with placeholder values) IS committed and serves as the template. --- ## Step 2 — Start the Adapter ```bash npm start # or for development with auto-reload: npm run dev ``` Expected startup log (structured JSON): ```json {"message": "Loaded global data: adapter_settings", "keys": ["tokenUrl","username","password","clientId","scope"]} {"message": "Loaded 1 global variables", "json": 1, "js": 0} {"message": "Configuration loaded", "port": 3000, "host": "0.0.0.0", ...} {"message": "Configuration validated successfully"} {"message": "Server listening", "port": 3000, "host": "0.0.0.0"} ``` --- ## Step 3 — Send a Test Request ```bash curl -v http://localhost:3000/ProxyScript/run/67bca862210071627d32ef12/current/kmeAdapter ``` **Expected response on first request** (token fetched from OIDC service): ``` HTTP/1.1 200 OK Content-Type: text/plain Authorized ``` **Expected response on subsequent requests** (token served from cache): ``` HTTP/1.1 200 OK Content-Type: text/plain Authorized ``` No visible difference from the caller's perspective; the adapter log will show no new axios call for the second request (cache hit). **Expected response with invalid credentials**: ``` HTTP/1.1 401 Unauthorized Content-Type: text/plain Unauthorized: HTTP 401 ``` --- ## Step 4 — Run Tests ### Unit tests (fast, no network, no server required) ```bash npm run test:unit # runs: node --test tests/unit/proxy.test.js ``` Exercises: cache hit, cache miss, token expiry, stampede prevention, timeout handling, missing `id_token`, missing required fields, HTTP error from token service. ### Contract tests (starts real HTTP server with mock token endpoint) ```bash npm run test:contract # runs: node --test tests/contract/proxy-http.test.js ``` Exercises: end-to-end `200 OK / Authorized`, end-to-end `401 Unauthorized`, verifies response headers and exact body strings. ### All tests ```bash npm test # runs: node --test tests/**/*.test.js ``` --- ## Architecture Summary ``` Inbound HTTP request │ ▼ server.js (http.createServer) │ creates fresh vm.createContext({ │ ...globalVMContext, ← axios, URLSearchParams, console, ... │ ...globalVariableContext, ← adapter_settings (from JSON) │ req, res ← fresh per request │ }) │ ▼ proxy.js (vm.Script, compiled once) │ ├─ reads adapter_settings._cache │ ├─ CACHE HIT: token valid → 200 OK / Authorized │ └─ CACHE MISS / EXPIRED: │ ├─ FETCHING (stampede guard): queue on _cache.pendingFetch → 200/401 │ └─ NEW FETCH: │ POST tokenUrl (timeout: 5s) │ ├─ SUCCESS: cache token + expiry → 200 OK / Authorized │ └─ FAILURE: → 401 Unauthorized: ▼ HTTP response to caller ``` --- ## Key Files | File | Status | Purpose | |------|--------|---------| | `src/globalVariables/adapter_settings.json` | **Create** | OIDC credentials (gitignored) | | `src/globalVariables/adapter_settings.json.example` | **Create** | Template with placeholder values | | `src/proxyScripts/proxy.js` | **Create** | OIDC authentication proxy (VM sandbox) | | `tests/unit/proxy.test.js` | **Create** | Unit tests (no network, no server) | | `tests/contract/proxy-http.test.js` | **Create** | HTTP response contract tests | | `src/server.js` | **No change** | Existing infrastructure; auto-loads adapter_settings.json | | `config/default.json` | **No change** | Infrastructure settings only (port, host, log level) | --- ## Token Lifecycle | Phase | What happens | |-------|-------------| | **First request** | `_cache.token` is null → fresh token fetch → cache `id_token` + `expires_in` | | **Subsequent requests (valid token)** | `Date.now()/1000 < _cache.expiry` → return `Authorized` immediately, no network call | | **After token expiry** | `Date.now()/1000 ≥ _cache.expiry` → fresh token fetch (transparent to caller) | | **Concurrent requests during fetch** | All requests `await` the shared `_cache.pendingFetch` promise; only ONE HTTP call made | | **Auth failure** | Clear `_cache.token` and `_cache.expiry` → respond `401 Unauthorized: ` | | **Timeout (5 s)** | axios `ECONNABORTED` error → treated as auth failure → `401 Unauthorized` | --- ## Troubleshooting | Symptom | Likely cause | Fix | |---------|-------------|-----| | `401 Unauthorized: HTTP 401` | Wrong credentials | Check `username`, `password`, `clientId` in `adapter_settings.json` | | `401 Unauthorized: connect ECONNREFUSED` | Token service unreachable | Check `tokenUrl` is correct and reachable | | `401 Unauthorized: token service timeout` | Network slow or token service down | Verify connectivity; check token service health | | `401 Unauthorized: id_token missing from response` | Token service returns `access_token` only | Ensure `openid` is in `scope` and the service issues `id_token` | | Server fails to start with `adapter_settings` not found | JSON file missing | Run Step 1 above | | `SyntaxError` in proxy.js at startup | `import` or `export` statement in proxy.js | Remove all `import`/`export` — proxy.js must be pure VM sandbox code |