@vymalo/opencode-oauth2 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +213 -0
- package/dist/cache.d.ts +9 -0
- package/dist/cache.js +91 -0
- package/dist/cache.js.map +1 -0
- package/dist/config.d.ts +97 -0
- package/dist/config.js +179 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/logging.d.ts +11 -0
- package/dist/logging.js +59 -0
- package/dist/logging.js.map +1 -0
- package/dist/model-discovery.d.ts +9 -0
- package/dist/model-discovery.js +74 -0
- package/dist/model-discovery.js.map +1 -0
- package/dist/model-normalization.d.ts +4 -0
- package/dist/model-normalization.js +86 -0
- package/dist/model-normalization.js.map +1 -0
- package/dist/oauth/browser.d.ts +1 -0
- package/dist/oauth/browser.js +29 -0
- package/dist/oauth/browser.js.map +1 -0
- package/dist/oauth/client.d.ts +56 -0
- package/dist/oauth/client.js +436 -0
- package/dist/oauth/client.js.map +1 -0
- package/dist/oauth/device-code.d.ts +22 -0
- package/dist/oauth/device-code.js +240 -0
- package/dist/oauth/device-code.js.map +1 -0
- package/dist/oauth/discovery.d.ts +8 -0
- package/dist/oauth/discovery.js +38 -0
- package/dist/oauth/discovery.js.map +1 -0
- package/dist/oauth/http-utils.d.ts +33 -0
- package/dist/oauth/http-utils.js +129 -0
- package/dist/oauth/http-utils.js.map +1 -0
- package/dist/oauth/local-callback.d.ts +10 -0
- package/dist/oauth/local-callback.js +82 -0
- package/dist/oauth/local-callback.js.map +1 -0
- package/dist/oauth/pkce.d.ts +5 -0
- package/dist/oauth/pkce.js +13 -0
- package/dist/oauth/pkce.js.map +1 -0
- package/dist/oauth/subject-token.d.ts +20 -0
- package/dist/oauth/subject-token.js +86 -0
- package/dist/oauth/subject-token.js.map +1 -0
- package/dist/opencode.d.ts +11 -0
- package/dist/opencode.js +347 -0
- package/dist/opencode.js.map +1 -0
- package/dist/plugin.d.ts +47 -0
- package/dist/plugin.js +220 -0
- package/dist/plugin.js.map +1 -0
- package/dist/scheduler.d.ts +11 -0
- package/dist/scheduler.js +44 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/types.d.ts +37 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 vymalo contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# @vymalo/opencode-oauth2
|
|
2
|
+
|
|
3
|
+
OAuth2/OIDC model sync plugin for OpenCode. This is the canonical configuration reference for the plugin. Long-form usage guides (CI cookbooks, Kubernetes manifests, troubleshooting) live in [`/docs/`](../../docs/) at the repo root.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
- Registers OpenAI-compatible providers into OpenCode config
|
|
8
|
+
- Supports five OAuth2 grants:
|
|
9
|
+
- `authorization_code` — interactive PKCE login (default)
|
|
10
|
+
- `device_code` — RFC 8628, for browserless user auth
|
|
11
|
+
- `client_credentials` — machine-to-machine via `clientSecret`
|
|
12
|
+
- `jwt_bearer` — RFC 7523, federated identity (e.g. GitHub Actions OIDC, K8s SA tokens) — no long-lived secret in CI
|
|
13
|
+
- `token_exchange` — RFC 8693, federated identity with explicit audience targeting
|
|
14
|
+
- Stores and refreshes provider access tokens
|
|
15
|
+
- Fetches and normalizes provider model catalogs
|
|
16
|
+
- Injects `Authorization` headers at chat request time
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
Add plugin package to OpenCode:
|
|
21
|
+
|
|
22
|
+
```jsonc
|
|
23
|
+
{
|
|
24
|
+
"$schema": "https://opencode.ai/config.json",
|
|
25
|
+
"plugin": ["@vymalo/opencode-oauth2"]
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Configuration
|
|
30
|
+
|
|
31
|
+
### Option A: Provider-embedded OAuth config (recommended)
|
|
32
|
+
|
|
33
|
+
```jsonc
|
|
34
|
+
{
|
|
35
|
+
"$schema": "https://opencode.ai/config.json",
|
|
36
|
+
"plugin": ["@vymalo/opencode-oauth2"],
|
|
37
|
+
"provider": {
|
|
38
|
+
"example-ai": {
|
|
39
|
+
"name": "Example AI",
|
|
40
|
+
"options": {
|
|
41
|
+
"baseURL": "https://api.example.com/v1",
|
|
42
|
+
"oauth2": {
|
|
43
|
+
"issuer": "https://auth.example.com",
|
|
44
|
+
"clientId": "opencode-client",
|
|
45
|
+
"scopes": ["openid", "profile", "offline_access"],
|
|
46
|
+
"syncIntervalMinutes": 60,
|
|
47
|
+
"nameOverrides": {
|
|
48
|
+
"glm-5": "GLM 5"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Option B: `pluginConfig.oauth2ModelSync.servers`
|
|
58
|
+
|
|
59
|
+
```jsonc
|
|
60
|
+
{
|
|
61
|
+
"$schema": "https://opencode.ai/config.json",
|
|
62
|
+
"plugin": ["@vymalo/opencode-oauth2"],
|
|
63
|
+
"pluginConfig": {
|
|
64
|
+
"oauth2ModelSync": {
|
|
65
|
+
"servers": [
|
|
66
|
+
{
|
|
67
|
+
"id": "example-ai",
|
|
68
|
+
"name": "Example AI",
|
|
69
|
+
"issuer": "https://auth.example.com",
|
|
70
|
+
"baseURL": "https://api.example.com/v1",
|
|
71
|
+
"clientId": "opencode-client",
|
|
72
|
+
"scopes": ["openid", "profile", "offline_access"],
|
|
73
|
+
"syncIntervalMinutes": 60,
|
|
74
|
+
"nameOverrides": {
|
|
75
|
+
"glm-5": "GLM 5"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Optional fields
|
|
85
|
+
|
|
86
|
+
These apply to both config shapes above.
|
|
87
|
+
|
|
88
|
+
| Field | Default | Notes |
|
|
89
|
+
| --- | --- | --- |
|
|
90
|
+
| `clientSecret` | _(unset)_ | For confidential clients. Sent as `client_secret` on every token-endpoint POST. Never logged. Required for `authFlow: "client_credentials"`. Optional but commonly required for `jwt_bearer` / `token_exchange` with confidential clients. PKCE is still required for `authorization_code`. |
|
|
91
|
+
| `authFlow` | `"authorization_code"` | One of `"authorization_code"`, `"device_code"`, `"client_credentials"`, `"jwt_bearer"`, `"token_exchange"`. |
|
|
92
|
+
| `subjectTokenSource` | _(required for `jwt_bearer` / `token_exchange`)_ | Where to read the platform JWT to present as the subject token. See [Federated identity](#federated-identity-no-long-lived-secrets-in-ci) below. |
|
|
93
|
+
| `tokenExchangeAudience` | _(unset)_ | Optional `audience` parameter for the `token_exchange` grant. |
|
|
94
|
+
| `deviceAuthorizationEndpoint` | discovered | Override for the device authorization endpoint. Otherwise discovered from `device_authorization_endpoint` in the OIDC metadata. Only used when `authFlow === "device_code"`. |
|
|
95
|
+
| `authorizationEndpoint` | discovered | Override for the authorization endpoint. |
|
|
96
|
+
| `tokenEndpoint` | discovered | Override for the token endpoint. |
|
|
97
|
+
| `redirectPort` | random | Fixed port for the local callback server (authorization-code only). |
|
|
98
|
+
| `nameOverrides` | `{}` | Map of model id → friendly display name. Applied during catalog normalization. |
|
|
99
|
+
| `syncIntervalMinutes` | `60` | Per-server scheduler interval. Failures preserve the last-known-good model list. |
|
|
100
|
+
| `jwksUri` | _(unset)_ | Reserved; not currently used at runtime. |
|
|
101
|
+
|
|
102
|
+
Plus the top-level `pluginConfig.oauth2ModelSync` block accepts:
|
|
103
|
+
|
|
104
|
+
| Field | Default | Notes |
|
|
105
|
+
| --- | --- | --- |
|
|
106
|
+
| `cacheNamespace` | `"opencode-oauth2-model-sync"` (OpenCode-hosted) / `"oauth2-model-sync"` (standalone) | Subdirectory under the OS cache root. See [architecture.md](../../docs/architecture.md#cache-layout) for the path table per OS. |
|
|
107
|
+
| `httpTimeoutMs` | `15000` | Timeout for token-endpoint / `/models` round trips. |
|
|
108
|
+
| `tokenExpirySkewMs` | `30000` | Treat a token as expired this many ms before its real `expiresAt`. |
|
|
109
|
+
|
|
110
|
+
## Federated identity (no long-lived secrets in CI)
|
|
111
|
+
|
|
112
|
+
For CI runners and Kubernetes workloads, the modern best practice is to skip stored client secrets entirely and use the platform's own short-lived OIDC token to authenticate. `@vymalo/opencode-oauth2` supports this via the **`jwt_bearer`** (RFC 7523) and **`token_exchange`** (RFC 8693) grants.
|
|
113
|
+
|
|
114
|
+
The plugin reads the platform JWT at token-acquisition time (never caches it) and presents it to your OAuth server as proof of identity. The OAuth server validates the JWT signature against the platform's JWKS, applies your IdP's policy, and returns an access token.
|
|
115
|
+
|
|
116
|
+
`subjectTokenSource` tells the plugin where to read the JWT:
|
|
117
|
+
|
|
118
|
+
| `type` | Reads from | Required fields |
|
|
119
|
+
| --- | --- | --- |
|
|
120
|
+
| `github_actions` | `ACTIONS_ID_TOKEN_REQUEST_URL` + `ACTIONS_ID_TOKEN_REQUEST_TOKEN` env vars | `audience` |
|
|
121
|
+
| `kubernetes_sa` | Projected service-account token file (default `/var/run/secrets/tokens/oauth2/token`) | _(optional `tokenPath`)_ |
|
|
122
|
+
| `file` | Arbitrary file path | `path` |
|
|
123
|
+
| `env` | Environment variable (dev/test only) | `var` |
|
|
124
|
+
|
|
125
|
+
End-to-end recipes:
|
|
126
|
+
|
|
127
|
+
- **GitHub Actions** — see [`docs/github-actions.md`](../../docs/github-actions.md) for the Keycloak / Auth0 / Okta setup walkthroughs, the reusable workflow at [`.github/workflows/opencode-run.yml`](../../.github/workflows/opencode-run.yml), matrix builds, audience pinning, and fork-PR limitations.
|
|
128
|
+
- **Kubernetes** — see [`docs/kubernetes.md`](../../docs/kubernetes.md) for the `CronJob` (headline), `Job`, and `Deployment` manifests, multi-provider pods, IdP setup with Keycloak/Dex, and RBAC notes (spoiler: you need almost none).
|
|
129
|
+
|
|
130
|
+
### Quick GHA reference
|
|
131
|
+
|
|
132
|
+
```jsonc
|
|
133
|
+
{
|
|
134
|
+
"provider": {
|
|
135
|
+
"example-ai": {
|
|
136
|
+
"options": {
|
|
137
|
+
"baseURL": "https://api.example.com/v1",
|
|
138
|
+
"oauth2": {
|
|
139
|
+
"issuer": "https://auth.example.com/realms/example",
|
|
140
|
+
"clientId": "ci-runner",
|
|
141
|
+
"scopes": ["openid"],
|
|
142
|
+
"authFlow": "jwt_bearer",
|
|
143
|
+
"subjectTokenSource": {
|
|
144
|
+
"type": "github_actions",
|
|
145
|
+
"audience": "https://auth.example.com/realms/example"
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Workflow needs `permissions: { id-token: write }`. No `clientSecret` anywhere.
|
|
155
|
+
|
|
156
|
+
### Quick Kubernetes reference
|
|
157
|
+
|
|
158
|
+
```jsonc
|
|
159
|
+
{
|
|
160
|
+
"provider": {
|
|
161
|
+
"example-ai": {
|
|
162
|
+
"options": {
|
|
163
|
+
"baseURL": "https://api.example.com/v1",
|
|
164
|
+
"oauth2": {
|
|
165
|
+
"issuer": "https://auth.example.com/realms/example",
|
|
166
|
+
"clientId": "k8s-runner",
|
|
167
|
+
"scopes": ["openid"],
|
|
168
|
+
"authFlow": "jwt_bearer",
|
|
169
|
+
"subjectTokenSource": {
|
|
170
|
+
"type": "kubernetes_sa"
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The pod must mount a projected `serviceAccountToken` at `/var/run/secrets/tokens/oauth2/token` with the IdP's expected `audience`. The projected token rotates automatically (kubelet refreshes it); the plugin re-reads on every access-token expiry, so rotation is transparent. Full manifests in [`docs/kubernetes.md`](../../docs/kubernetes.md).
|
|
180
|
+
|
|
181
|
+
### Choosing between `jwt_bearer` and `token_exchange`
|
|
182
|
+
|
|
183
|
+
- **`jwt_bearer`** is the standard federated grant. Single POST: `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer`, `assertion=<JWT>`. Keycloak, Auth0, Okta all support it. **Start here.**
|
|
184
|
+
- **`token_exchange`** (RFC 8693) is more general — supports `subject_token_type`, `actor_token`, `requested_token_type`, and an explicit `audience` claim. Use only when your IdP requires it or when you need the audience targeting (set `tokenExchangeAudience`).
|
|
185
|
+
|
|
186
|
+
## OAuth Token Requirements
|
|
187
|
+
|
|
188
|
+
Refresh tokens are mandatory for the flows that issue them (`authorization_code`, `device_code`).
|
|
189
|
+
|
|
190
|
+
- Initial token exchange for those flows must return `refresh_token`; missing → rejected.
|
|
191
|
+
- Cached tokens missing `refreshToken` are invalidated on load (unless the flow doesn't issue one — `client_credentials`, `jwt_bearer`, `token_exchange`).
|
|
192
|
+
- Refresh flow preserves the previous refresh token when providers omit it in refresh responses.
|
|
193
|
+
|
|
194
|
+
## Hooks Used
|
|
195
|
+
|
|
196
|
+
- `config`: register/patch provider config and merge cached discovered models
|
|
197
|
+
- `chat.headers`: ensure valid token and set `Authorization` header
|
|
198
|
+
|
|
199
|
+
See [architecture.md](../../docs/architecture.md#the-two-hooks) for the full hook semantics.
|
|
200
|
+
|
|
201
|
+
## Development
|
|
202
|
+
|
|
203
|
+
```sh
|
|
204
|
+
pnpm --filter @vymalo/opencode-oauth2 typecheck
|
|
205
|
+
pnpm --filter @vymalo/opencode-oauth2 test
|
|
206
|
+
pnpm --filter @vymalo/opencode-oauth2 build
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Exports
|
|
210
|
+
|
|
211
|
+
- `OpencodeOauth2Plugin` (default OpenCode plugin export)
|
|
212
|
+
- `createOpencodeOauth2Plugin()` (factory for testing / custom wiring)
|
|
213
|
+
- `OAuth2ModelSyncPlugin` (runtime orchestrator — useful for embedding outside OpenCode)
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CachedServerState } from "./types.js";
|
|
2
|
+
export declare function resolveCacheDir(namespace: string): string;
|
|
3
|
+
export declare class FileCacheStore {
|
|
4
|
+
private readonly baseDir;
|
|
5
|
+
constructor(baseDir: string);
|
|
6
|
+
ensureReady(): Promise<void>;
|
|
7
|
+
loadServerState(serverId: string): Promise<CachedServerState | undefined>;
|
|
8
|
+
saveServerState(state: CachedServerState): Promise<void>;
|
|
9
|
+
}
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { constants as fsConstants } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
function resolveDefaultCacheRoot() {
|
|
6
|
+
if (process.platform === "win32") {
|
|
7
|
+
return process.env.LOCALAPPDATA ?? join(homedir(), "AppData", "Local");
|
|
8
|
+
}
|
|
9
|
+
if (process.platform === "darwin") {
|
|
10
|
+
return join(homedir(), "Library", "Caches");
|
|
11
|
+
}
|
|
12
|
+
return process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache");
|
|
13
|
+
}
|
|
14
|
+
export function resolveCacheDir(namespace) {
|
|
15
|
+
return join(resolveDefaultCacheRoot(), "opencode-oauth2", namespace);
|
|
16
|
+
}
|
|
17
|
+
function statePath(baseDir, serverId) {
|
|
18
|
+
return join(baseDir, `${serverId}.json`);
|
|
19
|
+
}
|
|
20
|
+
function hasValidTokenShape(token) {
|
|
21
|
+
if (!token || typeof token !== "object" || Array.isArray(token)) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const candidate = token;
|
|
25
|
+
if (typeof candidate.accessToken !== "string" ||
|
|
26
|
+
candidate.accessToken.length === 0 ||
|
|
27
|
+
typeof candidate.tokenType !== "string" ||
|
|
28
|
+
candidate.tokenType.length === 0) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
// refreshToken is optional: client_credentials grant never returns one.
|
|
32
|
+
// When present, it must be a non-empty string.
|
|
33
|
+
if (candidate.refreshToken !== undefined) {
|
|
34
|
+
return typeof candidate.refreshToken === "string" && candidate.refreshToken.length > 0;
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
export class FileCacheStore {
|
|
39
|
+
baseDir;
|
|
40
|
+
constructor(baseDir) {
|
|
41
|
+
this.baseDir = baseDir;
|
|
42
|
+
}
|
|
43
|
+
async ensureReady() {
|
|
44
|
+
await mkdir(this.baseDir, { recursive: true, mode: 0o700 });
|
|
45
|
+
}
|
|
46
|
+
async loadServerState(serverId) {
|
|
47
|
+
const filePath = statePath(this.baseDir, serverId);
|
|
48
|
+
try {
|
|
49
|
+
const content = await readFile(filePath, "utf8");
|
|
50
|
+
const parsed = JSON.parse(content);
|
|
51
|
+
if (!parsed || parsed.serverId !== serverId) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
if (!Array.isArray(parsed.models) || !Array.isArray(parsed.rawModels)) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
if (parsed.token && !hasValidTokenShape(parsed.token)) {
|
|
58
|
+
parsed.token = undefined;
|
|
59
|
+
}
|
|
60
|
+
return parsed;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (typeof error === "object" &&
|
|
64
|
+
error &&
|
|
65
|
+
"code" in error &&
|
|
66
|
+
error.code === "ENOENT") {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async saveServerState(state) {
|
|
73
|
+
const filePath = statePath(this.baseDir, state.serverId);
|
|
74
|
+
const tempPath = `${filePath}.tmp`;
|
|
75
|
+
await mkdir(dirname(filePath), { recursive: true, mode: 0o700 });
|
|
76
|
+
const serialized = JSON.stringify(state, null, 2);
|
|
77
|
+
await writeFile(tempPath, serialized, {
|
|
78
|
+
encoding: "utf8",
|
|
79
|
+
mode: 0o600,
|
|
80
|
+
flag: fsConstants.O_CREAT | fsConstants.O_TRUNC | fsConstants.O_WRONLY
|
|
81
|
+
});
|
|
82
|
+
await rename(tempPath, filePath);
|
|
83
|
+
try {
|
|
84
|
+
await chmod(filePath, 0o600);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Some filesystems may ignore chmod semantics; keep operation non-fatal.
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export declare const DEFAULT_SYNC_INTERVAL_MINUTES = 60;
|
|
2
|
+
export declare const DEFAULT_HTTP_TIMEOUT_MS = 15000;
|
|
3
|
+
export declare const DEFAULT_TOKEN_EXPIRY_SKEW_MS = 30000;
|
|
4
|
+
export type OAuthAuthFlow = "authorization_code" | "device_code" | "client_credentials" | "jwt_bearer" | "token_exchange";
|
|
5
|
+
export declare const DEFAULT_AUTH_FLOW: OAuthAuthFlow;
|
|
6
|
+
/**
|
|
7
|
+
* Where to read the platform-supplied JWT that the plugin presents as the
|
|
8
|
+
* subject token / assertion for the `jwt_bearer` and `token_exchange` flows.
|
|
9
|
+
*
|
|
10
|
+
* - `github_actions` — fetches the OIDC token from the GitHub Actions runtime
|
|
11
|
+
* via `ACTIONS_ID_TOKEN_REQUEST_URL` + `ACTIONS_ID_TOKEN_REQUEST_TOKEN`,
|
|
12
|
+
* with the workflow-declared `audience`. The `id-token: write` permission
|
|
13
|
+
* is required on the job.
|
|
14
|
+
* - `kubernetes_sa` — reads a projected service-account token from the pod
|
|
15
|
+
* filesystem. Default path `/var/run/secrets/tokens/oauth2/token`; mount a
|
|
16
|
+
* `projected.sources.serviceAccountToken` volume with the OIDC issuer as
|
|
17
|
+
* the audience.
|
|
18
|
+
* - `file` — reads any JWT from disk. Useful when an external sidecar
|
|
19
|
+
* refreshes the token to a fixed path.
|
|
20
|
+
* - `env` — reads the JWT from a named environment variable. Mostly useful
|
|
21
|
+
* for tests and ad-hoc shells.
|
|
22
|
+
*/
|
|
23
|
+
export type SubjectTokenSource = {
|
|
24
|
+
type: "github_actions";
|
|
25
|
+
audience: string;
|
|
26
|
+
} | {
|
|
27
|
+
type: "kubernetes_sa";
|
|
28
|
+
tokenPath?: string;
|
|
29
|
+
} | {
|
|
30
|
+
type: "file";
|
|
31
|
+
path: string;
|
|
32
|
+
} | {
|
|
33
|
+
type: "env";
|
|
34
|
+
var: string;
|
|
35
|
+
};
|
|
36
|
+
export declare const DEFAULT_K8S_SA_TOKEN_PATH = "/var/run/secrets/tokens/oauth2/token";
|
|
37
|
+
export interface OAuthServerConfigInput {
|
|
38
|
+
id: string;
|
|
39
|
+
name?: string;
|
|
40
|
+
issuer: string;
|
|
41
|
+
baseURL: string;
|
|
42
|
+
clientId: string;
|
|
43
|
+
clientSecret?: string;
|
|
44
|
+
scopes: string[];
|
|
45
|
+
syncIntervalMinutes?: number;
|
|
46
|
+
nameOverrides?: Record<string, string>;
|
|
47
|
+
authorizationEndpoint?: string;
|
|
48
|
+
tokenEndpoint?: string;
|
|
49
|
+
deviceAuthorizationEndpoint?: string;
|
|
50
|
+
jwksUri?: string;
|
|
51
|
+
redirectPort?: number;
|
|
52
|
+
authFlow?: OAuthAuthFlow;
|
|
53
|
+
/**
|
|
54
|
+
* Required for `jwt_bearer` and `token_exchange`. Tells the plugin where to
|
|
55
|
+
* read the platform JWT it should present as the subject token.
|
|
56
|
+
*/
|
|
57
|
+
subjectTokenSource?: SubjectTokenSource;
|
|
58
|
+
/**
|
|
59
|
+
* Optional `audience` parameter for the `token_exchange` grant — the
|
|
60
|
+
* intended recipient of the resulting access token. Set this when the
|
|
61
|
+
* OAuth server expects an explicit audience claim distinct from the
|
|
62
|
+
* issuer.
|
|
63
|
+
*/
|
|
64
|
+
tokenExchangeAudience?: string;
|
|
65
|
+
}
|
|
66
|
+
export interface OAuth2ModelSyncConfigInput {
|
|
67
|
+
servers: OAuthServerConfigInput[];
|
|
68
|
+
cacheNamespace?: string;
|
|
69
|
+
httpTimeoutMs?: number;
|
|
70
|
+
tokenExpirySkewMs?: number;
|
|
71
|
+
}
|
|
72
|
+
export interface OAuthServerConfig {
|
|
73
|
+
id: string;
|
|
74
|
+
name: string;
|
|
75
|
+
issuer: string;
|
|
76
|
+
baseURL: string;
|
|
77
|
+
clientId: string;
|
|
78
|
+
clientSecret?: string;
|
|
79
|
+
scopes: string[];
|
|
80
|
+
syncIntervalMinutes: number;
|
|
81
|
+
nameOverrides: Record<string, string>;
|
|
82
|
+
authorizationEndpoint?: string;
|
|
83
|
+
tokenEndpoint?: string;
|
|
84
|
+
deviceAuthorizationEndpoint?: string;
|
|
85
|
+
jwksUri?: string;
|
|
86
|
+
redirectPort?: number;
|
|
87
|
+
authFlow: OAuthAuthFlow;
|
|
88
|
+
subjectTokenSource?: SubjectTokenSource;
|
|
89
|
+
tokenExchangeAudience?: string;
|
|
90
|
+
}
|
|
91
|
+
export interface OAuth2ModelSyncConfig {
|
|
92
|
+
servers: OAuthServerConfig[];
|
|
93
|
+
cacheNamespace: string;
|
|
94
|
+
httpTimeoutMs: number;
|
|
95
|
+
tokenExpirySkewMs: number;
|
|
96
|
+
}
|
|
97
|
+
export declare function validateConfig(input: OAuth2ModelSyncConfigInput): OAuth2ModelSyncConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
export const DEFAULT_SYNC_INTERVAL_MINUTES = 60;
|
|
2
|
+
export const DEFAULT_HTTP_TIMEOUT_MS = 15_000;
|
|
3
|
+
export const DEFAULT_TOKEN_EXPIRY_SKEW_MS = 30_000;
|
|
4
|
+
export const DEFAULT_AUTH_FLOW = "authorization_code";
|
|
5
|
+
export const DEFAULT_K8S_SA_TOKEN_PATH = "/var/run/secrets/tokens/oauth2/token";
|
|
6
|
+
function ensureString(value, path) {
|
|
7
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
8
|
+
throw new Error(`${path} must be a non-empty string`);
|
|
9
|
+
}
|
|
10
|
+
return value.trim();
|
|
11
|
+
}
|
|
12
|
+
function ensureStringArray(value, path) {
|
|
13
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
14
|
+
throw new Error(`${path} must be a non-empty array of strings`);
|
|
15
|
+
}
|
|
16
|
+
return value.map((item, index) => ensureString(item, `${path}[${index}]`));
|
|
17
|
+
}
|
|
18
|
+
function validateRedirectPort(value, path) {
|
|
19
|
+
if (value === undefined || value === null) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0 || value >= 65536) {
|
|
23
|
+
throw new Error(`${path} must be a positive integer less than 65536`);
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
function validateAuthFlow(value, path) {
|
|
28
|
+
if (value === undefined || value === null) {
|
|
29
|
+
return DEFAULT_AUTH_FLOW;
|
|
30
|
+
}
|
|
31
|
+
if (value === "authorization_code" ||
|
|
32
|
+
value === "device_code" ||
|
|
33
|
+
value === "client_credentials" ||
|
|
34
|
+
value === "jwt_bearer" ||
|
|
35
|
+
value === "token_exchange") {
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
throw new Error(`${path} must be one of "authorization_code" | "device_code" | "client_credentials" | "jwt_bearer" | "token_exchange" (received ${JSON.stringify(value)})`);
|
|
39
|
+
}
|
|
40
|
+
function validateSubjectTokenSource(value, path) {
|
|
41
|
+
if (value === undefined || value === null) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
45
|
+
throw new Error(`${path} must be an object`);
|
|
46
|
+
}
|
|
47
|
+
const record = value;
|
|
48
|
+
const type = record.type;
|
|
49
|
+
switch (type) {
|
|
50
|
+
case "github_actions": {
|
|
51
|
+
const audience = record.audience;
|
|
52
|
+
if (typeof audience !== "string" || audience.trim().length === 0) {
|
|
53
|
+
throw new Error(`${path}.audience must be a non-empty string when type is "github_actions"`);
|
|
54
|
+
}
|
|
55
|
+
return { type: "github_actions", audience: audience.trim() };
|
|
56
|
+
}
|
|
57
|
+
case "kubernetes_sa": {
|
|
58
|
+
const tokenPath = record.tokenPath;
|
|
59
|
+
if (tokenPath !== undefined && tokenPath !== null) {
|
|
60
|
+
if (typeof tokenPath !== "string" || tokenPath.trim().length === 0) {
|
|
61
|
+
throw new Error(`${path}.tokenPath must be a non-empty string when provided`);
|
|
62
|
+
}
|
|
63
|
+
return { type: "kubernetes_sa", tokenPath: tokenPath.trim() };
|
|
64
|
+
}
|
|
65
|
+
return { type: "kubernetes_sa" };
|
|
66
|
+
}
|
|
67
|
+
case "file": {
|
|
68
|
+
const filePath = record.path;
|
|
69
|
+
if (typeof filePath !== "string" || filePath.trim().length === 0) {
|
|
70
|
+
throw new Error(`${path}.path must be a non-empty string when type is "file"`);
|
|
71
|
+
}
|
|
72
|
+
return { type: "file", path: filePath.trim() };
|
|
73
|
+
}
|
|
74
|
+
case "env": {
|
|
75
|
+
const varName = record.var;
|
|
76
|
+
if (typeof varName !== "string" || varName.trim().length === 0) {
|
|
77
|
+
throw new Error(`${path}.var must be a non-empty string when type is "env"`);
|
|
78
|
+
}
|
|
79
|
+
return { type: "env", var: varName.trim() };
|
|
80
|
+
}
|
|
81
|
+
default:
|
|
82
|
+
throw new Error(`${path}.type must be one of "github_actions" | "kubernetes_sa" | "file" | "env" (received ${JSON.stringify(type)})`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function normalizeServerConfig(input, index) {
|
|
86
|
+
const path = `servers[${index}]`;
|
|
87
|
+
const id = ensureString(input.id, `${path}.id`);
|
|
88
|
+
const name = input.name && input.name.trim().length > 0 ? input.name.trim() : id;
|
|
89
|
+
const issuer = ensureString(input.issuer, `${path}.issuer`);
|
|
90
|
+
const baseURL = ensureString(input.baseURL, `${path}.baseURL`);
|
|
91
|
+
const clientId = ensureString(input.clientId, `${path}.clientId`);
|
|
92
|
+
const scopes = ensureStringArray(input.scopes, `${path}.scopes`);
|
|
93
|
+
const syncIntervalMinutes = typeof input.syncIntervalMinutes === "number" &&
|
|
94
|
+
Number.isFinite(input.syncIntervalMinutes) &&
|
|
95
|
+
input.syncIntervalMinutes > 0
|
|
96
|
+
? input.syncIntervalMinutes
|
|
97
|
+
: DEFAULT_SYNC_INTERVAL_MINUTES;
|
|
98
|
+
const redirectPort = validateRedirectPort(input.redirectPort, `${path}.redirectPort`);
|
|
99
|
+
const authFlow = validateAuthFlow(input.authFlow, `${path}.authFlow`);
|
|
100
|
+
const subjectTokenSource = validateSubjectTokenSource(input.subjectTokenSource, `${path}.subjectTokenSource`);
|
|
101
|
+
let clientSecret;
|
|
102
|
+
if (input.clientSecret !== undefined && input.clientSecret !== null) {
|
|
103
|
+
if (typeof input.clientSecret !== "string" || input.clientSecret.length === 0) {
|
|
104
|
+
throw new Error(`${path}.clientSecret must be a non-empty string when provided`);
|
|
105
|
+
}
|
|
106
|
+
clientSecret = input.clientSecret;
|
|
107
|
+
}
|
|
108
|
+
if (authFlow === "client_credentials" && !clientSecret) {
|
|
109
|
+
throw new Error(`${path}.clientSecret is required when authFlow is "client_credentials"`);
|
|
110
|
+
}
|
|
111
|
+
if ((authFlow === "jwt_bearer" || authFlow === "token_exchange") && !subjectTokenSource) {
|
|
112
|
+
throw new Error(`${path}.subjectTokenSource is required when authFlow is "${authFlow}" — set it to {type: "github_actions" | "kubernetes_sa" | "file" | "env", ...}`);
|
|
113
|
+
}
|
|
114
|
+
let tokenExchangeAudience;
|
|
115
|
+
if (input.tokenExchangeAudience !== undefined && input.tokenExchangeAudience !== null) {
|
|
116
|
+
if (typeof input.tokenExchangeAudience !== "string" ||
|
|
117
|
+
input.tokenExchangeAudience.trim().length === 0) {
|
|
118
|
+
throw new Error(`${path}.tokenExchangeAudience must be a non-empty string when provided`);
|
|
119
|
+
}
|
|
120
|
+
tokenExchangeAudience = input.tokenExchangeAudience.trim();
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
id,
|
|
124
|
+
name,
|
|
125
|
+
issuer,
|
|
126
|
+
baseURL,
|
|
127
|
+
clientId,
|
|
128
|
+
clientSecret,
|
|
129
|
+
scopes,
|
|
130
|
+
syncIntervalMinutes,
|
|
131
|
+
nameOverrides: input.nameOverrides ?? {},
|
|
132
|
+
authorizationEndpoint: input.authorizationEndpoint,
|
|
133
|
+
tokenEndpoint: input.tokenEndpoint,
|
|
134
|
+
deviceAuthorizationEndpoint: input.deviceAuthorizationEndpoint,
|
|
135
|
+
jwksUri: input.jwksUri,
|
|
136
|
+
redirectPort,
|
|
137
|
+
authFlow,
|
|
138
|
+
subjectTokenSource,
|
|
139
|
+
tokenExchangeAudience
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
export function validateConfig(input) {
|
|
143
|
+
if (!input || typeof input !== "object") {
|
|
144
|
+
throw new Error("plugin config must be an object");
|
|
145
|
+
}
|
|
146
|
+
if (!Array.isArray(input.servers) || input.servers.length === 0) {
|
|
147
|
+
throw new Error("servers must be a non-empty array");
|
|
148
|
+
}
|
|
149
|
+
const normalizedServers = input.servers.map(normalizeServerConfig);
|
|
150
|
+
const ids = new Set();
|
|
151
|
+
for (const server of normalizedServers) {
|
|
152
|
+
if (ids.has(server.id)) {
|
|
153
|
+
throw new Error(`duplicate server id detected: ${server.id}`);
|
|
154
|
+
}
|
|
155
|
+
ids.add(server.id);
|
|
156
|
+
}
|
|
157
|
+
let tokenExpirySkewMs = DEFAULT_TOKEN_EXPIRY_SKEW_MS;
|
|
158
|
+
if (input.tokenExpirySkewMs !== undefined && input.tokenExpirySkewMs !== null) {
|
|
159
|
+
if (typeof input.tokenExpirySkewMs !== "number" ||
|
|
160
|
+
!Number.isFinite(input.tokenExpirySkewMs) ||
|
|
161
|
+
input.tokenExpirySkewMs <= 0) {
|
|
162
|
+
throw new Error("tokenExpirySkewMs must be a positive number");
|
|
163
|
+
}
|
|
164
|
+
tokenExpirySkewMs = input.tokenExpirySkewMs;
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
servers: normalizedServers,
|
|
168
|
+
cacheNamespace: typeof input.cacheNamespace === "string" && input.cacheNamespace.trim().length > 0
|
|
169
|
+
? input.cacheNamespace.trim()
|
|
170
|
+
: "oauth2-model-sync",
|
|
171
|
+
httpTimeoutMs: typeof input.httpTimeoutMs === "number" &&
|
|
172
|
+
Number.isFinite(input.httpTimeoutMs) &&
|
|
173
|
+
input.httpTimeoutMs > 0
|
|
174
|
+
? input.httpTimeoutMs
|
|
175
|
+
: DEFAULT_HTTP_TIMEOUT_MS,
|
|
176
|
+
tokenExpirySkewMs
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { DEFAULT_HTTP_TIMEOUT_MS, DEFAULT_SYNC_INTERVAL_MINUTES, type OAuth2ModelSyncConfig, type OAuth2ModelSyncConfigInput, type OAuthServerConfig, type OAuthServerConfigInput, validateConfig } from "./config.js";
|
|
2
|
+
export { createJsonConsoleLogger, type LogLevel, type Logger } from "./logging.js";
|
|
3
|
+
export { buildModelsUrl, fetchModels } from "./model-discovery.js";
|
|
4
|
+
export { diffModels, normalizeModelId, normalizeModelList } from "./model-normalization.js";
|
|
5
|
+
export { OAuth2ModelSyncPlugin, type PluginOptions } from "./plugin.js";
|
|
6
|
+
export { resolveCacheDir, FileCacheStore } from "./cache.js";
|
|
7
|
+
export { createOpencodeOauth2Plugin, OpencodeOauth2Plugin, OpencodeOauth2Plugin as default } from "./opencode.js";
|
|
8
|
+
export type { OpenCodePluginFactoryOptions } from "./opencode.js";
|
|
9
|
+
export type { CachedServerState, ModelDiff, NormalizedModel, RawModel, ServerSnapshot, TokenSet } from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { DEFAULT_HTTP_TIMEOUT_MS, DEFAULT_SYNC_INTERVAL_MINUTES, validateConfig } from "./config.js";
|
|
2
|
+
export { createJsonConsoleLogger } from "./logging.js";
|
|
3
|
+
export { buildModelsUrl, fetchModels } from "./model-discovery.js";
|
|
4
|
+
export { diffModels, normalizeModelId, normalizeModelList } from "./model-normalization.js";
|
|
5
|
+
export { OAuth2ModelSyncPlugin } from "./plugin.js";
|
|
6
|
+
export { resolveCacheDir, FileCacheStore } from "./cache.js";
|
|
7
|
+
export { createOpencodeOauth2Plugin, OpencodeOauth2Plugin, OpencodeOauth2Plugin as default } from "./opencode.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,6BAA6B,EAK7B,cAAc,EACf,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,uBAAuB,EAA8B,MAAM,cAAc,CAAC;AAEnF,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnE,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE5F,OAAO,EAAE,qBAAqB,EAAsB,MAAM,aAAa,CAAC;AAExE,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE7D,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,oBAAoB,IAAI,OAAO,EAChC,MAAM,eAAe,CAAC"}
|