109 lines
6.1 KiB
Markdown
109 lines
6.1 KiB
Markdown
# Implementation Plan: OIDC Proxy Script Authentication
|
|
|
|
**Branch**: `001-oidc-proxy-script` | **Date**: 2025-07-17 | **Spec**: [spec.md](spec.md)
|
|
**Input**: Feature specification from `specs/001-oidc-proxy-script/spec.md`
|
|
|
|
## Summary
|
|
|
|
Create `src/globalVariables/adapter_settings.json` with OIDC credentials and implement
|
|
`src/proxyScripts/proxy.js` — a zero-import/export Node.js VM-sandbox script that:
|
|
|
|
- Reads OIDC credentials exclusively from the injected `adapter_settings` context variable (FR-001)
|
|
- POSTs to the configured `tokenUrl` with `application/x-www-form-urlencoded` body and a 5-second
|
|
timeout (FR-003, FR-014)
|
|
- Extracts the bearer token from the `id_token` field of the JSON response (FR-004)
|
|
- Caches the token in **Redis** (`redis.hSet('authorization', 'token', ...)` / `redis.hSet('authorization', 'expiry', ...)`) using `expires_in` as an **absolute Unix epoch timestamp** (FR-005, FR-006)
|
|
- Queues concurrent callers on a shared promise to prevent token-fetch stampedes (FR-013)
|
|
- Returns `200 OK / Authorized` on success and `401 Unauthorized` with a descriptive message on any
|
|
failure (FR-007, FR-008)
|
|
|
|
No modifications to `server.js` are required. The existing `loadGlobalVariables()` pattern
|
|
automatically picks up `adapter_settings.json` and injects it as `adapter_settings` into every VM
|
|
context.
|
|
|
|
## Technical Context
|
|
|
|
**Language/Version**: Node.js 18+ (ES Modules; `"type": "module"` in package.json)
|
|
**Primary Dependencies**: `axios` ^1.13.6, `uuid` ^13.0.0, `jsonwebtoken` ^9.0.3,
|
|
`xmlbuilder2` ^4.0.3 — all already present in `package.json`; no new packages required
|
|
**Storage**: Redis — token and expiry stored in hash key `authorization` via `redis.hSet`/`hGet`; `redis` is injected into the VM context as a global. An in-process `adapter_settings._pendingFetch` guards against stampede (Promises cannot be serialised to Redis).
|
|
**Testing**: Node.js built-in `node:test` runner (`node --test tests/**/*.test.js`);
|
|
`t.mock.fn()`, `t.mock.timers` for fakes; no external test framework needed
|
|
**Target Platform**: Linux/macOS, long-running Node.js 18+ server process
|
|
**Project Type**: HTTP proxy adapter — VM-sandboxed script (IVA Studio proxy script pattern)
|
|
**Performance Goals**: Every request responds within 5 s (SC-001); zero token-service round-trips
|
|
when a valid cached token exists (SC-002)
|
|
**Constraints**: `proxy.js` MUST have zero `import`/`export` statements; MUST NOT reference
|
|
`config`, `global.config`, or `process.env`; all dependencies injected via `vm.createContext()`
|
|
**Scale/Scope**: Single-process, single-tenant; one OIDC token shared across all concurrent
|
|
requests
|
|
|
|
## Constitution Check
|
|
|
|
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
|
|
|
| Principle | Status | Notes |
|
|
|-----------|--------|-------|
|
|
| **I. Monolithic Architecture** | ✅ PASS | All auth + cache logic in `proxy.js`; no helper extraction |
|
|
| **I. Zero Imports/Exports** | ✅ PASS | `proxy.js` uses only VM-injected globals; zero `import`/`export` |
|
|
| **I.0 Forbidden Globals** | ✅ PASS | No `config`, `global.config`, or `process.env` in `proxy.js` |
|
|
| **I.I What MUST be in proxy.js** | ✅ PASS | Authentication, token cache, stampede queue all in `proxy.js` |
|
|
| **I.II Allowed Separate Files** | ✅ PASS | Only adding `adapter_settings.json` to `src/globalVariables/` |
|
|
| **I.IV Configuration** | ✅ PASS | Credentials in `src/globalVariables/adapter_settings.json`, not `config/default.json` |
|
|
| **I.V VM Context Injection** | ✅ PASS | `adapter_settings` auto-loaded by existing `loadGlobalVariables()` — no server.js changes |
|
|
| **II. API-First Design** | ✅ PASS | HTTP response contract and VM context contract documented before implementation |
|
|
| **III. Test-First Development** | ✅ PASS | Test scenarios defined; tests written before implementation code |
|
|
|
|
**No violations. Complexity Tracking not required.**
|
|
|
|
### Post-Design Re-check
|
|
|
|
| Principle | Status | Notes |
|
|
|-----------|--------|-------|
|
|
| **Cache state isolation** | ✅ PASS | Token/expiry stored in Redis hash `authorization`; in-process `adapter_settings._pendingFetch` holds stampede guard (Promise not serialisable to Redis) |
|
|
| **Cross-realm Promise** | ✅ PASS | Stampede guard uses duck-type check (`typeof .then === 'function'`) rather than `instanceof Promise`; `await` uses the Promises/A+ thenable protocol which is cross-realm safe |
|
|
| **Async error containment** | ✅ PASS | `script.runInContext()` is not awaited by server.js; proxy.js top-level async IIFE catches all errors internally and always sends a response |
|
|
|
|
## Project Structure
|
|
|
|
### Documentation (this feature)
|
|
|
|
```text
|
|
specs/001-oidc-proxy-script/
|
|
├── plan.md # This file
|
|
├── research.md # Phase 0 — resolved unknowns
|
|
├── data-model.md # Phase 1 — entities and state transitions
|
|
├── quickstart.md # Phase 1 — setup and testing guide
|
|
├── contracts/
|
|
│ ├── proxy-http.md # HTTP response contract (success + error)
|
|
│ └── vm-context.md # VM dependency injection contract
|
|
└── tasks.md # Phase 2 — task list (/speckit.tasks, not created here)
|
|
```
|
|
|
|
### Source Code (repository root)
|
|
|
|
```text
|
|
src/
|
|
├── proxyScripts/
|
|
│ └── proxy.js # NEW — OIDC authentication proxy (VM sandbox)
|
|
├── globalVariables/
|
|
│ └── adapter_settings.json # NEW — OIDC credentials and token endpoint
|
|
├── logger.js # EXISTING — no changes
|
|
└── server.js # EXISTING — no changes required
|
|
|
|
tests/
|
|
├── unit/
|
|
│ └── proxy.test.js # NEW — unit tests: cache, expiry, stampede, error paths
|
|
└── contract/
|
|
└── proxy-http.test.js # NEW — contract tests: HTTP 200/401 response shape
|
|
```
|
|
|
|
**Structure Decision**: Single-project layout (Option 1). Two new source files, two new test
|
|
files. No new directories (both `src/proxyScripts/` and `src/globalVariables/` already exist as
|
|
defined directories in the constitution; `tests/unit/` and `tests/contract/` match the existing
|
|
`package.json` test scripts).
|
|
|
|
## Complexity Tracking
|
|
|
|
> No constitution violations — this section is intentionally empty.
|