axauth 3.1.1 → 3.1.4
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/README.md +55 -44
- package/dist/auth/adapter.d.ts +2 -0
- package/dist/auth/agents/claude.js +2 -2
- package/dist/auth/agents/codex-auth-check.js +0 -5
- package/dist/auth/agents/codex.js +1 -1
- package/dist/auth/agents/copilot-storage.d.ts +15 -4
- package/dist/auth/agents/copilot-storage.js +39 -24
- package/dist/auth/agents/copilot.js +2 -4
- package/dist/auth/agents/gemini.js +0 -3
- package/dist/auth/agents/opencode-credentials.js +0 -2
- package/dist/auth/agents/opencode-storage.js +5 -4
- package/dist/auth/agents/opencode.js +0 -4
- package/dist/auth/build-refreshed-credentials.d.ts +6 -6
- package/dist/auth/build-refreshed-credentials.js +6 -46
- package/dist/auth/extract-creds-from-directory.js +1 -1
- package/dist/auth/install-from-environment.js +0 -7
- package/dist/auth/keychain.d.ts +5 -1
- package/dist/auth/keychain.js +29 -1
- package/dist/auth/refresh-credentials.d.ts +13 -6
- package/dist/auth/refresh-credentials.js +12 -13
- package/dist/auth/registry.d.ts +3 -3
- package/dist/auth/registry.js +10 -10
- package/dist/auth/wait-for-refreshed-credentials.d.ts +2 -1
- package/dist/cli.js +2 -2
- package/dist/commands/auth.js +2 -2
- package/dist/commands/encrypt.d.ts +1 -0
- package/dist/commands/encrypt.js +4 -18
- package/dist/commands/install-credentials.js +3 -17
- package/dist/commands/vault.js +5 -4
- package/dist/vault/vault-client.d.ts +4 -2
- package/dist/vault/vault-client.js +9 -40
- package/dist/vault/vault-config.d.ts +2 -6
- package/dist/vault/vault-config.js +6 -9
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -24,13 +24,27 @@ axauth provides a consistent interface for managing credentials across multiple
|
|
|
24
24
|
## Installation
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
|
|
27
|
+
# CLI (global)
|
|
28
|
+
npm install -g axauth
|
|
28
29
|
# or
|
|
29
|
-
pnpm add axauth
|
|
30
|
+
pnpm add -g axauth
|
|
31
|
+
|
|
32
|
+
# Library usage (non-global)
|
|
33
|
+
npm install axauth
|
|
30
34
|
```
|
|
31
35
|
|
|
36
|
+
## Prerequisites
|
|
37
|
+
|
|
38
|
+
- Node.js 22+
|
|
39
|
+
- pnpm or npm
|
|
40
|
+
- jq for JSON examples
|
|
41
|
+
- Agent CLIs installed (as needed): `claude`, `codex`, `gemini`, `opencode`, `copilot`
|
|
42
|
+
- POSIX shell assumed in examples (bash/zsh); PowerShell needs syntax adjustments
|
|
43
|
+
|
|
32
44
|
## CLI Usage
|
|
33
45
|
|
|
46
|
+
Examples use long-form flags; short flags exist but prefer long for clarity.
|
|
47
|
+
|
|
34
48
|
```bash
|
|
35
49
|
# List agents and their auth status
|
|
36
50
|
axauth list
|
|
@@ -53,18 +67,34 @@ axauth remove-credentials --agent claude
|
|
|
53
67
|
axauth remove-credentials --agent claude --config-dir /tmp/config
|
|
54
68
|
```
|
|
55
69
|
|
|
70
|
+
## Output Formats
|
|
71
|
+
|
|
72
|
+
- `axauth list` outputs TSV by default; `--json` returns a JSON array
|
|
73
|
+
- `axauth vault fetch` outputs JSON by default; `--json` pretty-prints JSON
|
|
74
|
+
- `axauth token` outputs the raw token for piping
|
|
75
|
+
|
|
56
76
|
### Pipeline Examples
|
|
57
77
|
|
|
58
|
-
The
|
|
78
|
+
The `axauth list` command outputs TSV with a header row:
|
|
79
|
+
|
|
80
|
+
- Columns: `AGENT`, `STATUS`, `METHOD`
|
|
81
|
+
- `STATUS` values: `authenticated` | `not_configured`
|
|
59
82
|
|
|
60
83
|
```bash
|
|
61
84
|
# List all agents and their auth status
|
|
62
85
|
axauth list
|
|
63
|
-
|
|
64
|
-
# claude authenticated OAuth (max)
|
|
65
|
-
# codex authenticated ChatGPT OAuth
|
|
66
|
-
# ...
|
|
86
|
+
```
|
|
67
87
|
|
|
88
|
+
Output (TSV):
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
AGENT STATUS METHOD
|
|
92
|
+
claude authenticated OAuth (max)
|
|
93
|
+
codex authenticated ChatGPT OAuth
|
|
94
|
+
...
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```bash
|
|
68
98
|
# Filter to show only authenticated agents
|
|
69
99
|
axauth list | tail -n +2 | awk -F'\t' '$2 == "authenticated"'
|
|
70
100
|
|
|
@@ -80,6 +110,10 @@ curl -s -H "Authorization: Bearer $(axauth token --agent claude)" \
|
|
|
80
110
|
https://api.anthropic.com/api/oauth/usage | jq .
|
|
81
111
|
```
|
|
82
112
|
|
|
113
|
+
## Security Note: `--no-password`
|
|
114
|
+
|
|
115
|
+
`--no-password` uses a deterministic “default password” derived from the source code. This is intended for CI/CD convenience (e.g., storing the exported file as a secret), not for protecting credentials at rest.
|
|
116
|
+
|
|
83
117
|
## Library API
|
|
84
118
|
|
|
85
119
|
```typescript
|
|
@@ -170,9 +204,9 @@ Each agent adapter declares its storage capabilities:
|
|
|
170
204
|
| Agent | Keychain | File | Environment | Install API Key |
|
|
171
205
|
| -------- | :------: | :--: | :---------: | :-------------: |
|
|
172
206
|
| claude | macOS | Yes | Yes | No (env-only) |
|
|
173
|
-
| codex | macOS | Yes | Yes |
|
|
207
|
+
| codex | macOS | Yes | Yes | Yes |
|
|
174
208
|
| gemini | macOS | Yes | Yes | No (env-only) |
|
|
175
|
-
| opencode | No | Yes |
|
|
209
|
+
| opencode | No | Yes | No | Yes |
|
|
176
210
|
| copilot | macOS | Yes | Yes | No (env-only) |
|
|
177
211
|
|
|
178
212
|
**Notes:**
|
|
@@ -215,47 +249,24 @@ For CI/CD workflows, credentials can be passed via environment variables:
|
|
|
215
249
|
|
|
216
250
|
Use `installCredentialsFromEnvironmentVariable()` to install credentials from these variables programmatically.
|
|
217
251
|
|
|
218
|
-
##
|
|
252
|
+
## Custom Directory Behavior
|
|
219
253
|
|
|
220
|
-
|
|
254
|
+
Directory handling depends on whether the agent separates config and data:
|
|
221
255
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
| codex | Any name | `/tmp/my-config` |
|
|
226
|
-
| gemini | Must end with `.gemini` | `/tmp/home/.gemini` |
|
|
227
|
-
| copilot | Must end with `.copilot` | `/tmp/home/.copilot` |
|
|
228
|
-
| opencode | Must end with `opencode` | `/tmp/data/opencode` |
|
|
256
|
+
- **Shared config/data agents** (`claude`, `codex`, `gemini`, `copilot`): `--config-dir` and `--data-dir` are interchangeable and point to the same location.
|
|
257
|
+
- **Separate config/data agent** (`opencode`): `--config-dir` and `--data-dir` are independent.
|
|
258
|
+
If only one is provided, the other uses the default location and axauth emits a warning.
|
|
229
259
|
|
|
230
260
|
## Architecture
|
|
231
261
|
|
|
232
|
-
axauth follows
|
|
262
|
+
axauth follows an adapter architecture with a functional core:
|
|
233
263
|
|
|
234
|
-
|
|
235
|
-
src/
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
│ └── auth.ts # CLI command handlers
|
|
241
|
-
└── auth/
|
|
242
|
-
├── adapter.ts # AuthAdapter interface
|
|
243
|
-
├── types.ts # AuthStatus, Credentials types
|
|
244
|
-
├── registry.ts # Adapter registry and unified operations
|
|
245
|
-
└── agents/ # Agent-specific adapters
|
|
246
|
-
├── claude-code.ts
|
|
247
|
-
├── claude-code-storage.ts
|
|
248
|
-
├── codex.ts
|
|
249
|
-
├── codex-storage.ts
|
|
250
|
-
├── codex-config.ts
|
|
251
|
-
├── gemini.ts
|
|
252
|
-
├── gemini-storage.ts
|
|
253
|
-
├── gemini-auth-check.ts
|
|
254
|
-
├── copilot.ts
|
|
255
|
-
├── copilot-storage.ts
|
|
256
|
-
├── copilot-auth-check.ts
|
|
257
|
-
└── opencode.ts
|
|
258
|
-
```
|
|
264
|
+
- `src/auth/` — core auth domain logic, adapter interfaces, registry, shared utilities
|
|
265
|
+
- `src/auth/agents/` — agent-specific adapter implementations and storage/install/remove flows
|
|
266
|
+
- `src/commands/` — CLI command handlers (`list`, `token`, `export`, `install-credentials`, `vault`, etc.)
|
|
267
|
+
- `src/vault/` — axvault client/config integration for fetch/push workflows
|
|
268
|
+
- `src/crypto.ts` — credential encryption/decryption primitives (AES-256-GCM + PBKDF2)
|
|
269
|
+
- `src/index.ts` / `src/cli.ts` — library exports and CLI entrypoint
|
|
259
270
|
|
|
260
271
|
## Related Packages
|
|
261
272
|
|
package/dist/auth/adapter.d.ts
CHANGED
|
@@ -51,6 +51,8 @@ interface InstallOptions {
|
|
|
51
51
|
configDir?: string;
|
|
52
52
|
/** Custom data directory for credentials */
|
|
53
53
|
dataDir?: string;
|
|
54
|
+
/** Provider ID for multi-provider agents (required for OpenCode) */
|
|
55
|
+
provider?: string;
|
|
54
56
|
}
|
|
55
57
|
/**
|
|
56
58
|
* Options for credential removal.
|
|
@@ -24,14 +24,14 @@ const claudeCodeAdapter = {
|
|
|
24
24
|
const result = getEnvironmentCredentialsInternal();
|
|
25
25
|
if (!result)
|
|
26
26
|
return undefined;
|
|
27
|
-
return {
|
|
27
|
+
return { type: result.type, data: result.data };
|
|
28
28
|
},
|
|
29
29
|
findStoredCredentials() {
|
|
30
30
|
const result = findStoredCredentialsInternal();
|
|
31
31
|
if (!result)
|
|
32
32
|
return undefined;
|
|
33
33
|
return {
|
|
34
|
-
credentials: {
|
|
34
|
+
credentials: { type: result.type, data: result.data },
|
|
35
35
|
source: result.source,
|
|
36
36
|
};
|
|
37
37
|
},
|
|
@@ -51,7 +51,6 @@ function getEnvironmentCredentials() {
|
|
|
51
51
|
const environmentKey = getEnvironmentApiKey();
|
|
52
52
|
if (environmentKey) {
|
|
53
53
|
return {
|
|
54
|
-
agent: AGENT_ID,
|
|
55
54
|
type: "api-key",
|
|
56
55
|
data: { apiKey: environmentKey },
|
|
57
56
|
};
|
|
@@ -64,7 +63,6 @@ function extractKeychainCredentials() {
|
|
|
64
63
|
if (keychainAuth?.OPENAI_API_KEY) {
|
|
65
64
|
return {
|
|
66
65
|
credentials: {
|
|
67
|
-
agent: AGENT_ID,
|
|
68
66
|
type: "api-key",
|
|
69
67
|
data: { apiKey: keychainAuth.OPENAI_API_KEY },
|
|
70
68
|
},
|
|
@@ -74,7 +72,6 @@ function extractKeychainCredentials() {
|
|
|
74
72
|
if (keychainAuth?.tokens) {
|
|
75
73
|
return {
|
|
76
74
|
credentials: {
|
|
77
|
-
agent: AGENT_ID,
|
|
78
75
|
type: "oauth-credentials",
|
|
79
76
|
data: keychainAuth,
|
|
80
77
|
},
|
|
@@ -89,7 +86,6 @@ function extractFileCredentials() {
|
|
|
89
86
|
if (fileAuth?.OPENAI_API_KEY) {
|
|
90
87
|
return {
|
|
91
88
|
credentials: {
|
|
92
|
-
agent: AGENT_ID,
|
|
93
89
|
type: "api-key",
|
|
94
90
|
data: { apiKey: fileAuth.OPENAI_API_KEY },
|
|
95
91
|
},
|
|
@@ -99,7 +95,6 @@ function extractFileCredentials() {
|
|
|
99
95
|
if (fileAuth?.tokens) {
|
|
100
96
|
return {
|
|
101
97
|
credentials: {
|
|
102
|
-
agent: AGENT_ID,
|
|
103
98
|
type: "oauth-credentials",
|
|
104
99
|
data: fileAuth,
|
|
105
100
|
},
|
|
@@ -9,13 +9,24 @@
|
|
|
9
9
|
declare function getConfigDirectory(): string;
|
|
10
10
|
/** Get the default config file path */
|
|
11
11
|
declare function getConfigFilePath(): string;
|
|
12
|
-
/**
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Load token from keychain.
|
|
14
|
+
*
|
|
15
|
+
* Searches by service only — the Copilot CLI stores credentials under the
|
|
16
|
+
* GitHub username (from the OAuth response), which may differ in case from
|
|
17
|
+
* the OS username ($USER). Service-only search avoids that mismatch.
|
|
18
|
+
*/
|
|
19
|
+
declare function loadKeychainToken(): string | undefined;
|
|
14
20
|
/** Save token to keychain */
|
|
15
21
|
declare function saveKeychainToken(token: string, host?: string): boolean;
|
|
16
22
|
/** Delete token from keychain */
|
|
17
|
-
declare function deleteKeychainToken(
|
|
18
|
-
/**
|
|
23
|
+
declare function deleteKeychainToken(): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Load token from config file.
|
|
26
|
+
*
|
|
27
|
+
* Matches by host prefix — the Copilot CLI stores tokens under the GitHub
|
|
28
|
+
* username (from OAuth), which may differ in case from $USER.
|
|
29
|
+
*/
|
|
19
30
|
declare function loadFileToken(host?: string): string | undefined;
|
|
20
31
|
/** Save token to config file */
|
|
21
32
|
declare function saveFileToken(token: string, host?: string): boolean;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { existsSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { ensureDirectory, loadJsonFile, saveJsonFile, } from "../file-storage.js";
|
|
11
|
-
import {
|
|
11
|
+
import { deleteFromKeychainByService, isMacOS, loadFromKeychainByService, saveToKeychain, } from "../keychain.js";
|
|
12
12
|
import { getResolvedConfigDirectory } from "../resolve-config-directory.js";
|
|
13
13
|
const KEYCHAIN_SERVICE = "copilot-cli";
|
|
14
14
|
const DEFAULT_HOST = "https://github.com";
|
|
@@ -24,30 +24,28 @@ function getConfigFilePath() {
|
|
|
24
24
|
function getUsername() {
|
|
25
25
|
return process.env.USER ?? process.env.USERNAME ?? "user";
|
|
26
26
|
}
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return loadFromKeychain(KEYCHAIN_SERVICE, account);
|
|
27
|
+
/**
|
|
28
|
+
* Load token from keychain.
|
|
29
|
+
*
|
|
30
|
+
* Searches by service only — the Copilot CLI stores credentials under the
|
|
31
|
+
* GitHub username (from the OAuth response), which may differ in case from
|
|
32
|
+
* the OS username ($USER). Service-only search avoids that mismatch.
|
|
33
|
+
*/
|
|
34
|
+
function loadKeychainToken() {
|
|
35
|
+
return loadFromKeychainByService(KEYCHAIN_SERVICE);
|
|
37
36
|
}
|
|
38
37
|
/** Save token to keychain */
|
|
39
38
|
function saveKeychainToken(token, host = DEFAULT_HOST) {
|
|
40
39
|
if (!isMacOS())
|
|
41
40
|
return false;
|
|
42
|
-
|
|
41
|
+
// Remove any existing entry first (handles username case mismatches)
|
|
42
|
+
deleteFromKeychainByService(KEYCHAIN_SERVICE);
|
|
43
|
+
const account = `${host}:${getUsername()}`;
|
|
43
44
|
return saveToKeychain(KEYCHAIN_SERVICE, account, token);
|
|
44
45
|
}
|
|
45
46
|
/** Delete token from keychain */
|
|
46
|
-
function deleteKeychainToken(
|
|
47
|
-
|
|
48
|
-
return false;
|
|
49
|
-
const account = getKeychainAccount(host);
|
|
50
|
-
return deleteFromKeychain(KEYCHAIN_SERVICE, account);
|
|
47
|
+
function deleteKeychainToken() {
|
|
48
|
+
return deleteFromKeychainByService(KEYCHAIN_SERVICE);
|
|
51
49
|
}
|
|
52
50
|
/** Load config file */
|
|
53
51
|
function loadConfig() {
|
|
@@ -57,17 +55,36 @@ function loadConfig() {
|
|
|
57
55
|
function saveConfig(config) {
|
|
58
56
|
return saveJsonFile(getConfigFilePath(), config, { mode: 0o600 });
|
|
59
57
|
}
|
|
60
|
-
/**
|
|
58
|
+
/** Find the first token key matching a host prefix */
|
|
59
|
+
function findTokenKeyForHost(tokens, host) {
|
|
60
|
+
const prefix = `${host}:`;
|
|
61
|
+
return Object.keys(tokens).find((key) => key.startsWith(prefix));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Load token from config file.
|
|
65
|
+
*
|
|
66
|
+
* Matches by host prefix — the Copilot CLI stores tokens under the GitHub
|
|
67
|
+
* username (from OAuth), which may differ in case from $USER.
|
|
68
|
+
*/
|
|
61
69
|
function loadFileToken(host = DEFAULT_HOST) {
|
|
62
70
|
const config = loadConfig();
|
|
63
|
-
|
|
71
|
+
if (!config?.copilot_tokens)
|
|
72
|
+
return undefined;
|
|
73
|
+
const key = findTokenKeyForHost(config.copilot_tokens, host);
|
|
74
|
+
return key ? config.copilot_tokens[key] : undefined;
|
|
64
75
|
}
|
|
65
76
|
/** Save token to config file */
|
|
66
77
|
function saveFileToken(token, host = DEFAULT_HOST) {
|
|
67
78
|
const config = loadConfig() ?? {};
|
|
68
79
|
const key = `${host}:${getUsername()}`;
|
|
80
|
+
// Rebuild tokens without any stale entry for this host (handles username case mismatches)
|
|
81
|
+
const existing = config.copilot_tokens ?? {};
|
|
82
|
+
const staleKey = findTokenKeyForHost(existing, host);
|
|
83
|
+
const filtered = staleKey && staleKey !== key
|
|
84
|
+
? Object.fromEntries(Object.entries(existing).filter(([k]) => k !== staleKey))
|
|
85
|
+
: existing;
|
|
69
86
|
config.copilot_tokens = {
|
|
70
|
-
...
|
|
87
|
+
...filtered,
|
|
71
88
|
[key]: token,
|
|
72
89
|
};
|
|
73
90
|
config.store_token_plaintext = true;
|
|
@@ -78,12 +95,10 @@ function deleteFileToken(host = DEFAULT_HOST) {
|
|
|
78
95
|
const config = loadConfig();
|
|
79
96
|
if (!config?.copilot_tokens)
|
|
80
97
|
return false;
|
|
81
|
-
const key =
|
|
82
|
-
if (!
|
|
98
|
+
const key = findTokenKeyForHost(config.copilot_tokens, host);
|
|
99
|
+
if (!key)
|
|
83
100
|
return false;
|
|
84
|
-
// Remove the token by filtering out the key
|
|
85
101
|
const remainingTokens = Object.fromEntries(Object.entries(config.copilot_tokens).filter(([k]) => k !== key));
|
|
86
|
-
// If no tokens left, remove the whole section
|
|
87
102
|
if (Object.keys(remainingTokens).length === 0) {
|
|
88
103
|
const rest = Object.fromEntries(Object.entries(config).filter(([k]) => k !== "copilot_tokens" && k !== "store_token_plaintext"));
|
|
89
104
|
return saveConfig(rest);
|
|
@@ -27,8 +27,8 @@ const copilotAdapter = {
|
|
|
27
27
|
}
|
|
28
28
|
const methodMap = {
|
|
29
29
|
environment: `Token (${result.environmentVariable})`,
|
|
30
|
-
keychain: "
|
|
31
|
-
file: "
|
|
30
|
+
keychain: "OAuth (keychain)",
|
|
31
|
+
file: "OAuth (file)",
|
|
32
32
|
"gh-cli": "GitHub CLI",
|
|
33
33
|
};
|
|
34
34
|
return {
|
|
@@ -42,7 +42,6 @@ const copilotAdapter = {
|
|
|
42
42
|
if (!token)
|
|
43
43
|
return undefined;
|
|
44
44
|
return {
|
|
45
|
-
agent: AGENT_ID,
|
|
46
45
|
type: "oauth-token",
|
|
47
46
|
data: { accessToken: token },
|
|
48
47
|
};
|
|
@@ -55,7 +54,6 @@ const copilotAdapter = {
|
|
|
55
54
|
const source = result.source === "keychain" ? "keychain" : "file";
|
|
56
55
|
return {
|
|
57
56
|
credentials: {
|
|
58
|
-
agent: AGENT_ID,
|
|
59
57
|
type: "oauth-token",
|
|
60
58
|
data: { accessToken: result.token },
|
|
61
59
|
},
|
|
@@ -34,7 +34,6 @@ const geminiAdapter = {
|
|
|
34
34
|
if (!result?.data)
|
|
35
35
|
return undefined;
|
|
36
36
|
return {
|
|
37
|
-
agent: AGENT_ID,
|
|
38
37
|
type: "api-key",
|
|
39
38
|
data: result.data,
|
|
40
39
|
};
|
|
@@ -47,7 +46,6 @@ const geminiAdapter = {
|
|
|
47
46
|
if (result.source === "api-key" || result.source === "vertex-ai") {
|
|
48
47
|
return {
|
|
49
48
|
credentials: {
|
|
50
|
-
agent: AGENT_ID,
|
|
51
49
|
type: "api-key",
|
|
52
50
|
data: result.data,
|
|
53
51
|
},
|
|
@@ -57,7 +55,6 @@ const geminiAdapter = {
|
|
|
57
55
|
// OAuth credentials from keychain or file - return source separately
|
|
58
56
|
return {
|
|
59
57
|
credentials: {
|
|
60
|
-
agent: AGENT_ID,
|
|
61
58
|
type: "oauth-credentials",
|
|
62
59
|
data: result.data,
|
|
63
60
|
},
|
|
@@ -21,15 +21,16 @@ function installCredentials(creds, options) {
|
|
|
21
21
|
if (!existsSync(targetDirectory)) {
|
|
22
22
|
mkdirSync(targetDirectory, { recursive: true, mode: 0o700 });
|
|
23
23
|
}
|
|
24
|
-
// OpenCode requires per-provider credentials
|
|
25
|
-
|
|
24
|
+
// OpenCode requires per-provider credentials via options
|
|
25
|
+
const rawProvider = options?.provider;
|
|
26
|
+
if (!rawProvider) {
|
|
26
27
|
return {
|
|
27
28
|
ok: false,
|
|
28
|
-
message: "
|
|
29
|
+
message: "OpenCode requires provider option for credential installation.",
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
// Validate provider key is non-empty (defensive check for programmatic use)
|
|
32
|
-
const provider =
|
|
33
|
+
const provider = rawProvider.trim();
|
|
33
34
|
if (provider.length === 0) {
|
|
34
35
|
return { ok: false, message: "Provider name cannot be empty" };
|
|
35
36
|
}
|
|
@@ -76,9 +76,7 @@ const opencodeAdapter = {
|
|
|
76
76
|
}
|
|
77
77
|
return {
|
|
78
78
|
credentials: {
|
|
79
|
-
agent: AGENT_ID,
|
|
80
79
|
type: mapToCredentialType(parsed.data),
|
|
81
|
-
provider: normalizedProvider,
|
|
82
80
|
data: entry,
|
|
83
81
|
},
|
|
84
82
|
source: "file",
|
|
@@ -108,9 +106,7 @@ const opencodeAdapter = {
|
|
|
108
106
|
if (!parsed.success)
|
|
109
107
|
return undefined;
|
|
110
108
|
return {
|
|
111
|
-
agent: AGENT_ID,
|
|
112
109
|
type: mapToCredentialType(parsed.data),
|
|
113
|
-
provider: targetProvider,
|
|
114
110
|
data: entry,
|
|
115
111
|
};
|
|
116
112
|
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Build refreshed credentials
|
|
2
|
+
* Build refreshed credentials from refresh operation output.
|
|
3
3
|
*/
|
|
4
4
|
import type { Credentials } from "./types.js";
|
|
5
5
|
/**
|
|
6
|
-
* Build refreshed credentials
|
|
6
|
+
* Build refreshed credentials from the refresh operation.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Returns the refreshed type and data. Provider context is handled
|
|
9
|
+
* by the caller, not embedded in credentials.
|
|
10
10
|
*
|
|
11
|
-
* @param
|
|
11
|
+
* @param _originalCreds - Original credentials (reserved for future validation)
|
|
12
12
|
* @param refreshedCreds - Credentials returned from refresh operation
|
|
13
13
|
* @returns Built credentials or error message
|
|
14
14
|
*/
|
|
15
|
-
declare function buildRefreshedCredentials(
|
|
15
|
+
declare function buildRefreshedCredentials(_originalCreds: Credentials, refreshedCreds: Credentials): {
|
|
16
16
|
ok: true;
|
|
17
17
|
credentials: Credentials;
|
|
18
18
|
} | {
|
|
@@ -1,60 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Build refreshed credentials
|
|
2
|
+
* Build refreshed credentials from refresh operation output.
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
|
-
* Build refreshed credentials
|
|
5
|
+
* Build refreshed credentials from the refresh operation.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Returns the refreshed type and data. Provider context is handled
|
|
8
|
+
* by the caller, not embedded in credentials.
|
|
9
9
|
*
|
|
10
|
-
* @param
|
|
10
|
+
* @param _originalCreds - Original credentials (reserved for future validation)
|
|
11
11
|
* @param refreshedCreds - Credentials returned from refresh operation
|
|
12
12
|
* @returns Built credentials or error message
|
|
13
13
|
*/
|
|
14
|
-
function buildRefreshedCredentials(
|
|
15
|
-
// OpenCode per-provider refresh: verify provider matches and use data directly
|
|
16
|
-
if (originalCreds.agent === "opencode" && originalCreds.provider) {
|
|
17
|
-
const normalizedProvider = originalCreds.provider.trim();
|
|
18
|
-
// refreshedCreds from loadCredentialsFromDirectory is now per-provider format:
|
|
19
|
-
// - refreshedCreds.provider = the provider name
|
|
20
|
-
// - refreshedCreds.data = the provider's auth entry directly
|
|
21
|
-
if (refreshedCreds.agent !== "opencode") {
|
|
22
|
-
return {
|
|
23
|
-
ok: false,
|
|
24
|
-
error: `Provider '${normalizedProvider}' not found in refreshed credentials`,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
// TypeScript narrows to OpenCodeCredentials which has provider as required
|
|
28
|
-
// (z.string().trim().min(1) in axshared) - NOT optional despite Credentials union
|
|
29
|
-
if (refreshedCreds.provider.trim() !== normalizedProvider) {
|
|
30
|
-
return {
|
|
31
|
-
ok: false,
|
|
32
|
-
error: `Provider '${normalizedProvider}' not found in refreshed credentials`,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
return {
|
|
36
|
-
ok: true,
|
|
37
|
-
credentials: {
|
|
38
|
-
agent: originalCreds.agent,
|
|
39
|
-
type: refreshedCreds.type,
|
|
40
|
-
provider: normalizedProvider,
|
|
41
|
-
data: refreshedCreds.data,
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
// Standard agent refresh: use refreshed data
|
|
46
|
-
// Both originalCreds and refreshedCreds are standard agents here (not OpenCode)
|
|
47
|
-
if (originalCreds.agent === "opencode") {
|
|
48
|
-
// TypeScript narrowing: unreachable, but satisfies type checker
|
|
49
|
-
return {
|
|
50
|
-
ok: false,
|
|
51
|
-
error: "Unexpected OpenCode credential without provider",
|
|
52
|
-
};
|
|
53
|
-
}
|
|
14
|
+
function buildRefreshedCredentials(_originalCreds, refreshedCreds) {
|
|
54
15
|
return {
|
|
55
16
|
ok: true,
|
|
56
17
|
credentials: {
|
|
57
|
-
agent: originalCreds.agent,
|
|
58
18
|
type: refreshedCreds.type,
|
|
59
19
|
data: refreshedCreds.data,
|
|
60
20
|
},
|
|
@@ -31,7 +31,7 @@ function extractCredsFromDirectory(agentId, directory, fileName, transform) {
|
|
|
31
31
|
const data = transform ? transform(record) : record;
|
|
32
32
|
if (!data)
|
|
33
33
|
return undefined;
|
|
34
|
-
return {
|
|
34
|
+
return { type: "oauth-credentials", data };
|
|
35
35
|
}
|
|
36
36
|
catch {
|
|
37
37
|
return undefined;
|
|
@@ -63,13 +63,6 @@ async function installCredentialsFromEnvironmentCore(parameters) {
|
|
|
63
63
|
}
|
|
64
64
|
// Install all credentials
|
|
65
65
|
for (const credentials of credentialsList.credentials) {
|
|
66
|
-
// Defense-in-depth: verify each credential matches the target agent
|
|
67
|
-
if (credentials.agent !== parameters.agent) {
|
|
68
|
-
return {
|
|
69
|
-
ok: false,
|
|
70
|
-
error: `Credential agent mismatch: expected '${parameters.agent}', got '${credentials.agent}'`,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
66
|
const result = parameters.installCredentials(credentials, {
|
|
74
67
|
configDir: parameters.configDir,
|
|
75
68
|
dataDir: parameters.dataDir,
|
package/dist/auth/keychain.d.ts
CHANGED
|
@@ -9,4 +9,8 @@ declare function loadFromKeychain(service: string, account: string): string | un
|
|
|
9
9
|
declare function saveToKeychain(service: string, account: string, data: string): boolean;
|
|
10
10
|
/** Delete entry from macOS Keychain */
|
|
11
11
|
declare function deleteFromKeychain(service: string, account: string): boolean;
|
|
12
|
-
|
|
12
|
+
/** Load data from macOS Keychain by service only (any account) */
|
|
13
|
+
declare function loadFromKeychainByService(service: string): string | undefined;
|
|
14
|
+
/** Delete entry from macOS Keychain by service only (first match) */
|
|
15
|
+
declare function deleteFromKeychainByService(service: string): boolean;
|
|
16
|
+
export { deleteFromKeychain, deleteFromKeychainByService, isMacOS, loadFromKeychain, loadFromKeychainByService, saveToKeychain, };
|
package/dist/auth/keychain.js
CHANGED
|
@@ -53,4 +53,32 @@ function deleteFromKeychain(service, account) {
|
|
|
53
53
|
return false;
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
-
|
|
56
|
+
/** Load data from macOS Keychain by service only (any account) */
|
|
57
|
+
function loadFromKeychainByService(service) {
|
|
58
|
+
if (!isMacOS()) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const result = execFileSync("security", ["find-generic-password", "-s", service, "-w"], { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
63
|
+
return result.trim();
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Delete entry from macOS Keychain by service only (first match) */
|
|
70
|
+
function deleteFromKeychainByService(service) {
|
|
71
|
+
if (!isMacOS()) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
execFileSync("security", ["delete-generic-password", "-s", service], {
|
|
76
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
77
|
+
});
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export { deleteFromKeychain, deleteFromKeychainByService, isMacOS, loadFromKeychain, loadFromKeychainByService, saveToKeychain, };
|