114 lines
3.6 KiB
Markdown
114 lines
3.6 KiB
Markdown
# Contract: Proxy HTTP Responses
|
|
|
|
**Feature**: 001-oidc-proxy-script
|
|
**File**: `src/proxyScripts/proxy.js`
|
|
**Endpoint**: Any path handled by the adapter (all requests delegated to proxy.js by server.js)
|
|
**Date**: 2025-07-17
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
`proxy.js` responds to every inbound HTTP request with exactly one of two outcomes:
|
|
a success response (authentication succeeded) or an error response (authentication failed for
|
|
any reason). The contract defines the exact shape of both outcomes.
|
|
|
|
---
|
|
|
|
## Success Response
|
|
|
|
**Trigger**: OIDC token successfully obtained (fresh fetch or valid cached token).
|
|
|
|
```
|
|
HTTP/1.1 200 OK
|
|
Content-Type: text/plain
|
|
|
|
Authorized
|
|
```
|
|
|
|
| Property | Value |
|
|
|----------|-------|
|
|
| Status code | `200` |
|
|
| Status text | `OK` |
|
|
| `Content-Type` header | `text/plain` |
|
|
| Body | Literal string `Authorized` (no trailing newline) |
|
|
|
|
**Acceptance test** (FR-007, SC-001, SC-002):
|
|
|
|
```javascript
|
|
assert.strictEqual(res.statusCode, 200);
|
|
assert.strictEqual(res.body, 'Authorized');
|
|
```
|
|
|
|
---
|
|
|
|
## Error Response
|
|
|
|
**Trigger**: Any of the following (FR-008, SC-004):
|
|
- Token service returns HTTP 4xx or 5xx
|
|
- Token service is unreachable (network error)
|
|
- Token request times out after 5 seconds (FR-014)
|
|
- Token service response is missing `id_token` or `expires_in`
|
|
- `adapter_settings` is missing a required field
|
|
|
|
```
|
|
HTTP/1.1 401 Unauthorized
|
|
Content-Type: text/plain
|
|
|
|
Unauthorized: <descriptive message>
|
|
```
|
|
|
|
| Property | Value |
|
|
|----------|-------|
|
|
| Status code | `401` |
|
|
| Status text | `Unauthorized` |
|
|
| `Content-Type` header | `text/plain` |
|
|
| Body prefix | `Unauthorized: ` (literal, followed by the error message) |
|
|
| Body | Never empty; always includes a human-readable description |
|
|
|
|
**Example bodies by error cause**:
|
|
|
|
| Cause | Example body |
|
|
|-------|-------------|
|
|
| Invalid credentials (401 from token service) | `Unauthorized: HTTP 401` |
|
|
| Token service unavailable | `Unauthorized: connect ECONNREFUSED 127.0.0.1:443` |
|
|
| 5-second timeout | `Unauthorized: token service timeout` |
|
|
| Response missing `id_token` | `Unauthorized: id_token missing from response` |
|
|
| Response missing `expires_in` | `Unauthorized: expires_in missing from response` |
|
|
| Missing `tokenUrl` in settings | `Unauthorized: missing required field: tokenUrl` |
|
|
|
|
**Acceptance test** (FR-008):
|
|
|
|
```javascript
|
|
assert.strictEqual(res.statusCode, 401);
|
|
assert.match(res.body, /^Unauthorized: .+/);
|
|
```
|
|
|
|
---
|
|
|
|
## Invariants
|
|
|
|
These MUST hold for every request, regardless of outcome:
|
|
|
|
1. **One response per request**: `res.writeHead()` MUST be called exactly once;
|
|
`res.end()` MUST be called exactly once.
|
|
2. **Never 500**: `proxy.js` MUST NOT emit a 500 or leave the connection open. All
|
|
errors, including unexpected runtime errors, MUST result in a `401` (not a crash or hang).
|
|
3. **No imports/exports** (FR-009): The script MUST contain zero `import` or `export`
|
|
statements — verified by static analysis.
|
|
4. **No forbidden globals** (FR-010): No `config`, `global.config`, or `process.env`
|
|
references — verified by static analysis.
|
|
5. **Response within 5 seconds** (SC-001, FR-014): The HTTP timeout on the token POST
|
|
is 5 000 ms. Combined with synchronous error handling, every request resolves within
|
|
5 seconds under normal network conditions.
|
|
|
|
---
|
|
|
|
## Out of Scope
|
|
|
|
- The proxy script does NOT validate the inbound request (method, path, headers, body).
|
|
Its sole responsibility is OIDC authentication.
|
|
- The response does NOT include the OIDC token in the body. The `200 OK / Authorized`
|
|
body is sufficient to confirm authentication succeeded (spec assumption, line 106).
|
|
- No `Authorization` response header is set. The adapter's caller does not require it.
|