axauth 1.8.0 → 1.9.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/README.md +1 -1
- package/dist/auth/adapter.d.ts +25 -0
- package/dist/auth/agents/claude-storage.d.ts +12 -2
- package/dist/auth/agents/claude-storage.js +35 -12
- package/dist/auth/agents/claude.js +11 -3
- package/dist/auth/agents/codex-auth-check.d.ts +3 -1
- package/dist/auth/agents/codex-auth-check.js +3 -3
- package/dist/auth/agents/codex-storage.d.ts +1 -1
- package/dist/auth/agents/codex.js +4 -3
- package/dist/auth/agents/copilot-install.d.ts +18 -0
- package/dist/auth/agents/copilot-install.js +73 -0
- package/dist/auth/agents/copilot.js +14 -61
- package/dist/auth/agents/gemini-auth-check.d.ts +3 -1
- package/dist/auth/agents/gemini-auth-check.js +1 -1
- package/dist/auth/agents/gemini.js +14 -3
- package/dist/auth/agents/opencode.js +1 -1
- package/dist/auth/extract-creds-from-directory.js +1 -1
- package/dist/auth/registry.d.ts +8 -0
- package/dist/auth/registry.js +8 -0
- package/dist/auth/types.d.ts +2 -1
- package/dist/auth/types.js +2 -2
- package/dist/auth/validate-directories.d.ts +1 -0
- package/dist/cli.js +53 -1
- package/dist/commands/vault.d.ts +22 -0
- package/dist/commands/vault.js +123 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/vault/vault-client.d.ts +48 -0
- package/dist/vault/vault-client.js +102 -0
- package/dist/vault/vault-config.d.ts +54 -0
- package/dist/vault/vault-config.js +112 -0
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -253,7 +253,7 @@ src/
|
|
|
253
253
|
|
|
254
254
|
## Related Packages
|
|
255
255
|
|
|
256
|
-
axauth is part of the [a╳
|
|
256
|
+
axauth is part of the [a╳kit](https://axkit.dev) ecosystem:
|
|
257
257
|
|
|
258
258
|
- **axshared** - Shared types and agent metadata
|
|
259
259
|
- **axconfig** - Permission and configuration management
|
package/dist/auth/adapter.d.ts
CHANGED
|
@@ -111,6 +111,18 @@ interface AdapterCapabilities {
|
|
|
111
111
|
*
|
|
112
112
|
* Encapsulates all auth operations for a single agent, hiding the
|
|
113
113
|
* complexity of different storage mechanisms and credential formats.
|
|
114
|
+
*
|
|
115
|
+
* ## Credential Source Priority
|
|
116
|
+
*
|
|
117
|
+
* When resolving credentials, sources are checked in this order:
|
|
118
|
+
*
|
|
119
|
+
* 1. **Environment variables** (via `extractFromEnvironment`)
|
|
120
|
+
* 2. **Vault** (external, via axvault server when configured)
|
|
121
|
+
* 3. **Local storage** (keychain/file via `extractRawCredentials`)
|
|
122
|
+
*
|
|
123
|
+
* Environment variables always take precedence to allow easy overrides
|
|
124
|
+
* in CI/CD and development. Vault provides centralized credential
|
|
125
|
+
* management. Local storage is the fallback for interactive use.
|
|
114
126
|
*/
|
|
115
127
|
interface AuthAdapter {
|
|
116
128
|
/** Agent identifier */
|
|
@@ -124,6 +136,19 @@ interface AuthAdapter {
|
|
|
124
136
|
* and returns the status without making API calls.
|
|
125
137
|
*/
|
|
126
138
|
checkAuth(): AuthStatus;
|
|
139
|
+
/**
|
|
140
|
+
* Extract credentials from environment variables only.
|
|
141
|
+
*
|
|
142
|
+
* This is called before checking vault or local storage to ensure
|
|
143
|
+
* environment variables have highest priority. Returns undefined if
|
|
144
|
+
* no credentials are set via environment variables.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* // For Claude, checks CLAUDE_CODE_OAUTH_TOKEN and ANTHROPIC_API_KEY
|
|
148
|
+
* const envCreds = adapter.extractFromEnvironment?.();
|
|
149
|
+
* if (envCreds) return envCreds; // Env vars win over vault
|
|
150
|
+
*/
|
|
151
|
+
extractFromEnvironment?(): Credentials | undefined;
|
|
127
152
|
/**
|
|
128
153
|
* Extract raw credentials for export.
|
|
129
154
|
*
|
|
@@ -18,9 +18,19 @@ declare function checkAuth(): {
|
|
|
18
18
|
method?: string;
|
|
19
19
|
details?: Record<string, unknown>;
|
|
20
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* Extract credentials from environment variables only.
|
|
23
|
+
*
|
|
24
|
+
* This is called before checking vault or local storage to ensure
|
|
25
|
+
* environment variables have highest priority.
|
|
26
|
+
*/
|
|
27
|
+
declare function extractFromEnvironment(): {
|
|
28
|
+
type: "oauth-credentials" | "oauth-token" | "api-key";
|
|
29
|
+
data: Record<string, unknown>;
|
|
30
|
+
} | undefined;
|
|
21
31
|
/** Extract raw credentials from available sources */
|
|
22
32
|
declare function extractRawCredentials(): {
|
|
23
|
-
type: "oauth" | "api-key";
|
|
33
|
+
type: "oauth-credentials" | "oauth-token" | "api-key";
|
|
24
34
|
data: Record<string, unknown>;
|
|
25
35
|
} | undefined;
|
|
26
|
-
export { AGENT_ID, checkAuth, deleteFileCreds, deleteKeychainCreds, extractRawCredentials, getDefaultCredsFilePath, saveFileCreds, saveKeychainCreds, };
|
|
36
|
+
export { AGENT_ID, checkAuth, deleteFileCreds, deleteKeychainCreds, extractFromEnvironment, extractRawCredentials, getDefaultCredsFilePath, saveFileCreds, saveKeychainCreds, };
|
|
@@ -81,34 +81,57 @@ function checkAuth() {
|
|
|
81
81
|
}
|
|
82
82
|
return { authenticated: false };
|
|
83
83
|
}
|
|
84
|
-
/**
|
|
85
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Extract credentials from environment variables only.
|
|
86
|
+
*
|
|
87
|
+
* This is called before checking vault or local storage to ensure
|
|
88
|
+
* environment variables have highest priority.
|
|
89
|
+
*/
|
|
90
|
+
function extractFromEnvironment() {
|
|
86
91
|
if (process.env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
87
92
|
return {
|
|
88
|
-
type: "oauth",
|
|
93
|
+
type: "oauth-token",
|
|
89
94
|
data: { accessToken: process.env.CLAUDE_CODE_OAUTH_TOKEN },
|
|
90
95
|
};
|
|
91
96
|
}
|
|
97
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
98
|
+
return {
|
|
99
|
+
type: "api-key",
|
|
100
|
+
data: { apiKey: process.env.ANTHROPIC_API_KEY },
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Extract credentials from local storage only (keychain + file).
|
|
107
|
+
*
|
|
108
|
+
* This skips environment variable checks and only looks at local storage.
|
|
109
|
+
* Used as fallback after vault fetch fails.
|
|
110
|
+
*/
|
|
111
|
+
function extractFromLocalStorage() {
|
|
92
112
|
const keychainCreds = loadKeychainCreds();
|
|
93
113
|
if (keychainCreds) {
|
|
94
114
|
return {
|
|
95
|
-
type: "oauth",
|
|
115
|
+
type: "oauth-credentials",
|
|
96
116
|
data: { ...keychainCreds, _source: "keychain" },
|
|
97
117
|
};
|
|
98
118
|
}
|
|
99
119
|
const fileCreds = loadFileCreds();
|
|
100
120
|
if (fileCreds) {
|
|
101
121
|
return {
|
|
102
|
-
type: "oauth",
|
|
122
|
+
type: "oauth-credentials",
|
|
103
123
|
data: { ...fileCreds, _source: "file" },
|
|
104
124
|
};
|
|
105
125
|
}
|
|
106
|
-
if (process.env.ANTHROPIC_API_KEY) {
|
|
107
|
-
return {
|
|
108
|
-
type: "api-key",
|
|
109
|
-
data: { apiKey: process.env.ANTHROPIC_API_KEY },
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
126
|
return undefined;
|
|
113
127
|
}
|
|
114
|
-
|
|
128
|
+
/** Extract raw credentials from available sources */
|
|
129
|
+
function extractRawCredentials() {
|
|
130
|
+
// Check env vars first
|
|
131
|
+
const environmentCreds = extractFromEnvironment();
|
|
132
|
+
if (environmentCreds)
|
|
133
|
+
return environmentCreds;
|
|
134
|
+
// Fall back to local storage
|
|
135
|
+
return extractFromLocalStorage();
|
|
136
|
+
}
|
|
137
|
+
export { AGENT_ID, checkAuth, deleteFileCreds, deleteKeychainCreds, extractFromEnvironment, extractRawCredentials, getDefaultCredsFilePath, saveFileCreds, saveKeychainCreds, };
|
|
@@ -7,7 +7,7 @@ import path from "node:path";
|
|
|
7
7
|
import { extractCredsFromDirectory } from "../extract-creds-from-directory.js";
|
|
8
8
|
import { isMacOS } from "../keychain.js";
|
|
9
9
|
import { resolveCustomDirectory } from "../validate-directories.js";
|
|
10
|
-
import { AGENT_ID, checkAuth, deleteFileCreds, deleteKeychainCreds, extractRawCredentials, getDefaultCredsFilePath, saveFileCreds, saveKeychainCreds, } from "./claude-storage.js";
|
|
10
|
+
import { AGENT_ID, checkAuth, deleteFileCreds, deleteKeychainCreds, extractFromEnvironment, extractRawCredentials, getDefaultCredsFilePath, saveFileCreds, saveKeychainCreds, } from "./claude-storage.js";
|
|
11
11
|
const CREDS_FILE_NAME = ".credentials.json";
|
|
12
12
|
/** Claude Code authentication adapter */
|
|
13
13
|
const claudeCodeAdapter = {
|
|
@@ -22,6 +22,12 @@ const claudeCodeAdapter = {
|
|
|
22
22
|
const result = checkAuth();
|
|
23
23
|
return { agentId: AGENT_ID, ...result };
|
|
24
24
|
},
|
|
25
|
+
extractFromEnvironment() {
|
|
26
|
+
const result = extractFromEnvironment();
|
|
27
|
+
if (!result)
|
|
28
|
+
return undefined;
|
|
29
|
+
return { agent: AGENT_ID, ...result };
|
|
30
|
+
},
|
|
25
31
|
extractRawCredentials() {
|
|
26
32
|
const result = extractRawCredentials();
|
|
27
33
|
if (!result)
|
|
@@ -121,7 +127,8 @@ const claudeCodeAdapter = {
|
|
|
121
127
|
},
|
|
122
128
|
getAccessToken(creds) {
|
|
123
129
|
const data = creds.data;
|
|
124
|
-
if (creds.type === "oauth"
|
|
130
|
+
if ((creds.type === "oauth-credentials" || creds.type === "oauth-token") &&
|
|
131
|
+
typeof data.accessToken === "string") {
|
|
125
132
|
return data.accessToken;
|
|
126
133
|
}
|
|
127
134
|
if (creds.type === "api-key" && typeof data.apiKey === "string") {
|
|
@@ -132,7 +139,8 @@ const claudeCodeAdapter = {
|
|
|
132
139
|
credentialsToEnvironment(creds) {
|
|
133
140
|
const environment = {};
|
|
134
141
|
const data = creds.data;
|
|
135
|
-
if (creds.type === "oauth"
|
|
142
|
+
if ((creds.type === "oauth-credentials" || creds.type === "oauth-token") &&
|
|
143
|
+
typeof data.accessToken === "string") {
|
|
136
144
|
environment.CLAUDE_CODE_OAUTH_TOKEN = data.accessToken;
|
|
137
145
|
}
|
|
138
146
|
else if (creds.type === "api-key" && typeof data.apiKey === "string") {
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import type { AuthStatus, Credentials } from "../types.js";
|
|
5
5
|
/** Check auth status across all sources */
|
|
6
6
|
declare function checkAuth(): AuthStatus;
|
|
7
|
+
/** Extract credentials from environment */
|
|
8
|
+
declare function extractEnvironmentCredentials(): Credentials | undefined;
|
|
7
9
|
/** Extract raw credentials from first available source */
|
|
8
10
|
declare function extractRawCredentials(): Credentials | undefined;
|
|
9
|
-
export { checkAuth, extractRawCredentials };
|
|
11
|
+
export { checkAuth, extractEnvironmentCredentials, extractRawCredentials };
|
|
@@ -71,7 +71,7 @@ function extractKeychainCredentials() {
|
|
|
71
71
|
if (keychainAuth?.tokens) {
|
|
72
72
|
return {
|
|
73
73
|
agent: AGENT_ID,
|
|
74
|
-
type: "oauth",
|
|
74
|
+
type: "oauth-credentials",
|
|
75
75
|
data: { ...keychainAuth, _source: "keychain" },
|
|
76
76
|
};
|
|
77
77
|
}
|
|
@@ -88,7 +88,7 @@ function extractFileCredentials() {
|
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
90
|
if (fileAuth?.tokens) {
|
|
91
|
-
return { agent: AGENT_ID, type: "oauth", data: fileAuth };
|
|
91
|
+
return { agent: AGENT_ID, type: "oauth-credentials", data: fileAuth };
|
|
92
92
|
}
|
|
93
93
|
return undefined;
|
|
94
94
|
}
|
|
@@ -98,4 +98,4 @@ function extractRawCredentials() {
|
|
|
98
98
|
extractKeychainCredentials() ??
|
|
99
99
|
extractFileCredentials());
|
|
100
100
|
}
|
|
101
|
-
export { checkAuth, extractRawCredentials };
|
|
101
|
+
export { checkAuth, extractEnvironmentCredentials, extractRawCredentials };
|
|
@@ -39,5 +39,5 @@ interface CredentialsData {
|
|
|
39
39
|
[key: string]: unknown;
|
|
40
40
|
}
|
|
41
41
|
/** Build auth content from credentials data */
|
|
42
|
-
declare function buildAuthContent(type: "oauth" | "api-key", data: CredentialsData): CodexAuthJson;
|
|
42
|
+
declare function buildAuthContent(type: "oauth-credentials" | "oauth-token" | "api-key", data: CredentialsData): CodexAuthJson;
|
|
43
43
|
export { buildAuthContent, deleteFileCreds, deleteKeychainCreds, getEnvironmentApiKey, hasFileApiKey, hasFileOAuth, hasKeychainApiKey, hasKeychainOAuth, loadFileCreds, loadKeychainCreds, saveFileCreds, saveKeychainCreds, };
|
|
@@ -8,7 +8,7 @@ import path from "node:path";
|
|
|
8
8
|
import { ensureDirectory } from "../file-storage.js";
|
|
9
9
|
import { isMacOS } from "../keychain.js";
|
|
10
10
|
import { resolveCustomDirectory } from "../validate-directories.js";
|
|
11
|
-
import { checkAuth, extractRawCredentials } from "./codex-auth-check.js";
|
|
11
|
+
import { checkAuth, extractEnvironmentCredentials, extractRawCredentials, } from "./codex-auth-check.js";
|
|
12
12
|
import { getAuthFilePath, getCodexHome, updateConfigStorage, } from "./codex-config.js";
|
|
13
13
|
import { buildAuthContent, deleteFileCreds, deleteKeychainCreds, saveFileCreds, saveKeychainCreds, } from "./codex-storage.js";
|
|
14
14
|
const AGENT_ID = "codex";
|
|
@@ -23,6 +23,7 @@ const codexAdapter = {
|
|
|
23
23
|
installApiKey: true,
|
|
24
24
|
},
|
|
25
25
|
checkAuth,
|
|
26
|
+
extractFromEnvironment: extractEnvironmentCredentials,
|
|
26
27
|
extractRawCredentials,
|
|
27
28
|
extractRawCredentialsFromDirectory(options) {
|
|
28
29
|
// Codex doesn't separate config/data - use either directory
|
|
@@ -38,7 +39,7 @@ const codexAdapter = {
|
|
|
38
39
|
return undefined;
|
|
39
40
|
const data = parsed;
|
|
40
41
|
// Determine type from data structure
|
|
41
|
-
const type = data.api_key ? "api-key" : "oauth";
|
|
42
|
+
const type = data.api_key ? "api-key" : "oauth-credentials";
|
|
42
43
|
return { agent: AGENT_ID, type, data };
|
|
43
44
|
}
|
|
44
45
|
catch {
|
|
@@ -133,7 +134,7 @@ const codexAdapter = {
|
|
|
133
134
|
if (creds.type === "api-key" && typeof data.apiKey === "string") {
|
|
134
135
|
return data.apiKey;
|
|
135
136
|
}
|
|
136
|
-
if (creds.type === "oauth") {
|
|
137
|
+
if (creds.type === "oauth-credentials") {
|
|
137
138
|
// Direct access_token
|
|
138
139
|
if (typeof data.access_token === "string") {
|
|
139
140
|
return data.access_token;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copilot credential installation helpers.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from copilot.ts to reduce file complexity.
|
|
5
|
+
*/
|
|
6
|
+
import type { InstallOptions, OperationResult } from "../adapter.js";
|
|
7
|
+
import type { Credentials } from "../types.js";
|
|
8
|
+
import type { ResolveResult } from "../validate-directories.js";
|
|
9
|
+
declare const CREDS_FILE_NAME = "token.json";
|
|
10
|
+
/**
|
|
11
|
+
* Install Copilot credentials.
|
|
12
|
+
*
|
|
13
|
+
* Handles keychain vs file storage and custom directories.
|
|
14
|
+
*/
|
|
15
|
+
declare function installCopilotCredentials(creds: Credentials, resolved: Extract<ResolveResult, {
|
|
16
|
+
ok: true;
|
|
17
|
+
}>, options?: InstallOptions): OperationResult;
|
|
18
|
+
export { CREDS_FILE_NAME, installCopilotCredentials };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copilot credential installation helpers.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from copilot.ts to reduce file complexity.
|
|
5
|
+
*/
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { ensureDirectory } from "../file-storage.js";
|
|
8
|
+
import { isMacOS } from "../keychain.js";
|
|
9
|
+
import { getConfigDirectory, getConfigFilePath, saveFileToken, saveKeychainToken, saveTokenToPath, } from "./copilot-storage.js";
|
|
10
|
+
const CREDS_FILE_NAME = "token.json";
|
|
11
|
+
/**
|
|
12
|
+
* Install Copilot credentials.
|
|
13
|
+
*
|
|
14
|
+
* Handles keychain vs file storage and custom directories.
|
|
15
|
+
*/
|
|
16
|
+
function installCopilotCredentials(creds, resolved, options) {
|
|
17
|
+
if (creds.type === "api-key") {
|
|
18
|
+
return {
|
|
19
|
+
ok: false,
|
|
20
|
+
message: "API key credentials cannot be installed (use COPILOT_GITHUB_TOKEN env var)",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const token = typeof creds.data.accessToken === "string"
|
|
24
|
+
? creds.data.accessToken
|
|
25
|
+
: undefined;
|
|
26
|
+
if (!token) {
|
|
27
|
+
return { ok: false, message: "No access token found in credentials" };
|
|
28
|
+
}
|
|
29
|
+
// Custom directory forces file storage
|
|
30
|
+
if (resolved.customDir) {
|
|
31
|
+
const targetPath = path.join(resolved.customDir, CREDS_FILE_NAME);
|
|
32
|
+
if (saveTokenToPath(token, targetPath)) {
|
|
33
|
+
return {
|
|
34
|
+
ok: true,
|
|
35
|
+
message: `Installed credentials to ${targetPath}`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
ok: false,
|
|
40
|
+
message: `Failed to install credentials to ${targetPath}`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// Default location: use storage option or _source marker
|
|
44
|
+
const sourceMarker = creds.data._source;
|
|
45
|
+
const targetStorage = options?.storage ?? (sourceMarker === "keychain" ? "keychain" : "file");
|
|
46
|
+
if (targetStorage === "keychain") {
|
|
47
|
+
if (!isMacOS()) {
|
|
48
|
+
return {
|
|
49
|
+
ok: false,
|
|
50
|
+
message: "Keychain storage is only available on macOS",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (saveKeychainToken(token)) {
|
|
54
|
+
return { ok: true, message: "Installed credentials to macOS Keychain" };
|
|
55
|
+
}
|
|
56
|
+
return { ok: false, message: "Failed to save to macOS Keychain" };
|
|
57
|
+
}
|
|
58
|
+
// File storage
|
|
59
|
+
if (!ensureDirectory(getConfigDirectory())) {
|
|
60
|
+
return {
|
|
61
|
+
ok: false,
|
|
62
|
+
message: "Failed to create copilot config directory",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (saveFileToken(token)) {
|
|
66
|
+
return {
|
|
67
|
+
ok: true,
|
|
68
|
+
message: `Installed credentials to ${getConfigFilePath()}`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return { ok: false, message: "Failed to install credentials to file" };
|
|
72
|
+
}
|
|
73
|
+
export { CREDS_FILE_NAME, installCopilotCredentials };
|
|
@@ -6,13 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { extractCredsFromDirectory } from "../extract-creds-from-directory.js";
|
|
9
|
-
import { ensureDirectory } from "../file-storage.js";
|
|
10
|
-
import { isMacOS } from "../keychain.js";
|
|
11
9
|
import { resolveCustomDirectory } from "../validate-directories.js";
|
|
12
10
|
import { findFirstAvailableToken } from "./copilot-auth-check.js";
|
|
13
|
-
import {
|
|
11
|
+
import { CREDS_FILE_NAME, installCopilotCredentials, } from "./copilot-install.js";
|
|
12
|
+
import { deleteFileToken, deleteKeychainToken, deleteTokenFromPath, getConfigFilePath, getEnvironmentToken, } from "./copilot-storage.js";
|
|
14
13
|
const AGENT_ID = "copilot";
|
|
15
|
-
const CREDS_FILE_NAME = "token.json";
|
|
16
14
|
/** Copilot CLI authentication adapter */
|
|
17
15
|
const copilotAdapter = {
|
|
18
16
|
agentId: AGENT_ID,
|
|
@@ -39,13 +37,23 @@ const copilotAdapter = {
|
|
|
39
37
|
method: methodMap[result.source],
|
|
40
38
|
};
|
|
41
39
|
},
|
|
40
|
+
extractFromEnvironment() {
|
|
41
|
+
const token = getEnvironmentToken();
|
|
42
|
+
if (!token)
|
|
43
|
+
return undefined;
|
|
44
|
+
return {
|
|
45
|
+
agent: AGENT_ID,
|
|
46
|
+
type: "oauth-token",
|
|
47
|
+
data: { accessToken: token },
|
|
48
|
+
};
|
|
49
|
+
},
|
|
42
50
|
extractRawCredentials() {
|
|
43
51
|
const result = findFirstAvailableToken();
|
|
44
52
|
if (!result)
|
|
45
53
|
return undefined;
|
|
46
54
|
return {
|
|
47
55
|
agent: AGENT_ID,
|
|
48
|
-
type: "oauth",
|
|
56
|
+
type: "oauth-token",
|
|
49
57
|
data: { accessToken: result.token, _source: result.source },
|
|
50
58
|
};
|
|
51
59
|
},
|
|
@@ -57,65 +65,10 @@ const copilotAdapter = {
|
|
|
57
65
|
return extractCredsFromDirectory(AGENT_ID, directory, CREDS_FILE_NAME);
|
|
58
66
|
},
|
|
59
67
|
installCredentials(creds, options) {
|
|
60
|
-
// Resolve custom directory with validation
|
|
61
68
|
const resolved = resolveCustomDirectory(AGENT_ID, options?.configDir, options?.dataDir);
|
|
62
69
|
if (!resolved.ok)
|
|
63
70
|
return resolved.error;
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
ok: false,
|
|
67
|
-
message: "API key credentials cannot be installed (use COPILOT_GITHUB_TOKEN env var)",
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
const token = typeof creds.data.accessToken === "string"
|
|
71
|
-
? creds.data.accessToken
|
|
72
|
-
: undefined;
|
|
73
|
-
if (!token) {
|
|
74
|
-
return { ok: false, message: "No access token found in credentials" };
|
|
75
|
-
}
|
|
76
|
-
// Custom directory forces file storage
|
|
77
|
-
if (resolved.customDir) {
|
|
78
|
-
const targetPath = path.join(resolved.customDir, CREDS_FILE_NAME);
|
|
79
|
-
if (saveTokenToPath(token, targetPath)) {
|
|
80
|
-
return {
|
|
81
|
-
ok: true,
|
|
82
|
-
message: `Installed credentials to ${targetPath}`,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
return {
|
|
86
|
-
ok: false,
|
|
87
|
-
message: `Failed to install credentials to ${targetPath}`,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
// Default location: use storage option or _source marker
|
|
91
|
-
const sourceMarker = creds.data._source;
|
|
92
|
-
const targetStorage = options?.storage ?? (sourceMarker === "keychain" ? "keychain" : "file");
|
|
93
|
-
if (targetStorage === "keychain") {
|
|
94
|
-
if (!isMacOS()) {
|
|
95
|
-
return {
|
|
96
|
-
ok: false,
|
|
97
|
-
message: "Keychain storage is only available on macOS",
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
if (saveKeychainToken(token)) {
|
|
101
|
-
return { ok: true, message: "Installed credentials to macOS Keychain" };
|
|
102
|
-
}
|
|
103
|
-
return { ok: false, message: "Failed to save to macOS Keychain" };
|
|
104
|
-
}
|
|
105
|
-
// File storage
|
|
106
|
-
if (!ensureDirectory(getConfigDirectory())) {
|
|
107
|
-
return {
|
|
108
|
-
ok: false,
|
|
109
|
-
message: "Failed to create copilot config directory",
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
if (saveFileToken(token)) {
|
|
113
|
-
return {
|
|
114
|
-
ok: true,
|
|
115
|
-
message: `Installed credentials to ${getConfigFilePath()}`,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
return { ok: false, message: "Failed to install credentials to file" };
|
|
71
|
+
return installCopilotCredentials(creds, resolved, options);
|
|
119
72
|
},
|
|
120
73
|
removeCredentials(options) {
|
|
121
74
|
// Resolve custom directory with validation
|
|
@@ -11,6 +11,8 @@ interface CredentialResult {
|
|
|
11
11
|
method: string;
|
|
12
12
|
data?: Record<string, unknown>;
|
|
13
13
|
}
|
|
14
|
+
/** Check environment variable credentials (API key and Vertex AI) */
|
|
15
|
+
declare function checkEnvironmentAuth(): CredentialResult | undefined;
|
|
14
16
|
/** Find credentials using priority-ordered discovery: env → keychain → file */
|
|
15
17
|
declare function findCredentials(): CredentialResult | undefined;
|
|
16
|
-
export { findCredentials };
|
|
18
|
+
export { checkEnvironmentAuth, findCredentials };
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Supports OAuth via keychain (macOS) or file. API key auth is env-var only.
|
|
5
5
|
*/
|
|
6
6
|
import { extractCredsFromDirectory } from "../extract-creds-from-directory.js";
|
|
7
|
-
import { findCredentials } from "./gemini-auth-check.js";
|
|
7
|
+
import { checkEnvironmentAuth, findCredentials } from "./gemini-auth-check.js";
|
|
8
8
|
import { installOAuthCredentials, removeGeminiCredentials, } from "./gemini-install.js";
|
|
9
9
|
const AGENT_ID = "gemini";
|
|
10
10
|
/** Gemini authentication adapter */
|
|
@@ -27,6 +27,16 @@ const geminiAdapter = {
|
|
|
27
27
|
method: result.method,
|
|
28
28
|
};
|
|
29
29
|
},
|
|
30
|
+
extractFromEnvironment() {
|
|
31
|
+
const result = checkEnvironmentAuth();
|
|
32
|
+
if (!result || !result.data)
|
|
33
|
+
return undefined;
|
|
34
|
+
return {
|
|
35
|
+
agent: AGENT_ID,
|
|
36
|
+
type: "api-key",
|
|
37
|
+
data: result.data,
|
|
38
|
+
};
|
|
39
|
+
},
|
|
30
40
|
extractRawCredentials() {
|
|
31
41
|
const result = findCredentials();
|
|
32
42
|
if (!result || !result.data)
|
|
@@ -42,7 +52,7 @@ const geminiAdapter = {
|
|
|
42
52
|
// OAuth credentials from keychain or file include _source for round-tripping
|
|
43
53
|
return {
|
|
44
54
|
agent: AGENT_ID,
|
|
45
|
-
type: "oauth",
|
|
55
|
+
type: "oauth-credentials",
|
|
46
56
|
data: {
|
|
47
57
|
...result.data,
|
|
48
58
|
_source: result.source === "keychain" ? "keychain" : "file",
|
|
@@ -74,7 +84,8 @@ const geminiAdapter = {
|
|
|
74
84
|
if (creds.type === "api-key" && typeof data.apiKey === "string") {
|
|
75
85
|
return data.apiKey;
|
|
76
86
|
}
|
|
77
|
-
if (creds.type === "oauth" &&
|
|
87
|
+
if (creds.type === "oauth-credentials" &&
|
|
88
|
+
typeof data.access_token === "string") {
|
|
78
89
|
return data.access_token;
|
|
79
90
|
}
|
|
80
91
|
return undefined;
|
|
@@ -77,7 +77,7 @@ const opencodeAdapter = {
|
|
|
77
77
|
const auth = readAuthFile(getDefaultAuthFilePath());
|
|
78
78
|
if (!auth || Object.keys(auth).length === 0)
|
|
79
79
|
return undefined;
|
|
80
|
-
return { agent: AGENT_ID, type: "oauth", data: auth };
|
|
80
|
+
return { agent: AGENT_ID, type: "oauth-credentials", data: auth };
|
|
81
81
|
},
|
|
82
82
|
extractRawCredentialsFromDirectory(options) {
|
|
83
83
|
// OpenCode stores credentials in dataDir only - configDir is for settings
|
|
@@ -28,7 +28,7 @@ function extractCredsFromDirectory(agentId, directory, fileName, transform) {
|
|
|
28
28
|
const data = transform ? transform(record) : record;
|
|
29
29
|
if (!data)
|
|
30
30
|
return undefined;
|
|
31
|
-
return { agent: agentId, type: "oauth", data };
|
|
31
|
+
return { agent: agentId, type: "oauth-credentials", data };
|
|
32
32
|
}
|
|
33
33
|
catch {
|
|
34
34
|
return undefined;
|
package/dist/auth/registry.d.ts
CHANGED
|
@@ -51,9 +51,17 @@ declare function checkAllAuth(): AuthStatus[];
|
|
|
51
51
|
/**
|
|
52
52
|
* Extract raw credentials for an agent.
|
|
53
53
|
*
|
|
54
|
+
* Checks credentials from all available sources:
|
|
55
|
+
* - Environment variables
|
|
56
|
+
* - Keychain (macOS)
|
|
57
|
+
* - File storage
|
|
58
|
+
*
|
|
54
59
|
* Note: The `data` field format is agent-specific and not standardized.
|
|
55
60
|
* Use {@link getAccessToken} to extract the token in a uniform way.
|
|
56
61
|
*
|
|
62
|
+
* For vault credentials, use {@link fetchVaultCredentials} from the vault module
|
|
63
|
+
* or the `axauth vault fetch` CLI command.
|
|
64
|
+
*
|
|
57
65
|
* @example
|
|
58
66
|
* const creds = extractRawCredentials("codex");
|
|
59
67
|
* if (creds) { await exportCredentials(creds); }
|
package/dist/auth/registry.js
CHANGED
|
@@ -75,9 +75,17 @@ function checkAllAuth() {
|
|
|
75
75
|
/**
|
|
76
76
|
* Extract raw credentials for an agent.
|
|
77
77
|
*
|
|
78
|
+
* Checks credentials from all available sources:
|
|
79
|
+
* - Environment variables
|
|
80
|
+
* - Keychain (macOS)
|
|
81
|
+
* - File storage
|
|
82
|
+
*
|
|
78
83
|
* Note: The `data` field format is agent-specific and not standardized.
|
|
79
84
|
* Use {@link getAccessToken} to extract the token in a uniform way.
|
|
80
85
|
*
|
|
86
|
+
* For vault credentials, use {@link fetchVaultCredentials} from the vault module
|
|
87
|
+
* or the `axauth vault fetch` CLI command.
|
|
88
|
+
*
|
|
81
89
|
* @example
|
|
82
90
|
* const creds = extractRawCredentials("codex");
|
|
83
91
|
* if (creds) { await exportCredentials(creds); }
|
package/dist/auth/types.d.ts
CHANGED
|
@@ -21,8 +21,9 @@ declare const Credentials: z.ZodObject<{
|
|
|
21
21
|
copilot: "copilot";
|
|
22
22
|
}>;
|
|
23
23
|
type: z.ZodEnum<{
|
|
24
|
-
oauth: "oauth";
|
|
25
24
|
"api-key": "api-key";
|
|
25
|
+
"oauth-token": "oauth-token";
|
|
26
|
+
"oauth-credentials": "oauth-credentials";
|
|
26
27
|
}>;
|
|
27
28
|
data: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
28
29
|
}, z.core.$strip>;
|
package/dist/auth/types.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared types for auth module.
|
|
3
3
|
*/
|
|
4
|
-
import { AGENT_CLIS } from "axshared";
|
|
4
|
+
import { AGENT_CLIS, CredentialType } from "axshared";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
/** Zod schema for AgentCli */
|
|
7
7
|
const AgentCliSchema = z.enum(AGENT_CLIS);
|
|
8
8
|
/** Extracted credential data schema */
|
|
9
9
|
const Credentials = z.object({
|
|
10
10
|
agent: AgentCliSchema,
|
|
11
|
-
type:
|
|
11
|
+
type: CredentialType,
|
|
12
12
|
data: z.record(z.string(), z.unknown()),
|
|
13
13
|
});
|
|
14
14
|
/**
|
|
@@ -22,4 +22,5 @@ type ResolveResult = {
|
|
|
22
22
|
* @see resolveCustomDirectories in axshared for behavior details
|
|
23
23
|
*/
|
|
24
24
|
declare function resolveCustomDirectory(agentId: AgentCli, configDirectory: string | undefined, dataDirectory: string | undefined): ResolveResult;
|
|
25
|
+
export type { ResolveResult };
|
|
25
26
|
export { resolveCustomDirectory };
|
package/dist/cli.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { Command } from "@commander-js/extra-typings";
|
|
8
8
|
import packageJson from "../package.json" with { type: "json" };
|
|
9
9
|
import { handleAuthExport, handleAuthInstall, handleAuthList, handleAuthRemove, handleAuthToken, } from "./commands/auth.js";
|
|
10
|
+
import { handleVaultFetch } from "./commands/vault.js";
|
|
10
11
|
import { AGENT_CLIS } from "axshared";
|
|
11
12
|
// Handle SIGINT gracefully
|
|
12
13
|
process.on("SIGINT", () => {
|
|
@@ -38,7 +39,11 @@ Examples:
|
|
|
38
39
|
axauth remove-credentials --agent claude
|
|
39
40
|
|
|
40
41
|
# Install credentials from exported file
|
|
41
|
-
axauth install-credentials --agent claude --input creds.json
|
|
42
|
+
axauth install-credentials --agent claude --input creds.json
|
|
43
|
+
|
|
44
|
+
# Fetch credentials from vault (JSON config)
|
|
45
|
+
AXVAULT='{"url":"https://vault.example.com","apiKey":"axv_sk_xxx"}' \
|
|
46
|
+
axauth vault fetch --agent claude --name ci --install`);
|
|
42
47
|
program
|
|
43
48
|
.command("list")
|
|
44
49
|
.description("List agents and their auth status")
|
|
@@ -87,4 +92,51 @@ program
|
|
|
87
92
|
dataDir: options.dataDir,
|
|
88
93
|
});
|
|
89
94
|
});
|
|
95
|
+
// Vault subcommand
|
|
96
|
+
const vault = program
|
|
97
|
+
.command("vault")
|
|
98
|
+
.description("Manage credentials stored in axvault server");
|
|
99
|
+
vault
|
|
100
|
+
.command("fetch")
|
|
101
|
+
.description("Fetch credentials from vault server")
|
|
102
|
+
.requiredOption("-a, --agent <agent>", `Agent to fetch credentials for (${AGENT_CLIS.join(", ")})`)
|
|
103
|
+
.requiredOption("-n, --name <name>", "Credential name in vault (e.g., ci, prod)")
|
|
104
|
+
.option("-i, --install", "Install fetched credentials to local storage")
|
|
105
|
+
.option("-e, --env", "Output shell export commands for environment variables")
|
|
106
|
+
.option("--config-dir <dir>", "Custom config directory (for agents without separation)")
|
|
107
|
+
.option("--data-dir <dir>", "Custom data directory for credentials")
|
|
108
|
+
.option("--json", "Pretty-print JSON output")
|
|
109
|
+
.addHelpText("after", `
|
|
110
|
+
Environment variables (option 1 - single JSON):
|
|
111
|
+
AXVAULT JSON config: {"url":"...","apiKey":"...","credentialName":"..."}
|
|
112
|
+
|
|
113
|
+
Environment variables (option 2 - individual):
|
|
114
|
+
AXVAULT_URL Vault server URL (required)
|
|
115
|
+
AXVAULT_API_KEY API key for authentication (required)
|
|
116
|
+
AXVAULT_CREDENTIAL Default credential name (optional)
|
|
117
|
+
|
|
118
|
+
Output modes:
|
|
119
|
+
(default) Output credentials as JSON
|
|
120
|
+
--install Write credentials to local storage (file/keychain)
|
|
121
|
+
--env Output shell export commands for environment variables
|
|
122
|
+
|
|
123
|
+
Examples:
|
|
124
|
+
# Fetch and output credentials as JSON
|
|
125
|
+
axauth vault fetch --agent claude --name ci
|
|
126
|
+
|
|
127
|
+
# Fetch and install OAuth credentials locally
|
|
128
|
+
axauth vault fetch --agent claude --name ci --install
|
|
129
|
+
|
|
130
|
+
# Fetch and set environment variables (works for all credential types)
|
|
131
|
+
eval "$(axauth vault fetch --agent claude --name ci --env)"
|
|
132
|
+
|
|
133
|
+
# GitHub Actions: append to $GITHUB_ENV
|
|
134
|
+
axauth vault fetch --agent claude --name ci --env | sed 's/^export //' >> $GITHUB_ENV`)
|
|
135
|
+
.action((options) => {
|
|
136
|
+
void handleVaultFetch({
|
|
137
|
+
...options,
|
|
138
|
+
configDir: options.configDir,
|
|
139
|
+
dataDir: options.dataDir,
|
|
140
|
+
});
|
|
141
|
+
});
|
|
90
142
|
await program.parseAsync(process.argv);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault commands - fetch credentials from axvault server.
|
|
3
|
+
*/
|
|
4
|
+
interface VaultFetchOptions {
|
|
5
|
+
agent: string;
|
|
6
|
+
name: string;
|
|
7
|
+
install?: boolean;
|
|
8
|
+
env?: boolean;
|
|
9
|
+
configDir?: string;
|
|
10
|
+
dataDir?: string;
|
|
11
|
+
json?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Handle vault fetch command.
|
|
15
|
+
*
|
|
16
|
+
* Fetches credentials from the vault server and either:
|
|
17
|
+
* - Outputs them as JSON (default) for piping to other commands
|
|
18
|
+
* - Installs them locally with --install flag (writes to file)
|
|
19
|
+
* - Outputs shell export commands with --env flag (for eval/source)
|
|
20
|
+
*/
|
|
21
|
+
declare function handleVaultFetch(options: VaultFetchOptions): Promise<void>;
|
|
22
|
+
export { handleVaultFetch };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault commands - fetch credentials from axvault server.
|
|
3
|
+
*/
|
|
4
|
+
import { credentialsToEnvironment, installCredentials, } from "../auth/registry.js";
|
|
5
|
+
import { fetchVaultCredentials, } from "../vault/vault-client.js";
|
|
6
|
+
import { getVaultConfig } from "../vault/vault-config.js";
|
|
7
|
+
import { validateAgent } from "./validate-agent.js";
|
|
8
|
+
/** Human-readable error messages for vault failures */
|
|
9
|
+
const FAILURE_MESSAGES = {
|
|
10
|
+
"not-configured": "Vault not configured. Set AXVAULT (JSON) or AXVAULT_URL + AXVAULT_API_KEY.",
|
|
11
|
+
unreachable: "Vault server is unreachable. Check vault URL and network.",
|
|
12
|
+
unauthorized: "Invalid API key. Check apiKey in vault config.",
|
|
13
|
+
forbidden: "Access denied. API key doesn't have read access to this credential.",
|
|
14
|
+
"not-found": "Credential not found in vault.",
|
|
15
|
+
"legacy-credential": "Credential uses legacy encryption. Re-upload it to vault.",
|
|
16
|
+
"client-error": "Invalid request. Check credential name and try again.",
|
|
17
|
+
"server-error": "Vault server error. Check server logs.",
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Escape a value for safe use in shell export statement.
|
|
21
|
+
* Uses single quotes and escapes any single quotes in the value.
|
|
22
|
+
*
|
|
23
|
+
* @throws Error if value contains newlines or null bytes (cannot be safely exported)
|
|
24
|
+
*/
|
|
25
|
+
function shellEscape(value) {
|
|
26
|
+
if (value.includes("\n") || value.includes("\0")) {
|
|
27
|
+
throw new Error("Credential values cannot contain newlines or null bytes");
|
|
28
|
+
}
|
|
29
|
+
// Replace single quotes with '\'' (end quote, escaped quote, start quote)
|
|
30
|
+
return `'${value.replaceAll("'", String.raw `'\''`)}'`;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Handle vault fetch command.
|
|
34
|
+
*
|
|
35
|
+
* Fetches credentials from the vault server and either:
|
|
36
|
+
* - Outputs them as JSON (default) for piping to other commands
|
|
37
|
+
* - Installs them locally with --install flag (writes to file)
|
|
38
|
+
* - Outputs shell export commands with --env flag (for eval/source)
|
|
39
|
+
*/
|
|
40
|
+
async function handleVaultFetch(options) {
|
|
41
|
+
// Validate agent
|
|
42
|
+
const agentId = validateAgent(options.agent);
|
|
43
|
+
if (!agentId)
|
|
44
|
+
return;
|
|
45
|
+
// Validate mutually exclusive options
|
|
46
|
+
if (options.install && options.env) {
|
|
47
|
+
console.error("Error: --install and --env are mutually exclusive");
|
|
48
|
+
process.exitCode = 2;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Check vault is configured
|
|
52
|
+
const vaultConfig = getVaultConfig();
|
|
53
|
+
if (!vaultConfig) {
|
|
54
|
+
console.error(`Error: ${FAILURE_MESSAGES["not-configured"]}`);
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// Fetch from vault
|
|
59
|
+
const result = await fetchVaultCredentials({
|
|
60
|
+
agentId,
|
|
61
|
+
name: options.name,
|
|
62
|
+
});
|
|
63
|
+
if (!result.ok) {
|
|
64
|
+
console.error(`Error: ${FAILURE_MESSAGES[result.reason]}`);
|
|
65
|
+
process.exitCode = 1;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const { credentials, refreshed } = result;
|
|
69
|
+
// Log refresh status to stderr (not stdout, to allow piping)
|
|
70
|
+
if (refreshed) {
|
|
71
|
+
console.error("Note: Credentials were auto-refreshed by vault");
|
|
72
|
+
}
|
|
73
|
+
if (options.env) {
|
|
74
|
+
// Output shell export commands (for eval/source)
|
|
75
|
+
const environmentVariables = credentialsToEnvironment(credentials);
|
|
76
|
+
const environmentEntries = Object.entries(environmentVariables);
|
|
77
|
+
if (environmentEntries.length === 0) {
|
|
78
|
+
console.error("Warning: No environment variables to export for this credential type");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
for (const [key, value] of environmentEntries) {
|
|
83
|
+
console.log(`export ${key}=${shellEscape(value)}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
console.error(`Error: ${error instanceof Error ? error.message : "Failed to escape credential value"}`);
|
|
88
|
+
process.exitCode = 1;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else if (options.install) {
|
|
93
|
+
// Install credentials locally (writes to file)
|
|
94
|
+
const installResult = installCredentials(credentials, {
|
|
95
|
+
configDir: options.configDir,
|
|
96
|
+
dataDir: options.dataDir,
|
|
97
|
+
});
|
|
98
|
+
if (!installResult.ok) {
|
|
99
|
+
// For env-only credentials, suggest using --env instead
|
|
100
|
+
if (installResult.message.includes("cannot be installed")) {
|
|
101
|
+
console.error(`Error: ${installResult.message}`);
|
|
102
|
+
console.error("Hint: Use --env to get shell export commands instead");
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
console.error(`Error: ${installResult.message}`);
|
|
106
|
+
}
|
|
107
|
+
process.exitCode = 1;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
console.error(installResult.message);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
// Output credentials as JSON (default)
|
|
114
|
+
if (options.json) {
|
|
115
|
+
console.log(JSON.stringify(credentials, undefined, 2));
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Compact JSON for piping
|
|
119
|
+
console.log(JSON.stringify(credentials));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export { handleVaultFetch };
|
package/dist/index.d.ts
CHANGED
|
@@ -35,5 +35,7 @@ export { AGENT_CLIS } from "axshared";
|
|
|
35
35
|
export type { AdapterCapabilities, AuthAdapter, InstallOptions, OperationResult, RemoveOptions, } from "./auth/adapter.js";
|
|
36
36
|
export type { AuthStatus, Credentials } from "./auth/types.js";
|
|
37
37
|
export { checkAllAuth, checkAuth, credentialsToEnvironment, extractRawCredentials, extractRawCredentialsFromDirectory, getAccessToken, getAgentAccessToken, getCredentialsEnvironmentVariableName, installCredentials, installCredentialsFromEnvironmentVariable, removeCredentials, getAdapter, getAllAdapters, getCapabilities, type ExtractOptions, type InstallFromEnvironmentOptions, } from "./auth/registry.js";
|
|
38
|
+
export { getVaultConfig, isVaultConfigured, type VaultConfig, } from "./vault/vault-config.js";
|
|
39
|
+
export { fetchVaultCredentials, type VaultFailureReason, type VaultFetchOptions, type VaultResult, } from "./vault/vault-client.js";
|
|
38
40
|
export { isCredentialExpired, isTokenExpired, } from "./auth/is-token-expired.js";
|
|
39
41
|
export { refreshAndPersist, refreshCredentials, type RefreshAndPersistResult, type RefreshOptions, type RefreshResult, } from "./auth/refresh-credentials.js";
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,9 @@ export {
|
|
|
37
37
|
checkAllAuth, checkAuth, credentialsToEnvironment, extractRawCredentials, extractRawCredentialsFromDirectory, getAccessToken, getAgentAccessToken, getCredentialsEnvironmentVariableName, installCredentials, installCredentialsFromEnvironmentVariable, removeCredentials,
|
|
38
38
|
// Adapter access
|
|
39
39
|
getAdapter, getAllAdapters, getCapabilities, } from "./auth/registry.js";
|
|
40
|
+
// Vault utilities
|
|
41
|
+
export { getVaultConfig, isVaultConfigured, } from "./vault/vault-config.js";
|
|
42
|
+
export { fetchVaultCredentials, } from "./vault/vault-client.js";
|
|
40
43
|
// Token refresh utilities
|
|
41
44
|
export { isCredentialExpired, isTokenExpired, } from "./auth/is-token-expired.js";
|
|
42
45
|
export { refreshAndPersist, refreshCredentials, } from "./auth/refresh-credentials.js";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for fetching credentials from axvault server.
|
|
3
|
+
*
|
|
4
|
+
* The vault client provides a simple interface to fetch credentials from
|
|
5
|
+
* a remote axvault server. It handles authentication, error responses,
|
|
6
|
+
* and returns a typed result.
|
|
7
|
+
*/
|
|
8
|
+
import type { AgentCli } from "axshared";
|
|
9
|
+
import type { Credentials } from "../auth/types.js";
|
|
10
|
+
/** Reason why vault fetch failed */
|
|
11
|
+
type VaultFailureReason = "not-configured" | "unreachable" | "unauthorized" | "forbidden" | "not-found" | "legacy-credential" | "client-error" | "server-error";
|
|
12
|
+
/** Result of a vault fetch operation */
|
|
13
|
+
type VaultResult = {
|
|
14
|
+
ok: true;
|
|
15
|
+
credentials: Credentials;
|
|
16
|
+
refreshed: boolean;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
reason: VaultFailureReason;
|
|
20
|
+
};
|
|
21
|
+
/** Options for fetching from vault */
|
|
22
|
+
interface VaultFetchOptions {
|
|
23
|
+
/** Agent ID (e.g., "claude", "codex") */
|
|
24
|
+
agentId: AgentCli;
|
|
25
|
+
/** Credential name in vault (e.g., "ci", "prod") */
|
|
26
|
+
name: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Fetch credentials from the axvault server.
|
|
30
|
+
*
|
|
31
|
+
* This function attempts to fetch credentials for a specific agent and name
|
|
32
|
+
* from the configured vault server. If the vault is not configured, it returns
|
|
33
|
+
* a "not-configured" result without making any network requests.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const result = await fetchVaultCredentials({ agentId: "claude", name: "ci" });
|
|
37
|
+
* if (result.ok) {
|
|
38
|
+
* console.log("Got credentials:", result.credentials);
|
|
39
|
+
* if (result.refreshed) {
|
|
40
|
+
* console.log("Credentials were auto-refreshed by vault");
|
|
41
|
+
* }
|
|
42
|
+
* } else {
|
|
43
|
+
* console.log("Failed:", result.reason);
|
|
44
|
+
* }
|
|
45
|
+
*/
|
|
46
|
+
declare function fetchVaultCredentials(options: VaultFetchOptions): Promise<VaultResult>;
|
|
47
|
+
export type { VaultFailureReason, VaultFetchOptions, VaultResult };
|
|
48
|
+
export { fetchVaultCredentials };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for fetching credentials from axvault server.
|
|
3
|
+
*
|
|
4
|
+
* The vault client provides a simple interface to fetch credentials from
|
|
5
|
+
* a remote axvault server. It handles authentication, error responses,
|
|
6
|
+
* and returns a typed result.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { getVaultConfig } from "./vault-config.js";
|
|
10
|
+
/** Zod schema for vault API response */
|
|
11
|
+
const VaultCredentialResponse = z.object({
|
|
12
|
+
agent: z.string(),
|
|
13
|
+
name: z.string(),
|
|
14
|
+
type: z.enum(["oauth-credentials", "oauth-token", "api-key"]),
|
|
15
|
+
data: z.record(z.string(), z.unknown()),
|
|
16
|
+
expiresAt: z.string().nullable(),
|
|
17
|
+
updatedAt: z.string(),
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Fetch credentials from the axvault server.
|
|
21
|
+
*
|
|
22
|
+
* This function attempts to fetch credentials for a specific agent and name
|
|
23
|
+
* from the configured vault server. If the vault is not configured, it returns
|
|
24
|
+
* a "not-configured" result without making any network requests.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const result = await fetchVaultCredentials({ agentId: "claude", name: "ci" });
|
|
28
|
+
* if (result.ok) {
|
|
29
|
+
* console.log("Got credentials:", result.credentials);
|
|
30
|
+
* if (result.refreshed) {
|
|
31
|
+
* console.log("Credentials were auto-refreshed by vault");
|
|
32
|
+
* }
|
|
33
|
+
* } else {
|
|
34
|
+
* console.log("Failed:", result.reason);
|
|
35
|
+
* }
|
|
36
|
+
*/
|
|
37
|
+
async function fetchVaultCredentials(options) {
|
|
38
|
+
const config = getVaultConfig();
|
|
39
|
+
if (!config) {
|
|
40
|
+
return { ok: false, reason: "not-configured" };
|
|
41
|
+
}
|
|
42
|
+
const url = `${config.url}/api/v1/credentials/${encodeURIComponent(options.agentId)}/${encodeURIComponent(options.name)}`;
|
|
43
|
+
try {
|
|
44
|
+
const response = await fetch(url, {
|
|
45
|
+
method: "GET",
|
|
46
|
+
headers: {
|
|
47
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
48
|
+
Accept: "application/json",
|
|
49
|
+
"User-Agent": "axauth-vault-client",
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
// Handle error responses
|
|
53
|
+
if (response.status === 401) {
|
|
54
|
+
return { ok: false, reason: "unauthorized" };
|
|
55
|
+
}
|
|
56
|
+
if (response.status === 403) {
|
|
57
|
+
return { ok: false, reason: "forbidden" };
|
|
58
|
+
}
|
|
59
|
+
if (response.status === 404) {
|
|
60
|
+
return { ok: false, reason: "not-found" };
|
|
61
|
+
}
|
|
62
|
+
if (response.status === 410) {
|
|
63
|
+
return { ok: false, reason: "legacy-credential" };
|
|
64
|
+
}
|
|
65
|
+
if (response.status >= 500) {
|
|
66
|
+
return { ok: false, reason: "server-error" };
|
|
67
|
+
}
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
// Other 4xx errors (400 Bad Request, 429 Rate Limited, etc.)
|
|
70
|
+
return { ok: false, reason: "client-error" };
|
|
71
|
+
}
|
|
72
|
+
// Parse and validate response body
|
|
73
|
+
const json = await response.json();
|
|
74
|
+
const parsed = VaultCredentialResponse.safeParse(json);
|
|
75
|
+
if (!parsed.success) {
|
|
76
|
+
return { ok: false, reason: "server-error" };
|
|
77
|
+
}
|
|
78
|
+
const body = parsed.data;
|
|
79
|
+
// Check refresh headers
|
|
80
|
+
const wasRefreshed = response.headers.get("X-Axvault-Refreshed") === "true";
|
|
81
|
+
const refreshFailed = response.headers.get("X-Axvault-Refresh-Failed") === "true";
|
|
82
|
+
// Log warning if refresh failed (stale credentials returned)
|
|
83
|
+
if (refreshFailed) {
|
|
84
|
+
console.error(`[axauth] Vault returned stale credentials for ${options.agentId}/${options.name} (refresh failed)`);
|
|
85
|
+
}
|
|
86
|
+
const credentials = {
|
|
87
|
+
agent: options.agentId,
|
|
88
|
+
type: body.type,
|
|
89
|
+
data: body.data,
|
|
90
|
+
};
|
|
91
|
+
return {
|
|
92
|
+
ok: true,
|
|
93
|
+
credentials,
|
|
94
|
+
refreshed: wasRefreshed,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Network errors (DNS failure, connection refused, timeout, etc.)
|
|
99
|
+
return { ok: false, reason: "unreachable" };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export { fetchVaultCredentials };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault configuration from environment variables.
|
|
3
|
+
*
|
|
4
|
+
* Vault is an optional credential source. When configured via environment
|
|
5
|
+
* variables, axauth will attempt to fetch credentials from the vault server
|
|
6
|
+
* before falling back to local storage.
|
|
7
|
+
*/
|
|
8
|
+
/** Vault configuration parsed from environment */
|
|
9
|
+
interface VaultConfig {
|
|
10
|
+
/** Vault server URL (e.g., "https://vault.example.com") */
|
|
11
|
+
url: string;
|
|
12
|
+
/** API key for authentication */
|
|
13
|
+
apiKey: string;
|
|
14
|
+
/** Default credential name to fetch (e.g., "ci", "prod") */
|
|
15
|
+
credentialName?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get vault configuration from environment variables.
|
|
19
|
+
*
|
|
20
|
+
* Supports two configuration methods (checked in order):
|
|
21
|
+
*
|
|
22
|
+
* 1. Single JSON env var:
|
|
23
|
+
* - `AXVAULT`: JSON object with url, apiKey, and optional credentialName
|
|
24
|
+
*
|
|
25
|
+
* 2. Individual env vars:
|
|
26
|
+
* - `AXVAULT_URL`: Vault server URL (required)
|
|
27
|
+
* - `AXVAULT_API_KEY`: API key for authentication (required)
|
|
28
|
+
* - `AXVAULT_CREDENTIAL`: Default credential name (optional)
|
|
29
|
+
*
|
|
30
|
+
* @returns Vault configuration if configured, undefined otherwise
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // Option 1: Single JSON env var
|
|
34
|
+
* // AXVAULT='{"url":"https://vault.axkit.dev","apiKey":"axv_sk_xxx","credentialName":"ci"}'
|
|
35
|
+
*
|
|
36
|
+
* // Option 2: Individual env vars
|
|
37
|
+
* // AXVAULT_URL=https://vault.axkit.dev
|
|
38
|
+
* // AXVAULT_API_KEY=axv_sk_xxx
|
|
39
|
+
* // AXVAULT_CREDENTIAL=ci
|
|
40
|
+
*
|
|
41
|
+
* const config = getVaultConfig();
|
|
42
|
+
* if (config) {
|
|
43
|
+
* // Vault is configured, can fetch credentials
|
|
44
|
+
* }
|
|
45
|
+
*/
|
|
46
|
+
declare function getVaultConfig(): VaultConfig | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Check if vault is configured.
|
|
49
|
+
*
|
|
50
|
+
* @returns true if AXVAULT_URL and AXVAULT_API_KEY are both set
|
|
51
|
+
*/
|
|
52
|
+
declare function isVaultConfigured(): boolean;
|
|
53
|
+
export type { VaultConfig };
|
|
54
|
+
export { getVaultConfig, isVaultConfigured };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault configuration from environment variables.
|
|
3
|
+
*
|
|
4
|
+
* Vault is an optional credential source. When configured via environment
|
|
5
|
+
* variables, axauth will attempt to fetch credentials from the vault server
|
|
6
|
+
* before falling back to local storage.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
/** Zod schema for vault config JSON */
|
|
10
|
+
const VaultConfigJson = z.object({
|
|
11
|
+
url: z.string(),
|
|
12
|
+
apiKey: z.string(),
|
|
13
|
+
credentialName: z.string().optional(),
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Validate and normalize a vault URL.
|
|
17
|
+
*
|
|
18
|
+
* @returns Normalized URL (trailing slashes removed) or undefined if invalid
|
|
19
|
+
*/
|
|
20
|
+
function validateVaultUrl(url) {
|
|
21
|
+
try {
|
|
22
|
+
new URL(url);
|
|
23
|
+
return url.replaceAll(/\/+$/gu, "");
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
console.error(`Warning: Invalid vault URL "${url}", ignoring`);
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Parse vault config from JSON environment variable.
|
|
32
|
+
*
|
|
33
|
+
* @returns Parsed config or undefined if invalid/not set
|
|
34
|
+
*/
|
|
35
|
+
function parseVaultConfigJson() {
|
|
36
|
+
const json = process.env.AXVAULT;
|
|
37
|
+
if (!json)
|
|
38
|
+
return undefined;
|
|
39
|
+
try {
|
|
40
|
+
const parsed = VaultConfigJson.parse(JSON.parse(json));
|
|
41
|
+
const url = validateVaultUrl(parsed.url);
|
|
42
|
+
if (!url)
|
|
43
|
+
return undefined;
|
|
44
|
+
return {
|
|
45
|
+
url,
|
|
46
|
+
apiKey: parsed.apiKey,
|
|
47
|
+
credentialName: parsed.credentialName,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
console.error("Warning: AXVAULT env var contains invalid JSON, ignoring");
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get vault configuration from environment variables.
|
|
57
|
+
*
|
|
58
|
+
* Supports two configuration methods (checked in order):
|
|
59
|
+
*
|
|
60
|
+
* 1. Single JSON env var:
|
|
61
|
+
* - `AXVAULT`: JSON object with url, apiKey, and optional credentialName
|
|
62
|
+
*
|
|
63
|
+
* 2. Individual env vars:
|
|
64
|
+
* - `AXVAULT_URL`: Vault server URL (required)
|
|
65
|
+
* - `AXVAULT_API_KEY`: API key for authentication (required)
|
|
66
|
+
* - `AXVAULT_CREDENTIAL`: Default credential name (optional)
|
|
67
|
+
*
|
|
68
|
+
* @returns Vault configuration if configured, undefined otherwise
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* // Option 1: Single JSON env var
|
|
72
|
+
* // AXVAULT='{"url":"https://vault.axkit.dev","apiKey":"axv_sk_xxx","credentialName":"ci"}'
|
|
73
|
+
*
|
|
74
|
+
* // Option 2: Individual env vars
|
|
75
|
+
* // AXVAULT_URL=https://vault.axkit.dev
|
|
76
|
+
* // AXVAULT_API_KEY=axv_sk_xxx
|
|
77
|
+
* // AXVAULT_CREDENTIAL=ci
|
|
78
|
+
*
|
|
79
|
+
* const config = getVaultConfig();
|
|
80
|
+
* if (config) {
|
|
81
|
+
* // Vault is configured, can fetch credentials
|
|
82
|
+
* }
|
|
83
|
+
*/
|
|
84
|
+
function getVaultConfig() {
|
|
85
|
+
// Try JSON config first
|
|
86
|
+
const jsonConfig = parseVaultConfigJson();
|
|
87
|
+
if (jsonConfig)
|
|
88
|
+
return jsonConfig;
|
|
89
|
+
// Fall back to individual env vars
|
|
90
|
+
const rawUrl = process.env.AXVAULT_URL;
|
|
91
|
+
const apiKey = process.env.AXVAULT_API_KEY;
|
|
92
|
+
if (!rawUrl || !apiKey) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
const url = validateVaultUrl(rawUrl);
|
|
96
|
+
if (!url)
|
|
97
|
+
return undefined;
|
|
98
|
+
return {
|
|
99
|
+
url,
|
|
100
|
+
apiKey,
|
|
101
|
+
credentialName: process.env.AXVAULT_CREDENTIAL,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Check if vault is configured.
|
|
106
|
+
*
|
|
107
|
+
* @returns true if AXVAULT_URL and AXVAULT_API_KEY are both set
|
|
108
|
+
*/
|
|
109
|
+
function isVaultConfigured() {
|
|
110
|
+
return getVaultConfig() !== undefined;
|
|
111
|
+
}
|
|
112
|
+
export { getVaultConfig, isVaultConfigured };
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "axauth",
|
|
3
3
|
"author": "Łukasz Jerciński",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.9.0",
|
|
6
6
|
"description": "Authentication management library and CLI for AI coding agents",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"automation",
|
|
63
63
|
"coding-assistant"
|
|
64
64
|
],
|
|
65
|
-
"packageManager": "pnpm@10.
|
|
65
|
+
"packageManager": "pnpm@10.28.0",
|
|
66
66
|
"engines": {
|
|
67
67
|
"node": ">=22.14.0"
|
|
68
68
|
},
|
|
@@ -70,19 +70,19 @@
|
|
|
70
70
|
"@commander-js/extra-typings": "^14.0.0",
|
|
71
71
|
"@inquirer/password": "^5.0.3",
|
|
72
72
|
"axconfig": "^3.6.0",
|
|
73
|
-
"axshared": "^1.
|
|
73
|
+
"axshared": "^1.9.0",
|
|
74
74
|
"commander": "^14.0.2",
|
|
75
75
|
"zod": "^4.3.5"
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
78
|
"@total-typescript/ts-reset": "^0.6.1",
|
|
79
|
-
"@types/node": "^25.0.
|
|
79
|
+
"@types/node": "^25.0.5",
|
|
80
80
|
"@vitest/coverage-v8": "^4.0.16",
|
|
81
81
|
"eslint": "^9.39.2",
|
|
82
|
-
"eslint-config-
|
|
82
|
+
"eslint-config-axkit": "^1.0.0",
|
|
83
83
|
"fta-check": "^1.5.1",
|
|
84
84
|
"fta-cli": "^3.0.0",
|
|
85
|
-
"knip": "^5.80.
|
|
85
|
+
"knip": "^5.80.2",
|
|
86
86
|
"prettier": "3.7.4",
|
|
87
87
|
"semantic-release": "^25.0.2",
|
|
88
88
|
"typescript": "^5.9.3",
|