axauth 3.1.6 → 3.3.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/dist/auth/adapter.d.ts +16 -0
- package/dist/auth/agents/claude.js +18 -0
- package/dist/auth/agents/codex.js +31 -0
- package/dist/auth/agents/copilot.js +15 -0
- package/dist/auth/agents/gemini.js +21 -0
- package/dist/auth/agents/opencode.js +16 -0
- package/dist/auth/get-access-token-with-refresh.d.ts +12 -0
- package/dist/auth/get-access-token-with-refresh.js +44 -0
- package/dist/auth/is-token-expired.js +32 -12
- package/dist/auth/registry.js +6 -45
- package/dist/cli.js +7 -1
- package/dist/commands/vault-failure-messages.d.ts +4 -0
- package/dist/commands/vault-failure-messages.js +12 -0
- package/dist/commands/{vault.d.ts → vault-fetch.d.ts} +2 -15
- package/dist/commands/{vault.js → vault-fetch.js} +13 -77
- package/dist/commands/vault-push.d.ts +19 -0
- package/dist/commands/vault-push.js +70 -0
- package/dist/vault/vault-client.d.ts +6 -0
- package/dist/vault/vault-client.js +14 -0
- package/package.json +13 -13
package/dist/auth/adapter.d.ts
CHANGED
|
@@ -241,6 +241,22 @@ interface AuthAdapter {
|
|
|
241
241
|
* to specify which provider's token to extract.
|
|
242
242
|
*/
|
|
243
243
|
getAccessToken(creds: Credentials, options?: TokenOptions): string | undefined;
|
|
244
|
+
/**
|
|
245
|
+
* Check whether credentials are expired.
|
|
246
|
+
*
|
|
247
|
+
* Returns:
|
|
248
|
+
* - `true` when credentials are expired (or inside refresh buffer)
|
|
249
|
+
* - `false` when credentials are valid
|
|
250
|
+
* - `undefined` when expiry cannot be determined
|
|
251
|
+
*/
|
|
252
|
+
isExpired(creds: Credentials, bufferSeconds?: number): boolean | undefined;
|
|
253
|
+
/**
|
|
254
|
+
* Check whether credentials can be refreshed.
|
|
255
|
+
*
|
|
256
|
+
* Returns true when credentials contain a refresh token compatible with
|
|
257
|
+
* the adapter's underlying auth format.
|
|
258
|
+
*/
|
|
259
|
+
isRefreshable(creds: Credentials): boolean;
|
|
244
260
|
/**
|
|
245
261
|
* Convert credentials to environment variables.
|
|
246
262
|
*
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { extractCredsFromDirectory } from "../extract-creds-from-directory.js";
|
|
8
|
+
import { isCredentialExpired, isTokenExpired } from "../is-token-expired.js";
|
|
8
9
|
import { CREDS_FILE_NAME, installCredentials, removeCredentials, } from "./claude-install.js";
|
|
9
10
|
import { AGENT_ID, checkAuth, findStoredCredentials as findStoredCredentialsInternal, getEnvironmentCredentials as getEnvironmentCredentialsInternal, } from "./claude-storage.js";
|
|
10
11
|
/** Claude Code authentication adapter */
|
|
@@ -61,6 +62,23 @@ const claudeCodeAdapter = {
|
|
|
61
62
|
}
|
|
62
63
|
return undefined;
|
|
63
64
|
},
|
|
65
|
+
isExpired(creds, bufferSeconds = 60) {
|
|
66
|
+
const data = creds.data;
|
|
67
|
+
const token = (creds.type === "oauth-credentials" || creds.type === "oauth-token") &&
|
|
68
|
+
typeof data.accessToken === "string"
|
|
69
|
+
? data.accessToken
|
|
70
|
+
: undefined;
|
|
71
|
+
if (token) {
|
|
72
|
+
const expired = isTokenExpired(token, bufferSeconds);
|
|
73
|
+
if (expired !== undefined)
|
|
74
|
+
return expired;
|
|
75
|
+
}
|
|
76
|
+
return isCredentialExpired(data, bufferSeconds);
|
|
77
|
+
},
|
|
78
|
+
isRefreshable(creds) {
|
|
79
|
+
return (creds.type === "oauth-credentials" &&
|
|
80
|
+
typeof creds.data.refreshToken === "string");
|
|
81
|
+
},
|
|
64
82
|
credentialsToEnvironment(creds) {
|
|
65
83
|
const environment = {};
|
|
66
84
|
const data = creds.data;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync, readFileSync } from "node:fs";
|
|
7
7
|
import path from "node:path";
|
|
8
|
+
import { isCredentialExpired, isTokenExpired } from "../is-token-expired.js";
|
|
8
9
|
import { checkAuth, findStoredCredentials, getEnvironmentCredentials, } from "./codex-auth-check.js";
|
|
9
10
|
import { CREDS_FILE_NAME, installCodexCredentials, removeCodexCredentials, } from "./codex-install.js";
|
|
10
11
|
const AGENT_ID = "codex";
|
|
@@ -61,6 +62,36 @@ const codexAdapter = {
|
|
|
61
62
|
}
|
|
62
63
|
return undefined;
|
|
63
64
|
},
|
|
65
|
+
isExpired(creds, bufferSeconds = 60) {
|
|
66
|
+
const data = creds.data;
|
|
67
|
+
const token = (creds.type === "api-key" && typeof data.apiKey === "string"
|
|
68
|
+
? data.apiKey
|
|
69
|
+
: undefined) ??
|
|
70
|
+
(creds.type === "oauth-credentials" &&
|
|
71
|
+
typeof data.access_token === "string"
|
|
72
|
+
? data.access_token
|
|
73
|
+
: undefined) ??
|
|
74
|
+
(creds.type === "oauth-credentials" &&
|
|
75
|
+
typeof data.tokens === "object" &&
|
|
76
|
+
data.tokens !== null &&
|
|
77
|
+
typeof data.tokens.access_token === "string"
|
|
78
|
+
? data.tokens.access_token
|
|
79
|
+
: undefined);
|
|
80
|
+
if (token) {
|
|
81
|
+
const expired = isTokenExpired(token, bufferSeconds);
|
|
82
|
+
if (expired !== undefined)
|
|
83
|
+
return expired;
|
|
84
|
+
}
|
|
85
|
+
return isCredentialExpired(data, bufferSeconds);
|
|
86
|
+
},
|
|
87
|
+
isRefreshable(creds) {
|
|
88
|
+
if (creds.type !== "oauth-credentials")
|
|
89
|
+
return false;
|
|
90
|
+
const tokens = typeof creds.data.tokens === "object" && creds.data.tokens !== null
|
|
91
|
+
? creds.data.tokens
|
|
92
|
+
: undefined;
|
|
93
|
+
return typeof tokens?.refresh_token === "string";
|
|
94
|
+
},
|
|
64
95
|
credentialsToEnvironment(creds) {
|
|
65
96
|
const environment = {};
|
|
66
97
|
const data = creds.data;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { extractCredsFromDirectory } from "../extract-creds-from-directory.js";
|
|
9
|
+
import { isCredentialExpired, isTokenExpired } from "../is-token-expired.js";
|
|
9
10
|
import { resolveCustomDirectory } from "../validate-directories.js";
|
|
10
11
|
import { findFirstAvailableToken } from "./copilot-auth-check.js";
|
|
11
12
|
import { CREDS_FILE_NAME, installCopilotCredentials, } from "./copilot-install.js";
|
|
@@ -109,6 +110,20 @@ const copilotAdapter = {
|
|
|
109
110
|
}
|
|
110
111
|
return undefined;
|
|
111
112
|
},
|
|
113
|
+
isExpired(creds, bufferSeconds = 60) {
|
|
114
|
+
const token = typeof creds.data.accessToken === "string"
|
|
115
|
+
? creds.data.accessToken
|
|
116
|
+
: undefined;
|
|
117
|
+
if (token) {
|
|
118
|
+
const expired = isTokenExpired(token, bufferSeconds);
|
|
119
|
+
if (expired !== undefined)
|
|
120
|
+
return expired;
|
|
121
|
+
}
|
|
122
|
+
return isCredentialExpired(creds.data, bufferSeconds);
|
|
123
|
+
},
|
|
124
|
+
isRefreshable() {
|
|
125
|
+
return false;
|
|
126
|
+
},
|
|
112
127
|
credentialsToEnvironment(creds) {
|
|
113
128
|
const environment = {};
|
|
114
129
|
const data = creds.data;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { extractCredsFromDirectory } from "../extract-creds-from-directory.js";
|
|
8
|
+
import { isCredentialExpired, isTokenExpired } from "../is-token-expired.js";
|
|
8
9
|
import { checkEnvironmentAuth, findCredentials } from "./gemini-auth-check.js";
|
|
9
10
|
import { installOAuthCredentials, removeGeminiCredentials, } from "./gemini-install.js";
|
|
10
11
|
const AGENT_ID = "gemini";
|
|
@@ -91,6 +92,26 @@ const geminiAdapter = {
|
|
|
91
92
|
}
|
|
92
93
|
return undefined;
|
|
93
94
|
},
|
|
95
|
+
isExpired(creds, bufferSeconds = 60) {
|
|
96
|
+
const data = creds.data;
|
|
97
|
+
const token = (creds.type === "api-key" && typeof data.apiKey === "string"
|
|
98
|
+
? data.apiKey
|
|
99
|
+
: undefined) ??
|
|
100
|
+
(creds.type === "oauth-credentials" &&
|
|
101
|
+
typeof data.access_token === "string"
|
|
102
|
+
? data.access_token
|
|
103
|
+
: undefined);
|
|
104
|
+
if (token) {
|
|
105
|
+
const expired = isTokenExpired(token, bufferSeconds);
|
|
106
|
+
if (expired !== undefined)
|
|
107
|
+
return expired;
|
|
108
|
+
}
|
|
109
|
+
return isCredentialExpired(data, bufferSeconds);
|
|
110
|
+
},
|
|
111
|
+
isRefreshable(creds) {
|
|
112
|
+
return (creds.type === "oauth-credentials" &&
|
|
113
|
+
typeof creds.data.refresh_token === "string");
|
|
114
|
+
},
|
|
94
115
|
credentialsToEnvironment(creds) {
|
|
95
116
|
const environment = {};
|
|
96
117
|
const data = creds.data;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* - Data (auth): ~/.local/share/opencode (XDG_DATA_HOME/opencode)
|
|
9
9
|
*/
|
|
10
10
|
import path from "node:path";
|
|
11
|
+
import { isCredentialExpired, isTokenExpired } from "../is-token-expired.js";
|
|
11
12
|
import { extractTokenFromEntry, OpenCodeAuth } from "./opencode-schema.js";
|
|
12
13
|
import { AGENT_ID, CREDS_FILE_NAME, getDefaultAuthFilePath, mapToCredentialType, readAuthFile, } from "./opencode-credentials.js";
|
|
13
14
|
import { installCredentials, removeCredentials } from "./opencode-storage.js";
|
|
@@ -122,6 +123,21 @@ const opencodeAdapter = {
|
|
|
122
123
|
return undefined;
|
|
123
124
|
return extractTokenFromEntry(parsed.data);
|
|
124
125
|
},
|
|
126
|
+
isExpired(creds, bufferSeconds = 60) {
|
|
127
|
+
const parsed = OpenCodeAuth.safeParse(creds.data);
|
|
128
|
+
if (!parsed.success)
|
|
129
|
+
return undefined;
|
|
130
|
+
if (parsed.data.type !== "oauth")
|
|
131
|
+
return undefined;
|
|
132
|
+
const tokenExpired = isTokenExpired(parsed.data.access, bufferSeconds);
|
|
133
|
+
if (tokenExpired !== undefined)
|
|
134
|
+
return tokenExpired;
|
|
135
|
+
return isCredentialExpired({ expires: parsed.data.expires }, bufferSeconds);
|
|
136
|
+
},
|
|
137
|
+
isRefreshable(creds) {
|
|
138
|
+
const parsed = OpenCodeAuth.safeParse(creds.data);
|
|
139
|
+
return parsed.success && parsed.data.type === "oauth";
|
|
140
|
+
},
|
|
125
141
|
credentialsToEnvironment() {
|
|
126
142
|
// OpenCode requires auth.json file, no env var support
|
|
127
143
|
return {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AgentCli } from "axshared";
|
|
2
|
+
import type { AuthAdapter, FindStoredCredentialsOptions, InstallOptions, OperationResult, TokenOptions } from "./adapter.js";
|
|
3
|
+
import type { Credentials } from "./types.js";
|
|
4
|
+
interface AccessTokenDependencies {
|
|
5
|
+
adapter: AuthAdapter;
|
|
6
|
+
installCredentials: (agentId: AgentCli, creds: Credentials, options?: InstallOptions) => OperationResult;
|
|
7
|
+
}
|
|
8
|
+
declare function findCredentialsWithEnvironmentPriority(adapter: AuthAdapter, options?: FindStoredCredentialsOptions): Credentials | undefined;
|
|
9
|
+
type AccessTokenGetter = (agentId: AgentCli, creds: Credentials, options?: TokenOptions) => Promise<string | undefined>;
|
|
10
|
+
declare function getAgentAccessTokenFromStoredCredentials(agentId: AgentCli, adapter: AuthAdapter, options: TokenOptions | undefined, getAccessToken: AccessTokenGetter): Promise<string | undefined>;
|
|
11
|
+
declare function getAccessTokenWithRefresh(agentId: AgentCli, creds: Credentials, options: TokenOptions | undefined, dependencies: AccessTokenDependencies): Promise<string | undefined>;
|
|
12
|
+
export { findCredentialsWithEnvironmentPriority, getAccessTokenWithRefresh, getAgentAccessTokenFromStoredCredentials, };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { refreshAndPersist } from "./refresh-credentials.js";
|
|
2
|
+
function findCredentialsWithEnvironmentPriority(adapter, options) {
|
|
3
|
+
const environmentCredentials = adapter.getEnvironmentCredentials?.();
|
|
4
|
+
if (environmentCredentials)
|
|
5
|
+
return environmentCredentials;
|
|
6
|
+
const stored = adapter.findStoredCredentials(options);
|
|
7
|
+
return stored?.credentials;
|
|
8
|
+
}
|
|
9
|
+
function shouldRefreshToken(adapter, creds, options) {
|
|
10
|
+
if (creds.type === "api-key")
|
|
11
|
+
return false;
|
|
12
|
+
if (options?.skipRefresh)
|
|
13
|
+
return false;
|
|
14
|
+
if (!adapter.isRefreshable(creds))
|
|
15
|
+
return false;
|
|
16
|
+
return adapter.isExpired(creds) === true;
|
|
17
|
+
}
|
|
18
|
+
async function getAgentAccessTokenFromStoredCredentials(agentId, adapter, options, getAccessToken) {
|
|
19
|
+
const stored = adapter.findStoredCredentials({
|
|
20
|
+
provider: options?.provider,
|
|
21
|
+
});
|
|
22
|
+
if (!stored)
|
|
23
|
+
return undefined;
|
|
24
|
+
return getAccessToken(agentId, stored.credentials, {
|
|
25
|
+
...options,
|
|
26
|
+
storage: options?.storage ?? stored.source,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
async function getAccessTokenWithRefresh(agentId, creds, options, dependencies) {
|
|
30
|
+
const token = dependencies.adapter.getAccessToken(creds, options);
|
|
31
|
+
if (!token)
|
|
32
|
+
return undefined;
|
|
33
|
+
if (!shouldRefreshToken(dependencies.adapter, creds, options)) {
|
|
34
|
+
return token;
|
|
35
|
+
}
|
|
36
|
+
const result = await refreshAndPersist(agentId, creds, (resolvedAgentId, refreshedCreds, installOptions) => dependencies.installCredentials(resolvedAgentId, refreshedCreds, installOptions), {
|
|
37
|
+
timeout: options?.refreshTimeout,
|
|
38
|
+
provider: options?.provider,
|
|
39
|
+
storage: options?.storage,
|
|
40
|
+
});
|
|
41
|
+
const finalCreds = result.ok ? result.credentials : result.staleCredentials;
|
|
42
|
+
return dependencies.adapter.getAccessToken(finalCreds, options);
|
|
43
|
+
}
|
|
44
|
+
export { findCredentialsWithEnvironmentPriority, getAccessTokenWithRefresh, getAgentAccessTokenFromStoredCredentials, };
|
|
@@ -18,18 +18,15 @@
|
|
|
18
18
|
*/
|
|
19
19
|
const SECONDS_THRESHOLD = 10_000_000_000;
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* Decodes the JWT payload and checks the `exp` claim against current time.
|
|
21
|
+
* Extract the `exp` claim from a JWT as milliseconds.
|
|
24
22
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* @returns true if expired, false if valid, undefined if not a valid JWT
|
|
23
|
+
* Decodes the base64url payload and reads the `exp` field (seconds -> ms).
|
|
24
|
+
* Returns undefined if the string is not a valid JWT or has no `exp` claim.
|
|
28
25
|
*/
|
|
29
|
-
function
|
|
26
|
+
function jwtExpiryMs(token) {
|
|
30
27
|
const parts = token.split(".");
|
|
31
28
|
if (parts.length !== 3)
|
|
32
|
-
return undefined;
|
|
29
|
+
return undefined;
|
|
33
30
|
const payloadPart = parts[1];
|
|
34
31
|
if (!payloadPart)
|
|
35
32
|
return undefined;
|
|
@@ -40,14 +37,30 @@ function isTokenExpired(token, bufferSeconds = 60) {
|
|
|
40
37
|
const payload = JSON.parse(atob(padded));
|
|
41
38
|
const exp = payload.exp;
|
|
42
39
|
if (typeof exp !== "number")
|
|
43
|
-
return undefined;
|
|
44
|
-
|
|
45
|
-
return nowSeconds > exp - bufferSeconds;
|
|
40
|
+
return undefined;
|
|
41
|
+
return exp * 1000;
|
|
46
42
|
}
|
|
47
43
|
catch {
|
|
48
|
-
return undefined;
|
|
44
|
+
return undefined;
|
|
49
45
|
}
|
|
50
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if a JWT token is expired.
|
|
49
|
+
*
|
|
50
|
+
* Decodes the JWT payload and checks the `exp` claim against current time.
|
|
51
|
+
*
|
|
52
|
+
* @param token - The JWT token string
|
|
53
|
+
* @param bufferSeconds - Buffer before actual expiry (default: 60s)
|
|
54
|
+
* @returns true if expired, false if valid, undefined if not a valid JWT
|
|
55
|
+
*/
|
|
56
|
+
function isTokenExpired(token, bufferSeconds = 60) {
|
|
57
|
+
const expiryMs = jwtExpiryMs(token);
|
|
58
|
+
if (expiryMs === undefined)
|
|
59
|
+
return undefined;
|
|
60
|
+
const nowMs = Date.now();
|
|
61
|
+
const bufferMs = bufferSeconds * 1000;
|
|
62
|
+
return nowMs > expiryMs - bufferMs;
|
|
63
|
+
}
|
|
51
64
|
/**
|
|
52
65
|
* Check if credentials have expired using explicit timestamp fields.
|
|
53
66
|
*
|
|
@@ -103,6 +116,13 @@ function extractExpiryTimestamp(data) {
|
|
|
103
116
|
? data.expires * 1000
|
|
104
117
|
: data.expires;
|
|
105
118
|
}
|
|
119
|
+
// JWT fallback: decode flat access_token for exp claim.
|
|
120
|
+
const accessToken = typeof data.access_token === "string" ? data.access_token : undefined;
|
|
121
|
+
if (accessToken !== undefined) {
|
|
122
|
+
const expMs = jwtExpiryMs(accessToken);
|
|
123
|
+
if (expMs !== undefined)
|
|
124
|
+
return expMs;
|
|
125
|
+
}
|
|
106
126
|
return undefined;
|
|
107
127
|
}
|
|
108
128
|
/**
|
package/dist/auth/registry.js
CHANGED
|
@@ -9,9 +9,8 @@ import { codexAdapter } from "./agents/codex.js";
|
|
|
9
9
|
import { copilotAdapter } from "./agents/copilot.js";
|
|
10
10
|
import { geminiAdapter } from "./agents/gemini.js";
|
|
11
11
|
import { opencodeAdapter } from "./agents/opencode.js";
|
|
12
|
+
import { findCredentialsWithEnvironmentPriority, getAccessTokenWithRefresh, getAgentAccessTokenFromStoredCredentials, } from "./get-access-token-with-refresh.js";
|
|
12
13
|
import { installCredentialsFromEnvironmentCore } from "./install-from-environment.js";
|
|
13
|
-
import { isCredentialExpired, isTokenExpired } from "./is-token-expired.js";
|
|
14
|
-
import { refreshAndPersist } from "./refresh-credentials.js";
|
|
15
14
|
/** Registry of all adapters by agent ID */
|
|
16
15
|
const ADAPTERS = {
|
|
17
16
|
claude: claudeCodeAdapter,
|
|
@@ -92,14 +91,7 @@ function checkAllAuth() {
|
|
|
92
91
|
* const creds = findCredentials("opencode", { provider: "anthropic" });
|
|
93
92
|
*/
|
|
94
93
|
function findCredentials(agentId, options) {
|
|
95
|
-
|
|
96
|
-
// Check environment credentials first (allows easy overrides in CI/CD)
|
|
97
|
-
const environmentCredentials = adapter.getEnvironmentCredentials?.();
|
|
98
|
-
if (environmentCredentials)
|
|
99
|
-
return environmentCredentials;
|
|
100
|
-
// Fall back to stored credentials (keychain/file)
|
|
101
|
-
const stored = adapter.findStoredCredentials(options);
|
|
102
|
-
return stored?.credentials;
|
|
94
|
+
return findCredentialsWithEnvironmentPriority(ADAPTERS[agentId], options);
|
|
103
95
|
}
|
|
104
96
|
/**
|
|
105
97
|
* Extract raw credentials from custom directories.
|
|
@@ -158,32 +150,10 @@ function removeCredentials(agentId, options) {
|
|
|
158
150
|
* const token = await getAccessToken(creds, { skipRefresh: true });
|
|
159
151
|
*/
|
|
160
152
|
async function getAccessToken(agentId, creds, options) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return undefined;
|
|
165
|
-
// API keys don't expire
|
|
166
|
-
if (creds.type === "api-key")
|
|
167
|
-
return token;
|
|
168
|
-
// Skip refresh if requested
|
|
169
|
-
if (options?.skipRefresh)
|
|
170
|
-
return token;
|
|
171
|
-
// Check if token is expired (try JWT first, then credential fields)
|
|
172
|
-
let expired = isTokenExpired(token);
|
|
173
|
-
if (expired === undefined) {
|
|
174
|
-
// Token isn't a JWT - check credential's explicit expiry fields
|
|
175
|
-
expired = isCredentialExpired(creds.data);
|
|
176
|
-
}
|
|
177
|
-
if (expired !== true)
|
|
178
|
-
return token; // Valid or no expiry info
|
|
179
|
-
// Refresh and persist
|
|
180
|
-
const result = await refreshAndPersist(agentId, creds, (aid, c, installOptions) => installCredentials(aid, c, installOptions), {
|
|
181
|
-
timeout: options?.refreshTimeout,
|
|
182
|
-
provider: options?.provider,
|
|
183
|
-
storage: options?.storage,
|
|
153
|
+
return getAccessTokenWithRefresh(agentId, creds, options, {
|
|
154
|
+
adapter: ADAPTERS[agentId],
|
|
155
|
+
installCredentials,
|
|
184
156
|
});
|
|
185
|
-
const finalCreds = result.ok ? result.credentials : result.staleCredentials;
|
|
186
|
-
return ADAPTERS[agentId].getAccessToken(finalCreds, options);
|
|
187
157
|
}
|
|
188
158
|
/**
|
|
189
159
|
* Get access token for an agent by ID.
|
|
@@ -196,16 +166,7 @@ async function getAccessToken(agentId, creds, options) {
|
|
|
196
166
|
* const token = await getAgentAccessToken("claude");
|
|
197
167
|
*/
|
|
198
168
|
async function getAgentAccessToken(agentId, options) {
|
|
199
|
-
|
|
200
|
-
provider: options?.provider,
|
|
201
|
-
});
|
|
202
|
-
if (!stored)
|
|
203
|
-
return undefined;
|
|
204
|
-
// Pass the source to getAccessToken so refresh preserves storage location
|
|
205
|
-
return getAccessToken(agentId, stored.credentials, {
|
|
206
|
-
...options,
|
|
207
|
-
storage: options?.storage ?? stored.source,
|
|
208
|
-
});
|
|
169
|
+
return getAgentAccessTokenFromStoredCredentials(agentId, ADAPTERS[agentId], options, getAccessToken);
|
|
209
170
|
}
|
|
210
171
|
/**
|
|
211
172
|
* Convert credentials to environment variables.
|
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,8 @@ import packageJson from "../package.json" with { type: "json" };
|
|
|
9
9
|
import { handleAuthExport, handleAuthInstall, handleAuthList, handleAuthRemove, handleAuthToken, } from "./commands/auth.js";
|
|
10
10
|
import { handleDecrypt } from "./commands/decrypt.js";
|
|
11
11
|
import { handleEncrypt } from "./commands/encrypt.js";
|
|
12
|
-
import { handleVaultFetch
|
|
12
|
+
import { handleVaultFetch } from "./commands/vault-fetch.js";
|
|
13
|
+
import { handleVaultPush } from "./commands/vault-push.js";
|
|
13
14
|
import { AGENT_CLIS } from "axshared";
|
|
14
15
|
// Handle SIGINT gracefully
|
|
15
16
|
process.on("SIGINT", () => {
|
|
@@ -198,6 +199,8 @@ vault
|
|
|
198
199
|
.requiredOption("-a, --agent <agent>", `Agent to push credentials from (${AGENT_CLIS.join(", ")})`)
|
|
199
200
|
.requiredOption("-n, --name <name>", "Credential name in vault (e.g., ci, prod)")
|
|
200
201
|
.option("-p, --provider <provider>", "Provider to push (opencode only; pushes single provider instead of all)")
|
|
202
|
+
.option("--display-name <name>", "Human-readable display name for this credential (e.g., 'Claude (Work)')")
|
|
203
|
+
.option("--notes <text>", "Additional notes about this credential")
|
|
201
204
|
.addHelpText("after", String.raw `
|
|
202
205
|
Environment variables (option 1 - single JSON):
|
|
203
206
|
AXVAULT JSON config: {"url":"...","apiKey":"..."}
|
|
@@ -210,6 +213,9 @@ Examples:
|
|
|
210
213
|
# Push local Claude credentials to vault as "ci"
|
|
211
214
|
axauth vault push --agent claude --name ci
|
|
212
215
|
|
|
216
|
+
# Push with a display name for multi-instance identification
|
|
217
|
+
axauth vault push --agent claude --name work-claude --display-name "Claude (Work)"
|
|
218
|
+
|
|
213
219
|
# Push a single OpenCode provider to vault
|
|
214
220
|
axauth vault push --agent opencode --provider anthropic --name ci-anthropic
|
|
215
221
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Human-readable error messages for vault failures */
|
|
2
|
+
const FAILURE_MESSAGES = {
|
|
3
|
+
"not-configured": "Vault not configured. Set AXVAULT (JSON) or AXVAULT_URL + AXVAULT_API_KEY.",
|
|
4
|
+
unreachable: "Vault server is unreachable. Check vault URL and network.",
|
|
5
|
+
unauthorized: "Invalid API key. Check apiKey in vault config.",
|
|
6
|
+
forbidden: "Access denied. API key doesn't have required access to this credential.",
|
|
7
|
+
"not-found": "Credential not found in vault.",
|
|
8
|
+
"legacy-credential": "Credential uses legacy encryption. Re-upload it to vault.",
|
|
9
|
+
"client-error": "Invalid request. Check credential name and try again.",
|
|
10
|
+
"server-error": "Vault server error. Check server logs.",
|
|
11
|
+
};
|
|
12
|
+
export { FAILURE_MESSAGES };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Vault
|
|
2
|
+
* Vault fetch command - download credentials from axvault server.
|
|
3
3
|
*/
|
|
4
4
|
interface VaultFetchOptions {
|
|
5
5
|
agent: string;
|
|
@@ -19,17 +19,4 @@ interface VaultFetchOptions {
|
|
|
19
19
|
* - Outputs shell export commands with --env flag (for eval/source)
|
|
20
20
|
*/
|
|
21
21
|
declare function handleVaultFetch(options: VaultFetchOptions): Promise<void>;
|
|
22
|
-
|
|
23
|
-
agent: string;
|
|
24
|
-
name: string;
|
|
25
|
-
provider?: string;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Handle vault push command.
|
|
29
|
-
*
|
|
30
|
-
* Reads local credentials for an agent and pushes them to the vault server
|
|
31
|
-
* in raw (unencrypted) format. This allows the vault to handle encryption
|
|
32
|
-
* and automatic token refresh.
|
|
33
|
-
*/
|
|
34
|
-
declare function handleVaultPush(options: VaultPushOptions): Promise<void>;
|
|
35
|
-
export { handleVaultFetch, handleVaultPush };
|
|
22
|
+
export { handleVaultFetch };
|
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Vault
|
|
2
|
+
* Vault fetch command - download credentials from axvault server.
|
|
3
3
|
*/
|
|
4
|
-
import { credentialsToEnvironment,
|
|
5
|
-
import {
|
|
6
|
-
import { fetchVaultCredentials, pushVaultCredentials, } from "../vault/vault-client.js";
|
|
4
|
+
import { credentialsToEnvironment, installCredentials, } from "../auth/registry.js";
|
|
5
|
+
import { fetchVaultCredentials } from "../vault/vault-client.js";
|
|
7
6
|
import { getVaultConfig } from "../vault/vault-config.js";
|
|
7
|
+
import { FAILURE_MESSAGES } from "./vault-failure-messages.js";
|
|
8
8
|
import { validateAgent } from "./validate-agent.js";
|
|
9
|
-
/** Human-readable error messages for vault failures */
|
|
10
|
-
const FAILURE_MESSAGES = {
|
|
11
|
-
"not-configured": "Vault not configured. Set AXVAULT (JSON) or AXVAULT_URL + AXVAULT_API_KEY.",
|
|
12
|
-
unreachable: "Vault server is unreachable. Check vault URL and network.",
|
|
13
|
-
unauthorized: "Invalid API key. Check apiKey in vault config.",
|
|
14
|
-
forbidden: "Access denied. API key doesn't have read access to this credential.",
|
|
15
|
-
"not-found": "Credential not found in vault.",
|
|
16
|
-
"legacy-credential": "Credential uses legacy encryption. Re-upload it to vault.",
|
|
17
|
-
"client-error": "Invalid request. Check credential name and try again.",
|
|
18
|
-
"server-error": "Vault server error. Check server logs.",
|
|
19
|
-
};
|
|
20
9
|
/**
|
|
21
10
|
* Escape a value for safe use in shell export statement.
|
|
22
11
|
* Uses single quotes and escapes any single quotes in the value.
|
|
@@ -66,7 +55,7 @@ async function handleVaultFetch(options) {
|
|
|
66
55
|
process.exitCode = 1;
|
|
67
56
|
return;
|
|
68
57
|
}
|
|
69
|
-
const { credentials, refreshed } = result;
|
|
58
|
+
const { credentials, refreshed, displayName, notes } = result;
|
|
70
59
|
// Log refresh status to stderr (not stdout, to allow piping)
|
|
71
60
|
if (refreshed) {
|
|
72
61
|
console.error("Note: Credentials were auto-refreshed by vault");
|
|
@@ -112,71 +101,18 @@ async function handleVaultFetch(options) {
|
|
|
112
101
|
}
|
|
113
102
|
else {
|
|
114
103
|
// Output credentials as JSON (default)
|
|
104
|
+
const output = {
|
|
105
|
+
...credentials,
|
|
106
|
+
...(displayName !== undefined && { displayName }),
|
|
107
|
+
...(notes !== undefined && { notes }),
|
|
108
|
+
};
|
|
115
109
|
if (options.json) {
|
|
116
|
-
console.log(JSON.stringify(
|
|
110
|
+
console.log(JSON.stringify(output, undefined, 2));
|
|
117
111
|
}
|
|
118
112
|
else {
|
|
119
113
|
// Compact JSON for piping
|
|
120
|
-
console.log(JSON.stringify(
|
|
114
|
+
console.log(JSON.stringify(output));
|
|
121
115
|
}
|
|
122
116
|
}
|
|
123
117
|
}
|
|
124
|
-
|
|
125
|
-
* Handle vault push command.
|
|
126
|
-
*
|
|
127
|
-
* Reads local credentials for an agent and pushes them to the vault server
|
|
128
|
-
* in raw (unencrypted) format. This allows the vault to handle encryption
|
|
129
|
-
* and automatic token refresh.
|
|
130
|
-
*/
|
|
131
|
-
async function handleVaultPush(options) {
|
|
132
|
-
// Validate agent
|
|
133
|
-
const agentId = validateAgent(options.agent);
|
|
134
|
-
if (!agentId)
|
|
135
|
-
return;
|
|
136
|
-
// Validate provider flag usage
|
|
137
|
-
if (options.provider && agentId !== "opencode") {
|
|
138
|
-
console.error("Error: --provider flag is only supported for opencode agent");
|
|
139
|
-
process.exitCode = 2;
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
if (agentId === "opencode" && !options.provider) {
|
|
143
|
-
console.error("Error: --provider is required for opencode");
|
|
144
|
-
console.error("Hint: Use 'axauth list --json' to see available providers");
|
|
145
|
-
process.exitCode = 1;
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
// Check vault is configured
|
|
149
|
-
const vaultConfig = getVaultConfig();
|
|
150
|
-
if (!vaultConfig) {
|
|
151
|
-
console.error(`Error: ${FAILURE_MESSAGES["not-configured"]}`);
|
|
152
|
-
process.exitCode = 1;
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
// Extract local credentials
|
|
156
|
-
const credentials = agentId === "opencode" && options.provider
|
|
157
|
-
? getStoredCredentialsForProvider(options.provider)
|
|
158
|
-
: findCredentials(agentId);
|
|
159
|
-
if (!credentials) {
|
|
160
|
-
const hint = agentId === "opencode"
|
|
161
|
-
? `No credentials found for provider '${options.provider}'`
|
|
162
|
-
: `No credentials found for ${agentId}\nHint: Authenticate with the agent first, or use 'axauth list' to check status`;
|
|
163
|
-
console.error(`Error: ${hint}`);
|
|
164
|
-
process.exitCode = 1;
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
// Push to vault
|
|
168
|
-
const result = await pushVaultCredentials({
|
|
169
|
-
agentId,
|
|
170
|
-
name: options.name,
|
|
171
|
-
credentials,
|
|
172
|
-
provider: options.provider,
|
|
173
|
-
});
|
|
174
|
-
if (!result.ok) {
|
|
175
|
-
console.error(`Error: ${FAILURE_MESSAGES[result.reason]}`);
|
|
176
|
-
process.exitCode = 1;
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
const label = options.provider ? `${agentId}/${options.provider}` : agentId;
|
|
180
|
-
console.error(`Credentials pushed to vault: ${label} → ${options.name}`);
|
|
181
|
-
}
|
|
182
|
-
export { handleVaultFetch, handleVaultPush };
|
|
118
|
+
export { handleVaultFetch };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault push command - upload local credentials to axvault server.
|
|
3
|
+
*/
|
|
4
|
+
interface VaultPushOptions {
|
|
5
|
+
agent: string;
|
|
6
|
+
name: string;
|
|
7
|
+
provider?: string;
|
|
8
|
+
displayName?: string;
|
|
9
|
+
notes?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Handle vault push command.
|
|
13
|
+
*
|
|
14
|
+
* Reads local credentials for an agent and pushes them to the vault server
|
|
15
|
+
* in raw (unencrypted) format. This allows the vault to handle encryption
|
|
16
|
+
* and automatic token refresh.
|
|
17
|
+
*/
|
|
18
|
+
declare function handleVaultPush(options: VaultPushOptions): Promise<void>;
|
|
19
|
+
export { handleVaultPush };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault push command - upload local credentials to axvault server.
|
|
3
|
+
*/
|
|
4
|
+
import { findCredentials } from "../auth/registry.js";
|
|
5
|
+
import { getStoredCredentialsForProvider } from "../auth/agents/opencode.js";
|
|
6
|
+
import { pushVaultCredentials } from "../vault/vault-client.js";
|
|
7
|
+
import { getVaultConfig } from "../vault/vault-config.js";
|
|
8
|
+
import { FAILURE_MESSAGES } from "./vault-failure-messages.js";
|
|
9
|
+
import { validateAgent } from "./validate-agent.js";
|
|
10
|
+
/**
|
|
11
|
+
* Handle vault push command.
|
|
12
|
+
*
|
|
13
|
+
* Reads local credentials for an agent and pushes them to the vault server
|
|
14
|
+
* in raw (unencrypted) format. This allows the vault to handle encryption
|
|
15
|
+
* and automatic token refresh.
|
|
16
|
+
*/
|
|
17
|
+
async function handleVaultPush(options) {
|
|
18
|
+
// Validate agent
|
|
19
|
+
const agentId = validateAgent(options.agent);
|
|
20
|
+
if (!agentId)
|
|
21
|
+
return;
|
|
22
|
+
// Validate provider flag usage
|
|
23
|
+
if (options.provider && agentId !== "opencode") {
|
|
24
|
+
console.error("Error: --provider flag is only supported for opencode agent");
|
|
25
|
+
process.exitCode = 2;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (agentId === "opencode" && !options.provider) {
|
|
29
|
+
console.error("Error: --provider is required for opencode");
|
|
30
|
+
console.error("Hint: Use 'axauth list --json' to see available providers");
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// Check vault is configured
|
|
35
|
+
const vaultConfig = getVaultConfig();
|
|
36
|
+
if (!vaultConfig) {
|
|
37
|
+
console.error(`Error: ${FAILURE_MESSAGES["not-configured"]}`);
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// Extract local credentials
|
|
42
|
+
const credentials = agentId === "opencode" && options.provider
|
|
43
|
+
? getStoredCredentialsForProvider(options.provider)
|
|
44
|
+
: findCredentials(agentId);
|
|
45
|
+
if (!credentials) {
|
|
46
|
+
const hint = agentId === "opencode"
|
|
47
|
+
? `No credentials found for provider '${options.provider}'`
|
|
48
|
+
: `No credentials found for ${agentId}\nHint: Authenticate with the agent first, or use 'axauth list' to check status`;
|
|
49
|
+
console.error(`Error: ${hint}`);
|
|
50
|
+
process.exitCode = 1;
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Push to vault
|
|
54
|
+
const result = await pushVaultCredentials({
|
|
55
|
+
agentId,
|
|
56
|
+
name: options.name,
|
|
57
|
+
credentials,
|
|
58
|
+
provider: options.provider,
|
|
59
|
+
displayName: options.displayName,
|
|
60
|
+
notes: options.notes,
|
|
61
|
+
});
|
|
62
|
+
if (!result.ok) {
|
|
63
|
+
console.error(`Error: ${FAILURE_MESSAGES[result.reason]}`);
|
|
64
|
+
process.exitCode = 1;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const label = options.provider ? `${agentId}/${options.provider}` : agentId;
|
|
68
|
+
console.error(`Credentials pushed to vault: ${label} → ${options.name}`);
|
|
69
|
+
}
|
|
70
|
+
export { handleVaultPush };
|
|
@@ -14,6 +14,8 @@ type VaultResult = {
|
|
|
14
14
|
ok: true;
|
|
15
15
|
credentials: Credentials;
|
|
16
16
|
refreshed: boolean;
|
|
17
|
+
displayName: string | undefined;
|
|
18
|
+
notes: string | undefined;
|
|
17
19
|
} | {
|
|
18
20
|
ok: false;
|
|
19
21
|
reason: VaultFailureReason;
|
|
@@ -61,6 +63,10 @@ interface VaultPushOptions {
|
|
|
61
63
|
credentials: Credentials;
|
|
62
64
|
/** Provider for multi-provider agents (e.g., "anthropic" for OpenCode) */
|
|
63
65
|
provider?: string;
|
|
66
|
+
/** Human-readable display name for this credential */
|
|
67
|
+
displayName?: string;
|
|
68
|
+
/** Additional notes about this credential */
|
|
69
|
+
notes?: string;
|
|
64
70
|
}
|
|
65
71
|
/**
|
|
66
72
|
* Push credentials to the axvault server.
|
|
@@ -11,6 +11,14 @@ import { getVaultConfig } from "./vault-config.js";
|
|
|
11
11
|
const VaultCredentialResponse = z.object({
|
|
12
12
|
name: z.string(),
|
|
13
13
|
credential: z.record(z.string(), z.unknown()),
|
|
14
|
+
displayName: z
|
|
15
|
+
.string()
|
|
16
|
+
.nullish()
|
|
17
|
+
.transform((value) => value ?? undefined),
|
|
18
|
+
notes: z
|
|
19
|
+
.string()
|
|
20
|
+
.nullish()
|
|
21
|
+
.transform((value) => value ?? undefined),
|
|
14
22
|
updatedAt: z.string(),
|
|
15
23
|
});
|
|
16
24
|
/**
|
|
@@ -84,6 +92,8 @@ async function fetchVaultCredentials(options) {
|
|
|
84
92
|
ok: true,
|
|
85
93
|
credentials: body.credential,
|
|
86
94
|
refreshed: wasRefreshed,
|
|
95
|
+
displayName: body.displayName,
|
|
96
|
+
notes: body.notes,
|
|
87
97
|
};
|
|
88
98
|
}
|
|
89
99
|
catch {
|
|
@@ -133,6 +143,10 @@ async function pushVaultCredentials(options) {
|
|
|
133
143
|
type: options.credentials.type,
|
|
134
144
|
data: options.credentials.data,
|
|
135
145
|
...(options.provider !== undefined && { provider: options.provider }),
|
|
146
|
+
...(options.displayName !== undefined && {
|
|
147
|
+
displayName: options.displayName,
|
|
148
|
+
}),
|
|
149
|
+
...(options.notes !== undefined && { notes: options.notes }),
|
|
136
150
|
}),
|
|
137
151
|
});
|
|
138
152
|
// Handle error responses
|
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.
|
|
5
|
+
"version": "3.3.0",
|
|
6
6
|
"description": "Authentication management library and CLI for AI coding agents",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -62,31 +62,31 @@
|
|
|
62
62
|
"automation",
|
|
63
63
|
"coding-assistant"
|
|
64
64
|
],
|
|
65
|
-
"packageManager": "pnpm@10.
|
|
65
|
+
"packageManager": "pnpm@10.30.1",
|
|
66
66
|
"engines": {
|
|
67
67
|
"node": ">=22.14.0"
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"@commander-js/extra-typings": "^14.0.0",
|
|
71
|
-
"@inquirer/password": "^5.0.
|
|
71
|
+
"@inquirer/password": "^5.0.7",
|
|
72
72
|
"axconfig": "^3.6.3",
|
|
73
|
-
"axexec": "^2.0.
|
|
73
|
+
"axexec": "^2.0.1",
|
|
74
74
|
"axshared": "^5.0.0",
|
|
75
|
-
"commander": "^14.0.
|
|
76
|
-
"zod": "^4.3.
|
|
75
|
+
"commander": "^14.0.3",
|
|
76
|
+
"zod": "^4.3.6"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@total-typescript/ts-reset": "^0.6.1",
|
|
80
|
-
"@types/node": "^25.0
|
|
81
|
-
"@vitest/coverage-v8": "^4.0.
|
|
82
|
-
"eslint": "^
|
|
83
|
-
"eslint-config-axkit": "^1.1
|
|
80
|
+
"@types/node": "^25.3.0",
|
|
81
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
82
|
+
"eslint": "^10.0.1",
|
|
83
|
+
"eslint-config-axkit": "^1.2.1",
|
|
84
84
|
"fta-check": "^1.5.1",
|
|
85
85
|
"fta-cli": "^3.0.0",
|
|
86
|
-
"knip": "^5.
|
|
86
|
+
"knip": "^5.85.0",
|
|
87
87
|
"prettier": "3.8.1",
|
|
88
|
-
"semantic-release": "^25.0.
|
|
88
|
+
"semantic-release": "^25.0.3",
|
|
89
89
|
"typescript": "^5.9.3",
|
|
90
|
-
"vitest": "^4.0.
|
|
90
|
+
"vitest": "^4.0.18"
|
|
91
91
|
}
|
|
92
92
|
}
|