[Spec Kit] Implementation progress

This commit is contained in:
2026-04-22 19:41:14 -05:00
parent 24cfd85ac2
commit 110c2e961b
61 changed files with 5371 additions and 38 deletions

View File

@@ -0,0 +1,108 @@
# 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.