axauth 2.0.0 → 3.0.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 +10 -4
- package/dist/auth/adapter.d.ts +60 -20
- package/dist/auth/agents/claude-install.d.ts +11 -0
- package/dist/auth/agents/claude-install.js +80 -0
- package/dist/auth/agents/claude-storage.d.ts +13 -5
- package/dist/auth/agents/claude-storage.js +11 -16
- package/dist/auth/agents/claude.js +14 -89
- package/dist/auth/agents/codex-auth-check.d.ts +6 -5
- package/dist/auth/agents/codex-auth-check.js +34 -20
- package/dist/auth/agents/codex-install.js +1 -2
- package/dist/auth/agents/codex-storage.d.ts +0 -1
- package/dist/auth/agents/codex-storage.js +4 -5
- package/dist/auth/agents/codex.js +4 -4
- package/dist/auth/agents/copilot-install.js +2 -3
- package/dist/auth/agents/copilot.js +11 -6
- package/dist/auth/agents/gemini-install.d.ts +1 -1
- package/dist/auth/agents/gemini-install.js +3 -3
- package/dist/auth/agents/gemini.js +19 -17
- package/dist/auth/agents/opencode-credentials.d.ts +25 -37
- package/dist/auth/agents/opencode-credentials.js +48 -122
- package/dist/auth/agents/opencode-remove-provider.d.ts +25 -0
- package/dist/auth/agents/opencode-remove-provider.js +86 -0
- package/dist/auth/agents/opencode-storage.js +18 -22
- package/dist/auth/agents/opencode.d.ts +1 -1
- package/dist/auth/agents/opencode.js +59 -41
- package/dist/auth/build-refreshed-credentials.d.ts +6 -4
- package/dist/auth/build-refreshed-credentials.js +34 -19
- package/dist/auth/extract-creds-from-directory.d.ts +7 -3
- package/dist/auth/extract-creds-from-directory.js +4 -1
- package/dist/auth/is-token-expired.d.ts +52 -2
- package/dist/auth/is-token-expired.js +71 -5
- package/dist/auth/refresh-credentials.d.ts +38 -12
- package/dist/auth/refresh-credentials.js +49 -18
- package/dist/auth/registry.d.ts +18 -15
- package/dist/auth/registry.js +35 -18
- package/dist/auth/resolve-refresh-credentials.d.ts +11 -8
- package/dist/auth/resolve-refresh-credentials.js +12 -48
- package/dist/auth/types.d.ts +3 -35
- package/dist/auth/types.js +2 -47
- package/dist/auth/wait-for-refreshed-credentials.d.ts +5 -1
- package/dist/auth/wait-for-refreshed-credentials.js +7 -4
- package/dist/commands/auth-export.js +24 -46
- package/dist/commands/auth.js +13 -3
- package/dist/commands/copy-to-clipboard.d.ts +6 -0
- package/dist/commands/copy-to-clipboard.js +48 -0
- package/dist/commands/vault.js +16 -15
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -4
- package/dist/vault/vault-client.js +21 -2
- package/package.json +11 -11
|
@@ -11,4 +11,4 @@ import type { AuthAdapter } from "../adapter.js";
|
|
|
11
11
|
/** OpenCode authentication adapter */
|
|
12
12
|
declare const opencodeAdapter: AuthAdapter;
|
|
13
13
|
export { opencodeAdapter };
|
|
14
|
-
export {
|
|
14
|
+
export { getAllStoredCredentials, getStoredCredentialsForProvider, removeProviderCredentials, } from "./opencode-credentials.js";
|
|
@@ -8,9 +8,8 @@
|
|
|
8
8
|
* - Data (auth): ~/.local/share/opencode (XDG_DATA_HOME/opencode)
|
|
9
9
|
*/
|
|
10
10
|
import path from "node:path";
|
|
11
|
-
import { extractCredsFromDirectory } from "../extract-creds-from-directory.js";
|
|
12
11
|
import { extractTokenFromEntry, OpenCodeAuth } from "./opencode-schema.js";
|
|
13
|
-
import { AGENT_ID, CREDS_FILE_NAME, getDefaultAuthFilePath, readAuthFile, } from "./opencode-credentials.js";
|
|
12
|
+
import { AGENT_ID, CREDS_FILE_NAME, getDefaultAuthFilePath, mapToCredentialType, readAuthFile, } from "./opencode-credentials.js";
|
|
14
13
|
import { installCredentials, removeCredentials } from "./opencode-storage.js";
|
|
15
14
|
/** OpenCode authentication adapter */
|
|
16
15
|
const opencodeAdapter = {
|
|
@@ -54,59 +53,78 @@ const opencodeAdapter = {
|
|
|
54
53
|
details: { providers: validProviders.map((p) => p.name) },
|
|
55
54
|
};
|
|
56
55
|
},
|
|
57
|
-
|
|
56
|
+
findStoredCredentials(options) {
|
|
57
|
+
if (!options?.provider) {
|
|
58
|
+
throw new Error("OpenCode requires provider parameter. Use checkAuth() to list available providers.");
|
|
59
|
+
}
|
|
60
|
+
const normalizedProvider = options.provider.trim();
|
|
61
|
+
if (normalizedProvider.length === 0) {
|
|
62
|
+
throw new Error("Provider cannot be empty");
|
|
63
|
+
}
|
|
58
64
|
const authResult = readAuthFile(getDefaultAuthFilePath());
|
|
59
65
|
// Treat errors same as missing file for extraction (return undefined)
|
|
60
|
-
if (!authResult.ok ||
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
if (!authResult.ok || !authResult.data) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
const entry = authResult.data[normalizedProvider];
|
|
70
|
+
if (!entry) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const parsed = OpenCodeAuth.safeParse(entry);
|
|
74
|
+
if (!parsed.success) {
|
|
63
75
|
return undefined;
|
|
64
76
|
}
|
|
65
77
|
return {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
78
|
+
credentials: {
|
|
79
|
+
agent: AGENT_ID,
|
|
80
|
+
type: mapToCredentialType(parsed.data),
|
|
81
|
+
provider: normalizedProvider,
|
|
82
|
+
data: entry,
|
|
83
|
+
},
|
|
84
|
+
source: "file",
|
|
69
85
|
};
|
|
70
86
|
},
|
|
71
|
-
|
|
87
|
+
loadCredentialsFromDirectory(options) {
|
|
72
88
|
// OpenCode stores credentials in dataDir only - configDir is for settings
|
|
73
|
-
// If dataDir not provided, return undefined
|
|
89
|
+
// If dataDir not provided, return undefined; if provider not provided, throw
|
|
74
90
|
if (!options.dataDir)
|
|
75
91
|
return undefined;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const data = creds.data;
|
|
83
|
-
// Per-provider credential: data is the provider's entry directly
|
|
84
|
-
if (creds.provider) {
|
|
85
|
-
const parsed = OpenCodeAuth.safeParse(data);
|
|
86
|
-
if (!parsed.success)
|
|
87
|
-
return undefined;
|
|
88
|
-
return extractTokenFromEntry(parsed.data);
|
|
92
|
+
const authResult = readAuthFile(path.join(options.dataDir, CREDS_FILE_NAME));
|
|
93
|
+
// Treat errors same as missing file for extraction (return undefined)
|
|
94
|
+
if (!authResult.ok ||
|
|
95
|
+
!authResult.data ||
|
|
96
|
+
Object.keys(authResult.data).length === 0) {
|
|
97
|
+
return undefined;
|
|
89
98
|
}
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
if (!
|
|
99
|
+
// If provider filter is specified, return only that provider's credentials
|
|
100
|
+
if (options.provider) {
|
|
101
|
+
const targetProvider = options.provider.trim();
|
|
102
|
+
if (targetProvider.length === 0)
|
|
103
|
+
return undefined;
|
|
104
|
+
const entry = authResult.data[targetProvider];
|
|
105
|
+
if (!entry)
|
|
97
106
|
return undefined;
|
|
98
|
-
return extractTokenFromEntry(parsed.data);
|
|
99
|
-
}
|
|
100
|
-
// Otherwise return first available token
|
|
101
|
-
for (const entry of Object.values(data)) {
|
|
102
107
|
const parsed = OpenCodeAuth.safeParse(entry);
|
|
103
108
|
if (!parsed.success)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
return undefined;
|
|
110
|
+
return {
|
|
111
|
+
agent: AGENT_ID,
|
|
112
|
+
type: mapToCredentialType(parsed.data),
|
|
113
|
+
provider: targetProvider,
|
|
114
|
+
data: entry,
|
|
115
|
+
};
|
|
108
116
|
}
|
|
109
|
-
|
|
117
|
+
// No provider filter: throw to enforce explicit provider requirement
|
|
118
|
+
throw new Error("OpenCode requires provider parameter for loadCredentialsFromDirectory. Use findStoredCredentials() with provider option.");
|
|
119
|
+
},
|
|
120
|
+
installCredentials,
|
|
121
|
+
removeCredentials,
|
|
122
|
+
getAccessToken(creds) {
|
|
123
|
+
// OpenCode credentials are always per-provider: data is the provider's entry
|
|
124
|
+
const parsed = OpenCodeAuth.safeParse(creds.data);
|
|
125
|
+
if (!parsed.success)
|
|
126
|
+
return undefined;
|
|
127
|
+
return extractTokenFromEntry(parsed.data);
|
|
110
128
|
},
|
|
111
129
|
credentialsToEnvironment() {
|
|
112
130
|
// OpenCode requires auth.json file, no env var support
|
|
@@ -117,4 +135,4 @@ const opencodeAdapter = {
|
|
|
117
135
|
},
|
|
118
136
|
};
|
|
119
137
|
export { opencodeAdapter };
|
|
120
|
-
export {
|
|
138
|
+
export { getAllStoredCredentials, getStoredCredentialsForProvider, removeProviderCredentials, } from "./opencode-credentials.js";
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Build refreshed credentials with
|
|
2
|
+
* Build refreshed credentials with provider context preserved.
|
|
3
3
|
*/
|
|
4
4
|
import type { Credentials } from "./types.js";
|
|
5
5
|
/**
|
|
6
|
-
* Build refreshed credentials, preserving
|
|
7
|
-
* per-provider vs bundled credential formats.
|
|
6
|
+
* Build refreshed credentials, preserving provider context.
|
|
8
7
|
*
|
|
9
|
-
*
|
|
8
|
+
* For OpenCode (per-provider), extracts the specific provider's refreshed data.
|
|
9
|
+
* For other agents, uses the refreshed data directly.
|
|
10
|
+
*
|
|
11
|
+
* @param originalCreds - Original credentials
|
|
10
12
|
* @param refreshedCreds - Credentials returned from refresh operation
|
|
11
13
|
* @returns Built credentials or error message
|
|
12
14
|
*/
|
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Build refreshed credentials with
|
|
2
|
+
* Build refreshed credentials with provider context preserved.
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
|
-
* Build refreshed credentials, preserving
|
|
6
|
-
* per-provider vs bundled credential formats.
|
|
5
|
+
* Build refreshed credentials, preserving provider context.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
7
|
+
* For OpenCode (per-provider), extracts the specific provider's refreshed data.
|
|
8
|
+
* For other agents, uses the refreshed data directly.
|
|
9
|
+
*
|
|
10
|
+
* @param originalCreds - Original credentials
|
|
9
11
|
* @param refreshedCreds - Credentials returned from refresh operation
|
|
10
12
|
* @returns Built credentials or error message
|
|
11
13
|
*/
|
|
12
14
|
function buildRefreshedCredentials(originalCreds, refreshedCreds) {
|
|
13
|
-
//
|
|
14
|
-
if (originalCreds.provider) {
|
|
15
|
+
// OpenCode per-provider refresh: verify provider matches and use data directly
|
|
16
|
+
if (originalCreds.agent === "opencode" && originalCreds.provider) {
|
|
15
17
|
const normalizedProvider = originalCreds.provider.trim();
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
// refreshedCreds from loadCredentialsFromDirectory is now per-provider format:
|
|
19
|
+
// - refreshedCreds.provider = the provider name
|
|
20
|
+
// - refreshedCreds.data = the provider's auth entry directly
|
|
21
|
+
if (refreshedCreds.agent !== "opencode") {
|
|
22
|
+
return {
|
|
23
|
+
ok: false,
|
|
24
|
+
error: `Provider '${normalizedProvider}' not found in refreshed credentials`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
// TypeScript narrows to OpenCodeCredentials which has provider as required
|
|
28
|
+
// (z.string().trim().min(1) in axshared) - NOT optional despite Credentials union
|
|
29
|
+
if (refreshedCreds.provider.trim() !== normalizedProvider) {
|
|
18
30
|
return {
|
|
19
31
|
ok: false,
|
|
20
32
|
error: `Provider '${normalizedProvider}' not found in refreshed credentials`,
|
|
@@ -24,24 +36,27 @@ function buildRefreshedCredentials(originalCreds, refreshedCreds) {
|
|
|
24
36
|
ok: true,
|
|
25
37
|
credentials: {
|
|
26
38
|
agent: originalCreds.agent,
|
|
27
|
-
type:
|
|
39
|
+
type: refreshedCreds.type,
|
|
28
40
|
provider: normalizedProvider,
|
|
29
|
-
data:
|
|
30
|
-
...providerData,
|
|
31
|
-
_source: originalCreds.data._source,
|
|
32
|
-
},
|
|
41
|
+
data: refreshedCreds.data,
|
|
33
42
|
},
|
|
34
43
|
};
|
|
35
44
|
}
|
|
36
|
-
//
|
|
45
|
+
// Standard agent refresh: use refreshed data
|
|
46
|
+
// Both originalCreds and refreshedCreds are standard agents here (not OpenCode)
|
|
47
|
+
if (originalCreds.agent === "opencode") {
|
|
48
|
+
// TypeScript narrowing: unreachable, but satisfies type checker
|
|
49
|
+
return {
|
|
50
|
+
ok: false,
|
|
51
|
+
error: "Unexpected OpenCode credential without provider",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
37
54
|
return {
|
|
38
55
|
ok: true,
|
|
39
56
|
credentials: {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
_source: originalCreds.data._source,
|
|
44
|
-
},
|
|
57
|
+
agent: originalCreds.agent,
|
|
58
|
+
type: refreshedCreds.type,
|
|
59
|
+
data: refreshedCreds.data,
|
|
45
60
|
},
|
|
46
61
|
};
|
|
47
62
|
}
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Common utility for extracting credentials from a directory.
|
|
3
3
|
*/
|
|
4
|
-
import type { AgentCli } from "axshared";
|
|
5
4
|
import type { Credentials } from "./types.js";
|
|
5
|
+
/** Standard agents (all except OpenCode) */
|
|
6
|
+
type StandardAgentCli = "claude" | "codex" | "gemini" | "copilot";
|
|
6
7
|
/**
|
|
7
8
|
* Extract credentials from a JSON file in a directory.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
+
* Only for standard agents (claude, codex, gemini, copilot).
|
|
11
|
+
* OpenCode has its own extraction logic due to different credential structure.
|
|
12
|
+
*
|
|
13
|
+
* @param agentId - The agent ID (standard agents only)
|
|
10
14
|
* @param directory - Directory to read from
|
|
11
15
|
* @param fileName - Name of the credentials file
|
|
12
16
|
* @param transform - Optional transform to apply to file contents
|
|
13
17
|
* @returns Credentials or undefined if not found
|
|
14
18
|
*/
|
|
15
|
-
declare function extractCredsFromDirectory(agentId:
|
|
19
|
+
declare function extractCredsFromDirectory(agentId: StandardAgentCli, directory: string, fileName: string, transform?: (data: Record<string, unknown>) => Record<string, unknown> | undefined): Credentials | undefined;
|
|
16
20
|
export { extractCredsFromDirectory };
|
|
@@ -6,7 +6,10 @@ import path from "node:path";
|
|
|
6
6
|
/**
|
|
7
7
|
* Extract credentials from a JSON file in a directory.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* Only for standard agents (claude, codex, gemini, copilot).
|
|
10
|
+
* OpenCode has its own extraction logic due to different credential structure.
|
|
11
|
+
*
|
|
12
|
+
* @param agentId - The agent ID (standard agents only)
|
|
10
13
|
* @param directory - Directory to read from
|
|
11
14
|
* @param fileName - Name of the credentials file
|
|
12
15
|
* @param transform - Optional transform to apply to file contents
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Token and credential expiry
|
|
2
|
+
* Token and credential expiry/refresh utilities.
|
|
3
|
+
*
|
|
4
|
+
* Provides field detection for common OAuth credential formats:
|
|
5
|
+
* - Google OAuth: expiry_date (ms), refresh_token
|
|
6
|
+
* - Generic OAuth: expires_at (s or ms), refresh_token
|
|
7
|
+
* - OpenCode: expires (ms), refresh
|
|
8
|
+
* - Claude: expiresAt (ms), refreshToken (camelCase)
|
|
3
9
|
*/
|
|
4
10
|
/**
|
|
5
11
|
* Check if a JWT token is expired.
|
|
@@ -23,4 +29,48 @@ declare function isTokenExpired(token: string, bufferSeconds?: number): boolean
|
|
|
23
29
|
* @returns true if expired, false if valid, undefined if no expiry field found
|
|
24
30
|
*/
|
|
25
31
|
declare function isCredentialExpired(data: Record<string, unknown>, bufferSeconds?: number): boolean | undefined;
|
|
26
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Extract expiry timestamp in milliseconds from credential data.
|
|
34
|
+
*
|
|
35
|
+
* Checks common expiry field names in order of preference:
|
|
36
|
+
* - `expiry_date`: Google OAuth (milliseconds)
|
|
37
|
+
* - `expires_at`: Generic OAuth (seconds or milliseconds, auto-detected)
|
|
38
|
+
* - `expiresAt`: Claude (seconds or milliseconds, auto-detected)
|
|
39
|
+
* - `expires`: OpenCode (seconds or milliseconds, auto-detected)
|
|
40
|
+
*/
|
|
41
|
+
declare function extractExpiryTimestamp(data: Record<string, unknown>): number | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Extract expiry date from credential data.
|
|
44
|
+
*
|
|
45
|
+
* Convenience wrapper around {@link extractExpiryTimestamp} that returns
|
|
46
|
+
* a Date object instead of milliseconds.
|
|
47
|
+
*
|
|
48
|
+
* @param data - The credential data object
|
|
49
|
+
* @returns Date if expiry field found, undefined otherwise
|
|
50
|
+
*/
|
|
51
|
+
declare function extractExpiryDate(data: Record<string, unknown>): Date | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* Credential data containing a refresh token.
|
|
54
|
+
*
|
|
55
|
+
* Union of all supported refresh token field naming conventions.
|
|
56
|
+
*/
|
|
57
|
+
type RefreshableCredentials = (Record<string, unknown> & {
|
|
58
|
+
refresh_token: string;
|
|
59
|
+
}) | (Record<string, unknown> & {
|
|
60
|
+
refreshToken: string;
|
|
61
|
+
}) | (Record<string, unknown> & {
|
|
62
|
+
refresh: string;
|
|
63
|
+
});
|
|
64
|
+
/**
|
|
65
|
+
* Check if credential data contains a refresh token.
|
|
66
|
+
*
|
|
67
|
+
* Supports multiple field naming conventions:
|
|
68
|
+
* - `refresh_token`: Standard OAuth / Google / Generic
|
|
69
|
+
* - `refreshToken`: Claude (camelCase)
|
|
70
|
+
* - `refresh`: OpenCode
|
|
71
|
+
*
|
|
72
|
+
* @param data - The credential data object (accepts unknown for safety)
|
|
73
|
+
* @returns true if a refresh token field exists with a string value
|
|
74
|
+
*/
|
|
75
|
+
declare function isRefreshable(data: unknown): data is RefreshableCredentials;
|
|
76
|
+
export { extractExpiryDate, extractExpiryTimestamp, isCredentialExpired, isRefreshable, isTokenExpired, type RefreshableCredentials, };
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Token and credential expiry
|
|
2
|
+
* Token and credential expiry/refresh utilities.
|
|
3
|
+
*
|
|
4
|
+
* Provides field detection for common OAuth credential formats:
|
|
5
|
+
* - Google OAuth: expiry_date (ms), refresh_token
|
|
6
|
+
* - Generic OAuth: expires_at (s or ms), refresh_token
|
|
7
|
+
* - OpenCode: expires (ms), refresh
|
|
8
|
+
* - Claude: expiresAt (ms), refreshToken (camelCase)
|
|
3
9
|
*/
|
|
4
10
|
/**
|
|
5
11
|
* Timestamp threshold to distinguish seconds from milliseconds.
|
|
@@ -65,7 +71,15 @@ function isCredentialExpired(data, bufferSeconds = 60) {
|
|
|
65
71
|
const bufferMs = bufferSeconds * 1000;
|
|
66
72
|
return nowMs > expiryTimestampMs - bufferMs;
|
|
67
73
|
}
|
|
68
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Extract expiry timestamp in milliseconds from credential data.
|
|
76
|
+
*
|
|
77
|
+
* Checks common expiry field names in order of preference:
|
|
78
|
+
* - `expiry_date`: Google OAuth (milliseconds)
|
|
79
|
+
* - `expires_at`: Generic OAuth (seconds or milliseconds, auto-detected)
|
|
80
|
+
* - `expiresAt`: Claude (seconds or milliseconds, auto-detected)
|
|
81
|
+
* - `expires`: OpenCode (seconds or milliseconds, auto-detected)
|
|
82
|
+
*/
|
|
69
83
|
function extractExpiryTimestamp(data) {
|
|
70
84
|
// Google OAuth uses expiry_date (milliseconds)
|
|
71
85
|
if (typeof data.expiry_date === "number") {
|
|
@@ -77,10 +91,62 @@ function extractExpiryTimestamp(data) {
|
|
|
77
91
|
? data.expires_at * 1000
|
|
78
92
|
: data.expires_at;
|
|
79
93
|
}
|
|
80
|
-
//
|
|
94
|
+
// Claude uses expiresAt (milliseconds, camelCase)
|
|
95
|
+
if (typeof data.expiresAt === "number") {
|
|
96
|
+
return data.expiresAt < SECONDS_THRESHOLD
|
|
97
|
+
? data.expiresAt * 1000
|
|
98
|
+
: data.expiresAt;
|
|
99
|
+
}
|
|
100
|
+
// OpenCode uses expires (milliseconds, but apply heuristic for safety)
|
|
81
101
|
if (typeof data.expires === "number") {
|
|
82
|
-
return data.expires
|
|
102
|
+
return data.expires < SECONDS_THRESHOLD
|
|
103
|
+
? data.expires * 1000
|
|
104
|
+
: data.expires;
|
|
83
105
|
}
|
|
84
106
|
return undefined;
|
|
85
107
|
}
|
|
86
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Extract expiry date from credential data.
|
|
110
|
+
*
|
|
111
|
+
* Convenience wrapper around {@link extractExpiryTimestamp} that returns
|
|
112
|
+
* a Date object instead of milliseconds.
|
|
113
|
+
*
|
|
114
|
+
* @param data - The credential data object
|
|
115
|
+
* @returns Date if expiry field found, undefined otherwise
|
|
116
|
+
*/
|
|
117
|
+
function extractExpiryDate(data) {
|
|
118
|
+
const ts = extractExpiryTimestamp(data);
|
|
119
|
+
if (ts === undefined)
|
|
120
|
+
return undefined;
|
|
121
|
+
return new Date(ts);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Check if credential data contains a refresh token.
|
|
125
|
+
*
|
|
126
|
+
* Supports multiple field naming conventions:
|
|
127
|
+
* - `refresh_token`: Standard OAuth / Google / Generic
|
|
128
|
+
* - `refreshToken`: Claude (camelCase)
|
|
129
|
+
* - `refresh`: OpenCode
|
|
130
|
+
*
|
|
131
|
+
* @param data - The credential data object (accepts unknown for safety)
|
|
132
|
+
* @returns true if a refresh token field exists with a string value
|
|
133
|
+
*/
|
|
134
|
+
function isRefreshable(data) {
|
|
135
|
+
if (typeof data !== "object" || data === null)
|
|
136
|
+
return false;
|
|
137
|
+
const record = data;
|
|
138
|
+
// Standard OAuth field name
|
|
139
|
+
if ("refresh_token" in record && typeof record.refresh_token === "string") {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
// Claude uses camelCase
|
|
143
|
+
if ("refreshToken" in record && typeof record.refreshToken === "string") {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
// OpenCode uses 'refresh'
|
|
147
|
+
if ("refresh" in record && typeof record.refresh === "string") {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
export { extractExpiryDate, extractExpiryTimestamp, isCredentialExpired, isRefreshable, isTokenExpired, };
|
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
* Refreshes credentials by running the agent in an isolated temp config,
|
|
5
5
|
* triggering its internal auth refresh mechanism.
|
|
6
6
|
*/
|
|
7
|
-
import type
|
|
7
|
+
import { type Credentials } from "./types.js";
|
|
8
8
|
/** Options for credential refresh */
|
|
9
9
|
interface RefreshOptions {
|
|
10
10
|
/** Timeout in ms (default: 30000) */
|
|
11
11
|
timeout?: number;
|
|
12
12
|
/** Provider for multi-provider agents (OpenCode) */
|
|
13
13
|
provider?: string;
|
|
14
|
+
/** Storage type for persisting refreshed credentials (default: "file") */
|
|
15
|
+
storage?: "keychain" | "file";
|
|
14
16
|
}
|
|
15
17
|
/** Result of a credential refresh attempt */
|
|
16
18
|
type RefreshResult = {
|
|
@@ -39,22 +41,46 @@ type RefreshAndPersistResult = {
|
|
|
39
41
|
ok: false;
|
|
40
42
|
staleCredentials: Credentials;
|
|
41
43
|
};
|
|
44
|
+
/** Install function type for refreshAndPersist */
|
|
45
|
+
type InstallFunction = (creds: Credentials, options?: {
|
|
46
|
+
storage?: "keychain" | "file";
|
|
47
|
+
}) => {
|
|
48
|
+
ok: boolean;
|
|
49
|
+
message: string;
|
|
50
|
+
};
|
|
42
51
|
/**
|
|
43
|
-
* Refresh credentials and persist to
|
|
52
|
+
* Refresh credentials and persist to storage.
|
|
44
53
|
*
|
|
45
54
|
* Higher-level function that:
|
|
46
55
|
* 1. Refreshes credentials via agent subprocess
|
|
47
|
-
* 2.
|
|
48
|
-
* 3. Persists refreshed credentials to original storage location
|
|
56
|
+
* 2. Persists refreshed credentials to the specified storage location
|
|
49
57
|
*
|
|
50
|
-
* @param creds - Original credentials
|
|
58
|
+
* @param creds - Original credentials
|
|
51
59
|
* @param installFunction - Function to install credentials (avoids circular import)
|
|
52
|
-
* @param options - Refresh options
|
|
60
|
+
* @param options - Refresh options including storage type
|
|
53
61
|
* @returns Refreshed credentials or original credentials on failure
|
|
54
62
|
*/
|
|
55
|
-
declare function refreshAndPersist(creds: Credentials, installFunction:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
declare function refreshAndPersist(creds: Credentials, installFunction: InstallFunction, options?: RefreshOptions): Promise<RefreshAndPersistResult>;
|
|
64
|
+
/** Result of refreshing an opaque credential blob */
|
|
65
|
+
type RefreshBlobResult = {
|
|
66
|
+
ok: true;
|
|
67
|
+
blob: unknown;
|
|
68
|
+
expiresAt: Date | undefined;
|
|
69
|
+
} | {
|
|
70
|
+
ok: false;
|
|
71
|
+
error: string;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Refresh credentials from an opaque blob.
|
|
75
|
+
*
|
|
76
|
+
* This is the vault-friendly interface: accepts an opaque blob, validates it,
|
|
77
|
+
* refreshes the credentials, and returns the new blob. The caller (axvault)
|
|
78
|
+
* never needs to understand the blob's internal structure.
|
|
79
|
+
*
|
|
80
|
+
* @param blob - Opaque credential blob (expected to be a Credentials object)
|
|
81
|
+
* @param options - Refresh options (timeout)
|
|
82
|
+
* @returns RefreshBlobResult with new blob and expiresAt, or error
|
|
83
|
+
*/
|
|
84
|
+
declare function refreshBlob(blob: unknown, options?: RefreshOptions): Promise<RefreshBlobResult>;
|
|
85
|
+
export { refreshAndPersist, refreshBlob, refreshCredentials };
|
|
86
|
+
export type { RefreshAndPersistResult, RefreshBlobResult, RefreshOptions, RefreshResult, };
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
import { cleanupCredentials, runAgent } from "axexec";
|
|
8
8
|
import { buildRefreshedCredentials } from "./build-refreshed-credentials.js";
|
|
9
9
|
import { formatRunError } from "./format-run-error.js";
|
|
10
|
-
import {
|
|
10
|
+
import { extractExpiryDate } from "./is-token-expired.js";
|
|
11
|
+
import { resolveRefreshCredentials } from "./resolve-refresh-credentials.js";
|
|
11
12
|
import { waitForRefreshedCredentials } from "./wait-for-refreshed-credentials.js";
|
|
13
|
+
import { parseCredentials } from "./types.js";
|
|
12
14
|
/** Default timeout for refresh operations in milliseconds */
|
|
13
15
|
const DEFAULT_REFRESH_TIMEOUT_MS = 30_000;
|
|
14
16
|
async function safeCleanup(directory) {
|
|
@@ -33,7 +35,7 @@ async function safeCleanup(directory) {
|
|
|
33
35
|
async function refreshCredentials(creds, options) {
|
|
34
36
|
const timeoutMs = options?.timeout ?? DEFAULT_REFRESH_TIMEOUT_MS;
|
|
35
37
|
const deadlineMs = Date.now() + timeoutMs;
|
|
36
|
-
const resolved = resolveRefreshCredentials(creds
|
|
38
|
+
const resolved = resolveRefreshCredentials(creds);
|
|
37
39
|
if (!resolved.ok) {
|
|
38
40
|
return { ok: false, error: resolved.error };
|
|
39
41
|
}
|
|
@@ -88,13 +90,21 @@ async function refreshCredentials(creds, options) {
|
|
|
88
90
|
if (!directories) {
|
|
89
91
|
return { ok: false, error: "No directories in execution metadata" };
|
|
90
92
|
}
|
|
91
|
-
|
|
93
|
+
// For OpenCode, pass the provider to filter refreshed credentials
|
|
94
|
+
const provider = resolved.credentials.agent === "opencode"
|
|
95
|
+
? resolved.credentials.provider
|
|
96
|
+
: undefined;
|
|
97
|
+
const refreshedCredentials = await waitForRefreshedCredentials(resolved.credentials.agent, directories, deadlineMs, { provider });
|
|
92
98
|
if (!refreshedCredentials) {
|
|
93
99
|
return { ok: false, error: "No credentials found after refresh" };
|
|
94
100
|
}
|
|
101
|
+
const buildResult = buildRefreshedCredentials(creds, refreshedCredentials);
|
|
102
|
+
if (!buildResult.ok) {
|
|
103
|
+
return { ok: false, error: buildResult.error };
|
|
104
|
+
}
|
|
95
105
|
return {
|
|
96
106
|
ok: true,
|
|
97
|
-
credentials:
|
|
107
|
+
credentials: buildResult.credentials,
|
|
98
108
|
};
|
|
99
109
|
}
|
|
100
110
|
finally {
|
|
@@ -105,16 +115,15 @@ async function refreshCredentials(creds, options) {
|
|
|
105
115
|
}
|
|
106
116
|
}
|
|
107
117
|
/**
|
|
108
|
-
* Refresh credentials and persist to
|
|
118
|
+
* Refresh credentials and persist to storage.
|
|
109
119
|
*
|
|
110
120
|
* Higher-level function that:
|
|
111
121
|
* 1. Refreshes credentials via agent subprocess
|
|
112
|
-
* 2.
|
|
113
|
-
* 3. Persists refreshed credentials to original storage location
|
|
122
|
+
* 2. Persists refreshed credentials to the specified storage location
|
|
114
123
|
*
|
|
115
|
-
* @param creds - Original credentials
|
|
124
|
+
* @param creds - Original credentials
|
|
116
125
|
* @param installFunction - Function to install credentials (avoids circular import)
|
|
117
|
-
* @param options - Refresh options
|
|
126
|
+
* @param options - Refresh options including storage type
|
|
118
127
|
* @returns Refreshed credentials or original credentials on failure
|
|
119
128
|
*/
|
|
120
129
|
async function refreshAndPersist(creds, installFunction, options) {
|
|
@@ -123,17 +132,39 @@ async function refreshAndPersist(creds, installFunction, options) {
|
|
|
123
132
|
console.error(`Warning: Token refresh failed: ${result.error}`);
|
|
124
133
|
return { ok: false, staleCredentials: creds };
|
|
125
134
|
}
|
|
126
|
-
// Build refreshed credentials, preserving _source and provider context
|
|
127
|
-
const buildResult = buildRefreshedCredentials(creds, result.credentials);
|
|
128
|
-
if (!buildResult.ok) {
|
|
129
|
-
console.error(`Warning: ${buildResult.error}`);
|
|
130
|
-
return { ok: false, staleCredentials: creds };
|
|
131
|
-
}
|
|
132
135
|
// Persist refreshed credentials back to storage
|
|
133
|
-
const
|
|
136
|
+
const storage = options?.storage ?? "file";
|
|
137
|
+
const installResult = installFunction(result.credentials, { storage });
|
|
134
138
|
if (!installResult.ok) {
|
|
135
139
|
console.error(`Warning: Failed to save refreshed credentials: ${installResult.message}`);
|
|
136
140
|
}
|
|
137
|
-
return { ok: true, credentials:
|
|
141
|
+
return { ok: true, credentials: result.credentials };
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Refresh credentials from an opaque blob.
|
|
145
|
+
*
|
|
146
|
+
* This is the vault-friendly interface: accepts an opaque blob, validates it,
|
|
147
|
+
* refreshes the credentials, and returns the new blob. The caller (axvault)
|
|
148
|
+
* never needs to understand the blob's internal structure.
|
|
149
|
+
*
|
|
150
|
+
* @param blob - Opaque credential blob (expected to be a Credentials object)
|
|
151
|
+
* @param options - Refresh options (timeout)
|
|
152
|
+
* @returns RefreshBlobResult with new blob and expiresAt, or error
|
|
153
|
+
*/
|
|
154
|
+
async function refreshBlob(blob, options) {
|
|
155
|
+
// Parse and validate the blob
|
|
156
|
+
const credentials = parseCredentials(blob);
|
|
157
|
+
if (!credentials) {
|
|
158
|
+
return { ok: false, error: "Invalid credential format" };
|
|
159
|
+
}
|
|
160
|
+
// Refresh using existing function
|
|
161
|
+
const result = await refreshCredentials(credentials, options);
|
|
162
|
+
if (!result.ok) {
|
|
163
|
+
return { ok: false, error: result.error };
|
|
164
|
+
}
|
|
165
|
+
// Extract expiry from the refreshed data
|
|
166
|
+
const expiresAt = extractExpiryDate(result.credentials.data);
|
|
167
|
+
// Return the new blob (the Credentials object itself)
|
|
168
|
+
return { ok: true, blob: result.credentials, expiresAt };
|
|
138
169
|
}
|
|
139
|
-
export { refreshAndPersist, refreshCredentials };
|
|
170
|
+
export { refreshAndPersist, refreshBlob, refreshCredentials };
|