axauth 3.1.2 → 3.1.5
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/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 +12 -5
- 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 +1 -1
- 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 +3 -2
- package/dist/vault/vault-client.d.ts +3 -1
- package/dist/vault/vault-client.js +3 -7
- package/dist/vault/vault-config.d.ts +2 -6
- package/dist/vault/vault-config.js +6 -9
- package/package.json +3 -3
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, };
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Refreshes credentials by running the agent in an isolated temp config,
|
|
5
5
|
* triggering its internal auth refresh mechanism.
|
|
6
6
|
*/
|
|
7
|
+
import type { AgentCli } from "axshared";
|
|
7
8
|
import { type Credentials } from "./types.js";
|
|
8
9
|
/** Options for credential refresh */
|
|
9
10
|
interface RefreshOptions {
|
|
@@ -32,7 +33,7 @@ type RefreshResult = {
|
|
|
32
33
|
* @param options - Refresh options (timeout, provider for multi-provider agents)
|
|
33
34
|
* @returns RefreshResult with new credentials or error
|
|
34
35
|
*/
|
|
35
|
-
declare function refreshCredentials(creds: Credentials, options?: RefreshOptions): Promise<RefreshResult>;
|
|
36
|
+
declare function refreshCredentials(agentId: AgentCli, creds: Credentials, options?: RefreshOptions): Promise<RefreshResult>;
|
|
36
37
|
/** Result of refresh and persist operation */
|
|
37
38
|
type RefreshAndPersistResult = {
|
|
38
39
|
ok: true;
|
|
@@ -42,7 +43,7 @@ type RefreshAndPersistResult = {
|
|
|
42
43
|
staleCredentials: Credentials;
|
|
43
44
|
};
|
|
44
45
|
/** Install function type for refreshAndPersist */
|
|
45
|
-
type InstallFunction = (creds: Credentials, options?: {
|
|
46
|
+
type InstallFunction = (agentId: AgentCli, creds: Credentials, options?: {
|
|
46
47
|
storage?: "keychain" | "file";
|
|
47
48
|
}) => {
|
|
48
49
|
ok: boolean;
|
|
@@ -55,12 +56,13 @@ type InstallFunction = (creds: Credentials, options?: {
|
|
|
55
56
|
* 1. Refreshes credentials via agent subprocess
|
|
56
57
|
* 2. Persists refreshed credentials to the specified storage location
|
|
57
58
|
*
|
|
59
|
+
* @param agentId - Agent identifier
|
|
58
60
|
* @param creds - Original credentials
|
|
59
61
|
* @param installFunction - Function to install credentials (avoids circular import)
|
|
60
62
|
* @param options - Refresh options including storage type
|
|
61
63
|
* @returns Refreshed credentials or original credentials on failure
|
|
62
64
|
*/
|
|
63
|
-
declare function refreshAndPersist(creds: Credentials, installFunction: InstallFunction, options?: RefreshOptions): Promise<RefreshAndPersistResult>;
|
|
65
|
+
declare function refreshAndPersist(agentId: AgentCli, creds: Credentials, installFunction: InstallFunction, options?: RefreshOptions): Promise<RefreshAndPersistResult>;
|
|
64
66
|
/** Result of refreshing an opaque credential blob */
|
|
65
67
|
type RefreshBlobResult = {
|
|
66
68
|
ok: true;
|
|
@@ -70,6 +72,11 @@ type RefreshBlobResult = {
|
|
|
70
72
|
ok: false;
|
|
71
73
|
error: string;
|
|
72
74
|
};
|
|
75
|
+
/** Options for refreshBlob, extending refresh options with routing metadata */
|
|
76
|
+
interface RefreshBlobOptions extends RefreshOptions {
|
|
77
|
+
/** Agent identifier (required — comes from vault column) */
|
|
78
|
+
agent: AgentCli;
|
|
79
|
+
}
|
|
73
80
|
/**
|
|
74
81
|
* Refresh credentials from an opaque blob.
|
|
75
82
|
*
|
|
@@ -78,9 +85,9 @@ type RefreshBlobResult = {
|
|
|
78
85
|
* never needs to understand the blob's internal structure.
|
|
79
86
|
*
|
|
80
87
|
* @param blob - Opaque credential blob (expected to be a Credentials object)
|
|
81
|
-
* @param options - Refresh options
|
|
88
|
+
* @param options - Refresh options with agent and optional provider
|
|
82
89
|
* @returns RefreshBlobResult with new blob and expiresAt, or error
|
|
83
90
|
*/
|
|
84
|
-
declare function refreshBlob(blob: unknown, options
|
|
91
|
+
declare function refreshBlob(blob: unknown, options: RefreshBlobOptions): Promise<RefreshBlobResult>;
|
|
85
92
|
export { refreshAndPersist, refreshBlob, refreshCredentials };
|
|
86
93
|
export type { RefreshAndPersistResult, RefreshBlobResult, RefreshOptions, RefreshResult, };
|
|
@@ -32,7 +32,7 @@ async function safeCleanup(directory) {
|
|
|
32
32
|
* @param options - Refresh options (timeout, provider for multi-provider agents)
|
|
33
33
|
* @returns RefreshResult with new credentials or error
|
|
34
34
|
*/
|
|
35
|
-
async function refreshCredentials(creds, options) {
|
|
35
|
+
async function refreshCredentials(agentId, creds, options) {
|
|
36
36
|
const timeoutMs = options?.timeout ?? DEFAULT_REFRESH_TIMEOUT_MS;
|
|
37
37
|
const deadlineMs = Date.now() + timeoutMs;
|
|
38
38
|
const resolved = resolveRefreshCredentials(creds);
|
|
@@ -41,12 +41,8 @@ async function refreshCredentials(creds, options) {
|
|
|
41
41
|
}
|
|
42
42
|
// Run agent with a minimal prompt to trigger internal token refresh.
|
|
43
43
|
// preserveConfigDirectory keeps the temp dir so we can read refreshed credentials.
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
(resolved.credentials.agent === "opencode"
|
|
47
|
-
? resolved.credentials.provider
|
|
48
|
-
: undefined);
|
|
49
|
-
const resultPromise = runAgent(resolved.credentials.agent, {
|
|
44
|
+
const provider = options?.provider;
|
|
45
|
+
const resultPromise = runAgent(agentId, {
|
|
50
46
|
prompt: "ping",
|
|
51
47
|
credentials: resolved.credentials,
|
|
52
48
|
provider,
|
|
@@ -96,7 +92,7 @@ async function refreshCredentials(creds, options) {
|
|
|
96
92
|
if (!directories) {
|
|
97
93
|
return { ok: false, error: "No directories in execution metadata" };
|
|
98
94
|
}
|
|
99
|
-
const refreshedCredentials = await waitForRefreshedCredentials(
|
|
95
|
+
const refreshedCredentials = await waitForRefreshedCredentials(agentId, directories, deadlineMs, { provider });
|
|
100
96
|
if (!refreshedCredentials) {
|
|
101
97
|
return { ok: false, error: "No credentials found after refresh" };
|
|
102
98
|
}
|
|
@@ -123,20 +119,23 @@ async function refreshCredentials(creds, options) {
|
|
|
123
119
|
* 1. Refreshes credentials via agent subprocess
|
|
124
120
|
* 2. Persists refreshed credentials to the specified storage location
|
|
125
121
|
*
|
|
122
|
+
* @param agentId - Agent identifier
|
|
126
123
|
* @param creds - Original credentials
|
|
127
124
|
* @param installFunction - Function to install credentials (avoids circular import)
|
|
128
125
|
* @param options - Refresh options including storage type
|
|
129
126
|
* @returns Refreshed credentials or original credentials on failure
|
|
130
127
|
*/
|
|
131
|
-
async function refreshAndPersist(creds, installFunction, options) {
|
|
132
|
-
const result = await refreshCredentials(creds, options);
|
|
128
|
+
async function refreshAndPersist(agentId, creds, installFunction, options) {
|
|
129
|
+
const result = await refreshCredentials(agentId, creds, options);
|
|
133
130
|
if (!result.ok) {
|
|
134
131
|
console.error(`Warning: Token refresh failed: ${result.error}`);
|
|
135
132
|
return { ok: false, staleCredentials: creds };
|
|
136
133
|
}
|
|
137
134
|
// Persist refreshed credentials back to storage
|
|
138
135
|
const storage = options?.storage ?? "file";
|
|
139
|
-
const installResult = installFunction(result.credentials, {
|
|
136
|
+
const installResult = installFunction(agentId, result.credentials, {
|
|
137
|
+
storage,
|
|
138
|
+
});
|
|
140
139
|
if (!installResult.ok) {
|
|
141
140
|
console.error(`Warning: Failed to save refreshed credentials: ${installResult.message}`);
|
|
142
141
|
}
|
|
@@ -150,7 +149,7 @@ async function refreshAndPersist(creds, installFunction, options) {
|
|
|
150
149
|
* never needs to understand the blob's internal structure.
|
|
151
150
|
*
|
|
152
151
|
* @param blob - Opaque credential blob (expected to be a Credentials object)
|
|
153
|
-
* @param options - Refresh options
|
|
152
|
+
* @param options - Refresh options with agent and optional provider
|
|
154
153
|
* @returns RefreshBlobResult with new blob and expiresAt, or error
|
|
155
154
|
*/
|
|
156
155
|
async function refreshBlob(blob, options) {
|
|
@@ -160,7 +159,7 @@ async function refreshBlob(blob, options) {
|
|
|
160
159
|
return { ok: false, error: "Invalid credential format" };
|
|
161
160
|
}
|
|
162
161
|
// Refresh using existing function
|
|
163
|
-
const result = await refreshCredentials(credentials, options);
|
|
162
|
+
const result = await refreshCredentials(options.agent, credentials, options);
|
|
164
163
|
if (!result.ok) {
|
|
165
164
|
return { ok: false, error: result.error };
|
|
166
165
|
}
|
package/dist/auth/registry.d.ts
CHANGED
|
@@ -92,7 +92,7 @@ declare function findCredentialsFromDirectory(agentId: AgentCli, options: Extrac
|
|
|
92
92
|
* const result = installCredentials(creds, { storage: "keychain" });
|
|
93
93
|
* if (!result.ok) { console.error(result.message); }
|
|
94
94
|
*/
|
|
95
|
-
declare function installCredentials(creds: Credentials, options?: InstallOptions): OperationResult;
|
|
95
|
+
declare function installCredentials(agentId: AgentCli, creds: Credentials, options?: InstallOptions): OperationResult;
|
|
96
96
|
/**
|
|
97
97
|
* Remove credentials for an agent.
|
|
98
98
|
*
|
|
@@ -117,7 +117,7 @@ declare function removeCredentials(agentId: AgentCli, options?: RemoveOptions):
|
|
|
117
117
|
* const token = await getAccessToken(creds);
|
|
118
118
|
* const token = await getAccessToken(creds, { skipRefresh: true });
|
|
119
119
|
*/
|
|
120
|
-
declare function getAccessToken(creds: Credentials, options?: TokenOptions): Promise<string | undefined>;
|
|
120
|
+
declare function getAccessToken(agentId: AgentCli, creds: Credentials, options?: TokenOptions): Promise<string | undefined>;
|
|
121
121
|
/**
|
|
122
122
|
* Get access token for an agent by ID.
|
|
123
123
|
*
|
|
@@ -136,7 +136,7 @@ declare function getAgentAccessToken(agentId: AgentCli, options?: TokenOptions):
|
|
|
136
136
|
* const env = credentialsToEnvironment(creds);
|
|
137
137
|
* Object.assign(process.env, env);
|
|
138
138
|
*/
|
|
139
|
-
declare function credentialsToEnvironment(creds: Credentials): Record<string, string>;
|
|
139
|
+
declare function credentialsToEnvironment(agentId: AgentCli, creds: Credentials): Record<string, string>;
|
|
140
140
|
/**
|
|
141
141
|
* Get the conventional environment variable name for agent credentials.
|
|
142
142
|
*
|
package/dist/auth/registry.js
CHANGED
|
@@ -128,8 +128,8 @@ function findCredentialsFromDirectory(agentId, options) {
|
|
|
128
128
|
* const result = installCredentials(creds, { storage: "keychain" });
|
|
129
129
|
* if (!result.ok) { console.error(result.message); }
|
|
130
130
|
*/
|
|
131
|
-
function installCredentials(creds, options) {
|
|
132
|
-
return ADAPTERS[
|
|
131
|
+
function installCredentials(agentId, creds, options) {
|
|
132
|
+
return ADAPTERS[agentId].installCredentials(creds, options);
|
|
133
133
|
}
|
|
134
134
|
/**
|
|
135
135
|
* Remove credentials for an agent.
|
|
@@ -157,8 +157,8 @@ function removeCredentials(agentId, options) {
|
|
|
157
157
|
* const token = await getAccessToken(creds);
|
|
158
158
|
* const token = await getAccessToken(creds, { skipRefresh: true });
|
|
159
159
|
*/
|
|
160
|
-
async function getAccessToken(creds, options) {
|
|
161
|
-
const token = ADAPTERS[
|
|
160
|
+
async function getAccessToken(agentId, creds, options) {
|
|
161
|
+
const token = ADAPTERS[agentId].getAccessToken(creds, options);
|
|
162
162
|
// No token found
|
|
163
163
|
if (!token)
|
|
164
164
|
return undefined;
|
|
@@ -177,13 +177,13 @@ async function getAccessToken(creds, options) {
|
|
|
177
177
|
if (expired !== true)
|
|
178
178
|
return token; // Valid or no expiry info
|
|
179
179
|
// Refresh and persist
|
|
180
|
-
const result = await refreshAndPersist(creds, installCredentials, {
|
|
180
|
+
const result = await refreshAndPersist(agentId, creds, (aid, c, installOptions) => installCredentials(aid, c, installOptions), {
|
|
181
181
|
timeout: options?.refreshTimeout,
|
|
182
182
|
provider: options?.provider,
|
|
183
183
|
storage: options?.storage,
|
|
184
184
|
});
|
|
185
185
|
const finalCreds = result.ok ? result.credentials : result.staleCredentials;
|
|
186
|
-
return ADAPTERS[
|
|
186
|
+
return ADAPTERS[agentId].getAccessToken(finalCreds, options);
|
|
187
187
|
}
|
|
188
188
|
/**
|
|
189
189
|
* Get access token for an agent by ID.
|
|
@@ -202,7 +202,7 @@ async function getAgentAccessToken(agentId, options) {
|
|
|
202
202
|
if (!stored)
|
|
203
203
|
return undefined;
|
|
204
204
|
// Pass the source to getAccessToken so refresh preserves storage location
|
|
205
|
-
return getAccessToken(stored.credentials, {
|
|
205
|
+
return getAccessToken(agentId, stored.credentials, {
|
|
206
206
|
...options,
|
|
207
207
|
storage: options?.storage ?? stored.source,
|
|
208
208
|
});
|
|
@@ -214,8 +214,8 @@ async function getAgentAccessToken(agentId, options) {
|
|
|
214
214
|
* const env = credentialsToEnvironment(creds);
|
|
215
215
|
* Object.assign(process.env, env);
|
|
216
216
|
*/
|
|
217
|
-
function credentialsToEnvironment(creds) {
|
|
218
|
-
return ADAPTERS[
|
|
217
|
+
function credentialsToEnvironment(agentId, creds) {
|
|
218
|
+
return ADAPTERS[agentId].credentialsToEnvironment(creds);
|
|
219
219
|
}
|
|
220
220
|
/**
|
|
221
221
|
* Get the conventional environment variable name for agent credentials.
|
|
@@ -251,7 +251,7 @@ function getCredentialsEnvironmentVariableName(agent) {
|
|
|
251
251
|
function installCredentialsFromEnvironmentVariable(agent, options) {
|
|
252
252
|
return installCredentialsFromEnvironmentCore({
|
|
253
253
|
agent,
|
|
254
|
-
installCredentials,
|
|
254
|
+
installCredentials: (creds, installOptions) => installCredentials(agent, creds, installOptions),
|
|
255
255
|
configDir: options?.configDir,
|
|
256
256
|
dataDir: options?.dataDir,
|
|
257
257
|
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Polls for refreshed credentials to appear after agent execution.
|
|
3
3
|
*/
|
|
4
|
+
import type { AgentCli } from "axshared";
|
|
4
5
|
import type { ExecutionDirectories } from "axexec";
|
|
5
6
|
import type { Credentials } from "./types.js";
|
|
6
7
|
interface WaitOptions {
|
|
7
8
|
/** Provider ID for multi-provider agents (OpenCode) */
|
|
8
9
|
provider?: string;
|
|
9
10
|
}
|
|
10
|
-
declare function waitForRefreshedCredentials(agent:
|
|
11
|
+
declare function waitForRefreshedCredentials(agent: AgentCli, directories: ExecutionDirectories, deadlineMs: number, options?: WaitOptions): Promise<Credentials | undefined>;
|
|
11
12
|
export { waitForRefreshedCredentials };
|
package/dist/cli.js
CHANGED
|
@@ -84,6 +84,7 @@ program
|
|
|
84
84
|
program
|
|
85
85
|
.command("encrypt")
|
|
86
86
|
.description("Encrypt raw credentials to encrypted format")
|
|
87
|
+
.requiredOption("-a, --agent <agent>", `Agent the credentials belong to (${AGENT_CLIS.join(", ")})`)
|
|
87
88
|
.requiredOption("--input <file>", "Raw credentials file path")
|
|
88
89
|
.option("--output <file>", "Output file path (use - for stdout)")
|
|
89
90
|
.option("--no-password", "Use default password (no prompt)")
|
|
@@ -161,12 +162,11 @@ vault
|
|
|
161
162
|
.option("--json", "Pretty-print JSON output")
|
|
162
163
|
.addHelpText("after", `
|
|
163
164
|
Environment variables (option 1 - single JSON):
|
|
164
|
-
AXVAULT JSON config: {"url":"...","apiKey":"..."
|
|
165
|
+
AXVAULT JSON config: {"url":"...","apiKey":"..."}
|
|
165
166
|
|
|
166
167
|
Environment variables (option 2 - individual):
|
|
167
168
|
AXVAULT_URL Vault server URL (required)
|
|
168
169
|
AXVAULT_API_KEY API key for authentication (required)
|
|
169
|
-
AXVAULT_CREDENTIAL Default credential name (optional)
|
|
170
170
|
|
|
171
171
|
Output modes:
|
|
172
172
|
(default) Output credentials as JSON
|
package/dist/commands/auth.js
CHANGED
|
@@ -37,7 +37,7 @@ async function handleAuthToken(options) {
|
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
39
|
// Extract the token based on credential type
|
|
40
|
-
const token = await getAccessToken(creds, { provider: options.provider });
|
|
40
|
+
const token = await getAccessToken(agentId, creds, { provider: options.provider });
|
|
41
41
|
if (!token) {
|
|
42
42
|
const providerHint = options.provider
|
|
43
43
|
? ` for provider '${options.provider}'`
|
package/dist/commands/encrypt.js
CHANGED
|
@@ -7,9 +7,13 @@ import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
|
7
7
|
import promptPassword from "@inquirer/password";
|
|
8
8
|
import { parseCredentialsOrArray } from "../auth/types.js";
|
|
9
9
|
import { DEFAULT_PASSWORD, encrypt, toBase64 } from "../crypto.js";
|
|
10
|
+
import { validateAgent } from "./validate-agent.js";
|
|
10
11
|
/** Handle encrypt command */
|
|
11
12
|
async function handleEncrypt(options) {
|
|
12
13
|
const isStdout = options.output === "-";
|
|
14
|
+
const agentId = validateAgent(options.agent);
|
|
15
|
+
if (!agentId)
|
|
16
|
+
return;
|
|
13
17
|
if (!options.output) {
|
|
14
18
|
console.error("Error: --output is required (use - for stdout)");
|
|
15
19
|
process.exitCode = 2;
|
|
@@ -42,24 +46,6 @@ async function handleEncrypt(options) {
|
|
|
42
46
|
process.exitCode = 1;
|
|
43
47
|
return;
|
|
44
48
|
}
|
|
45
|
-
// Extract agent ID and validate all credentials are for the same agent
|
|
46
|
-
const firstCred = Array.isArray(creds) ? creds[0] : creds;
|
|
47
|
-
if (!firstCred) {
|
|
48
|
-
console.error("Error: No credentials found in input file");
|
|
49
|
-
process.exitCode = 1;
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const agentId = firstCred.agent;
|
|
53
|
-
// For arrays, verify all credentials are for the same agent
|
|
54
|
-
if (Array.isArray(creds)) {
|
|
55
|
-
const mixedAgent = creds.find((c) => c.agent !== agentId);
|
|
56
|
-
if (mixedAgent) {
|
|
57
|
-
console.error(`Error: Mixed agents in credentials array (found '${mixedAgent.agent}', expected '${agentId}')`);
|
|
58
|
-
console.error("All credentials in an array must be for the same agent");
|
|
59
|
-
process.exitCode = 1;
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
49
|
// Get password
|
|
64
50
|
const userPassword = options.password
|
|
65
51
|
? await promptPassword({ message: "Encryption password" })
|
|
@@ -10,7 +10,7 @@ import { parseCredentials } from "../auth/types.js";
|
|
|
10
10
|
import { fromBase64, tryDecrypt } from "../crypto.js";
|
|
11
11
|
import { validateAgent } from "./validate-agent.js";
|
|
12
12
|
/** Parse decrypted content as single credential or array of credentials */
|
|
13
|
-
function parseDecryptedCredentials(parsed
|
|
13
|
+
function parseDecryptedCredentials(parsed) {
|
|
14
14
|
// Handle array of credentials (OpenCode per-provider format)
|
|
15
15
|
if (Array.isArray(parsed)) {
|
|
16
16
|
const credentials = [];
|
|
@@ -19,13 +19,6 @@ function parseDecryptedCredentials(parsed, agentId) {
|
|
|
19
19
|
if (!cred) {
|
|
20
20
|
return { ok: false, error: "Invalid credentials format in array" };
|
|
21
21
|
}
|
|
22
|
-
// Defense-in-depth: verify each credential matches the target agent
|
|
23
|
-
if (cred.agent !== agentId) {
|
|
24
|
-
return {
|
|
25
|
-
ok: false,
|
|
26
|
-
error: `Credential agent mismatch: expected '${agentId}', got '${cred.agent}'`,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
22
|
credentials.push(cred);
|
|
30
23
|
}
|
|
31
24
|
if (credentials.length === 0) {
|
|
@@ -38,13 +31,6 @@ function parseDecryptedCredentials(parsed, agentId) {
|
|
|
38
31
|
if (!cred) {
|
|
39
32
|
return { ok: false, error: "Invalid credentials format" };
|
|
40
33
|
}
|
|
41
|
-
// Defense-in-depth: verify credential matches the target agent
|
|
42
|
-
if (cred.agent !== agentId) {
|
|
43
|
-
return {
|
|
44
|
-
ok: false,
|
|
45
|
-
error: `Decrypted credentials are for '${cred.agent}', not '${agentId}'`,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
34
|
return { ok: true, credentials: [cred] };
|
|
49
35
|
}
|
|
50
36
|
/** Handle auth install-credentials command */
|
|
@@ -87,7 +73,7 @@ async function handleAuthInstall(options) {
|
|
|
87
73
|
});
|
|
88
74
|
const parsed = JSON.parse(decrypted);
|
|
89
75
|
// Parse and validate credentials (handles both single and array formats)
|
|
90
|
-
const parseResult = parseDecryptedCredentials(parsed
|
|
76
|
+
const parseResult = parseDecryptedCredentials(parsed);
|
|
91
77
|
if (!parseResult.ok) {
|
|
92
78
|
console.error(`Error: ${parseResult.error}`);
|
|
93
79
|
process.exitCode = 1;
|
|
@@ -95,7 +81,7 @@ async function handleAuthInstall(options) {
|
|
|
95
81
|
}
|
|
96
82
|
// Install all credentials
|
|
97
83
|
for (const cred of parseResult.credentials) {
|
|
98
|
-
const result = installCredentials(cred, {
|
|
84
|
+
const result = installCredentials(agentId, cred, {
|
|
99
85
|
configDir: options.configDir,
|
|
100
86
|
dataDir: options.dataDir,
|
|
101
87
|
storage,
|
package/dist/commands/vault.js
CHANGED
|
@@ -73,7 +73,7 @@ async function handleVaultFetch(options) {
|
|
|
73
73
|
}
|
|
74
74
|
if (options.env) {
|
|
75
75
|
// Output shell export commands (for eval/source)
|
|
76
|
-
const environmentVariables = credentialsToEnvironment(credentials);
|
|
76
|
+
const environmentVariables = credentialsToEnvironment(agentId, credentials);
|
|
77
77
|
const environmentEntries = Object.entries(environmentVariables);
|
|
78
78
|
if (environmentEntries.length === 0) {
|
|
79
79
|
console.error("Warning: No environment variables to export for this credential type");
|
|
@@ -92,7 +92,7 @@ async function handleVaultFetch(options) {
|
|
|
92
92
|
}
|
|
93
93
|
else if (options.install) {
|
|
94
94
|
// Install credentials locally (writes to file)
|
|
95
|
-
const installResult = installCredentials(credentials, {
|
|
95
|
+
const installResult = installCredentials(agentId, credentials, {
|
|
96
96
|
configDir: options.configDir,
|
|
97
97
|
dataDir: options.dataDir,
|
|
98
98
|
});
|
|
@@ -169,6 +169,7 @@ async function handleVaultPush(options) {
|
|
|
169
169
|
agentId,
|
|
170
170
|
name: options.name,
|
|
171
171
|
credentials,
|
|
172
|
+
provider: options.provider,
|
|
172
173
|
});
|
|
173
174
|
if (!result.ok) {
|
|
174
175
|
console.error(`Error: ${FAILURE_MESSAGES[result.reason]}`);
|
|
@@ -59,6 +59,8 @@ interface VaultPushOptions {
|
|
|
59
59
|
name: string;
|
|
60
60
|
/** Credentials to push */
|
|
61
61
|
credentials: Credentials;
|
|
62
|
+
/** Provider for multi-provider agents (e.g., "anthropic" for OpenCode) */
|
|
63
|
+
provider?: string;
|
|
62
64
|
}
|
|
63
65
|
/**
|
|
64
66
|
* Push credentials to the axvault server.
|
|
@@ -72,7 +74,7 @@ interface VaultPushOptions {
|
|
|
72
74
|
* const result = await pushVaultCredentials({
|
|
73
75
|
* agentId: "claude",
|
|
74
76
|
* name: "ci",
|
|
75
|
-
* credentials: {
|
|
77
|
+
* credentials: { type: "oauth-credentials", data: { ... } }
|
|
76
78
|
* });
|
|
77
79
|
* if (result.ok) {
|
|
78
80
|
* console.log("Credentials pushed successfully");
|
|
@@ -105,7 +105,7 @@ async function fetchVaultCredentials(options) {
|
|
|
105
105
|
* const result = await pushVaultCredentials({
|
|
106
106
|
* agentId: "claude",
|
|
107
107
|
* name: "ci",
|
|
108
|
-
* credentials: {
|
|
108
|
+
* credentials: { type: "oauth-credentials", data: { ... } }
|
|
109
109
|
* });
|
|
110
110
|
* if (result.ok) {
|
|
111
111
|
* console.log("Credentials pushed successfully");
|
|
@@ -128,15 +128,11 @@ async function pushVaultCredentials(options) {
|
|
|
128
128
|
Accept: "application/json",
|
|
129
129
|
"User-Agent": "axauth-vault-client",
|
|
130
130
|
},
|
|
131
|
-
// agent and name are in the URL path (RESTful resource identifier);
|
|
132
|
-
// body contains only the credential payload to avoid redundancy
|
|
133
131
|
body: JSON.stringify({
|
|
132
|
+
agent: options.agentId,
|
|
134
133
|
type: options.credentials.type,
|
|
135
134
|
data: options.credentials.data,
|
|
136
|
-
|
|
137
|
-
...(options.credentials.agent === "opencode" && {
|
|
138
|
-
provider: options.credentials.provider,
|
|
139
|
-
}),
|
|
135
|
+
...(options.provider !== undefined && { provider: options.provider }),
|
|
140
136
|
}),
|
|
141
137
|
});
|
|
142
138
|
// Handle error responses
|
|
@@ -11,8 +11,6 @@ interface VaultConfig {
|
|
|
11
11
|
url: string;
|
|
12
12
|
/** API key for authentication */
|
|
13
13
|
apiKey: string;
|
|
14
|
-
/** Default credential name to fetch (e.g., "ci", "prod") */
|
|
15
|
-
credentialName?: string;
|
|
16
14
|
}
|
|
17
15
|
/**
|
|
18
16
|
* Get vault configuration from environment variables.
|
|
@@ -20,23 +18,21 @@ interface VaultConfig {
|
|
|
20
18
|
* Supports two configuration methods (checked in order):
|
|
21
19
|
*
|
|
22
20
|
* 1. Single JSON env var:
|
|
23
|
-
* - `AXVAULT`: JSON object with url
|
|
21
|
+
* - `AXVAULT`: JSON object with url and apiKey
|
|
24
22
|
*
|
|
25
23
|
* 2. Individual env vars:
|
|
26
24
|
* - `AXVAULT_URL`: Vault server URL (required)
|
|
27
25
|
* - `AXVAULT_API_KEY`: API key for authentication (required)
|
|
28
|
-
* - `AXVAULT_CREDENTIAL`: Default credential name (optional)
|
|
29
26
|
*
|
|
30
27
|
* @returns Vault configuration if configured, undefined otherwise
|
|
31
28
|
*
|
|
32
29
|
* @example
|
|
33
30
|
* // Option 1: Single JSON env var
|
|
34
|
-
* // AXVAULT='{"url":"https://vault.axkit.dev","apiKey":"axv_sk_xxx"
|
|
31
|
+
* // AXVAULT='{"url":"https://vault.axkit.dev","apiKey":"axv_sk_xxx"}'
|
|
35
32
|
*
|
|
36
33
|
* // Option 2: Individual env vars
|
|
37
34
|
* // AXVAULT_URL=https://vault.axkit.dev
|
|
38
35
|
* // AXVAULT_API_KEY=axv_sk_xxx
|
|
39
|
-
* // AXVAULT_CREDENTIAL=ci
|
|
40
36
|
*
|
|
41
37
|
* const config = getVaultConfig();
|
|
42
38
|
* if (config) {
|
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
/** Zod schema for vault config JSON */
|
|
10
|
-
const VaultConfigJson = z
|
|
10
|
+
const VaultConfigJson = z
|
|
11
|
+
.object({
|
|
11
12
|
url: z.string(),
|
|
12
13
|
apiKey: z.string(),
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
})
|
|
15
|
+
.strict();
|
|
15
16
|
/**
|
|
16
17
|
* Validate and normalize a vault URL.
|
|
17
18
|
*
|
|
@@ -44,7 +45,6 @@ function parseVaultConfigJson() {
|
|
|
44
45
|
return {
|
|
45
46
|
url,
|
|
46
47
|
apiKey: parsed.apiKey,
|
|
47
|
-
credentialName: parsed.credentialName,
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
50
|
catch {
|
|
@@ -58,23 +58,21 @@ function parseVaultConfigJson() {
|
|
|
58
58
|
* Supports two configuration methods (checked in order):
|
|
59
59
|
*
|
|
60
60
|
* 1. Single JSON env var:
|
|
61
|
-
* - `AXVAULT`: JSON object with url
|
|
61
|
+
* - `AXVAULT`: JSON object with url and apiKey
|
|
62
62
|
*
|
|
63
63
|
* 2. Individual env vars:
|
|
64
64
|
* - `AXVAULT_URL`: Vault server URL (required)
|
|
65
65
|
* - `AXVAULT_API_KEY`: API key for authentication (required)
|
|
66
|
-
* - `AXVAULT_CREDENTIAL`: Default credential name (optional)
|
|
67
66
|
*
|
|
68
67
|
* @returns Vault configuration if configured, undefined otherwise
|
|
69
68
|
*
|
|
70
69
|
* @example
|
|
71
70
|
* // Option 1: Single JSON env var
|
|
72
|
-
* // AXVAULT='{"url":"https://vault.axkit.dev","apiKey":"axv_sk_xxx"
|
|
71
|
+
* // AXVAULT='{"url":"https://vault.axkit.dev","apiKey":"axv_sk_xxx"}'
|
|
73
72
|
*
|
|
74
73
|
* // Option 2: Individual env vars
|
|
75
74
|
* // AXVAULT_URL=https://vault.axkit.dev
|
|
76
75
|
* // AXVAULT_API_KEY=axv_sk_xxx
|
|
77
|
-
* // AXVAULT_CREDENTIAL=ci
|
|
78
76
|
*
|
|
79
77
|
* const config = getVaultConfig();
|
|
80
78
|
* if (config) {
|
|
@@ -98,7 +96,6 @@ function getVaultConfig() {
|
|
|
98
96
|
return {
|
|
99
97
|
url,
|
|
100
98
|
apiKey,
|
|
101
|
-
credentialName: process.env.AXVAULT_CREDENTIAL,
|
|
102
99
|
};
|
|
103
100
|
}
|
|
104
101
|
/**
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "axauth",
|
|
3
3
|
"author": "Łukasz Jerciński",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "3.1.
|
|
5
|
+
"version": "3.1.5",
|
|
6
6
|
"description": "Authentication management library and CLI for AI coding agents",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -70,8 +70,8 @@
|
|
|
70
70
|
"@commander-js/extra-typings": "^14.0.0",
|
|
71
71
|
"@inquirer/password": "^5.0.4",
|
|
72
72
|
"axconfig": "^3.6.3",
|
|
73
|
-
"axexec": "^
|
|
74
|
-
"axshared": "^
|
|
73
|
+
"axexec": "^2.0.0",
|
|
74
|
+
"axshared": "^5.0.0",
|
|
75
75
|
"commander": "^14.0.2",
|
|
76
76
|
"zod": "^4.3.5"
|
|
77
77
|
},
|