axauth 3.2.0 → 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.js +5 -8
- package/dist/auth/is-token-expired.js +32 -12
- package/package.json +1 -1
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 {};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { isCredentialExpired, isTokenExpired } from "./is-token-expired.js";
|
|
2
1
|
import { refreshAndPersist } from "./refresh-credentials.js";
|
|
3
2
|
function findCredentialsWithEnvironmentPriority(adapter, options) {
|
|
4
3
|
const environmentCredentials = adapter.getEnvironmentCredentials?.();
|
|
@@ -7,16 +6,14 @@ function findCredentialsWithEnvironmentPriority(adapter, options) {
|
|
|
7
6
|
const stored = adapter.findStoredCredentials(options);
|
|
8
7
|
return stored?.credentials;
|
|
9
8
|
}
|
|
10
|
-
function shouldRefreshToken(
|
|
9
|
+
function shouldRefreshToken(adapter, creds, options) {
|
|
11
10
|
if (creds.type === "api-key")
|
|
12
11
|
return false;
|
|
13
12
|
if (options?.skipRefresh)
|
|
14
13
|
return false;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
return expired === true;
|
|
14
|
+
if (!adapter.isRefreshable(creds))
|
|
15
|
+
return false;
|
|
16
|
+
return adapter.isExpired(creds) === true;
|
|
20
17
|
}
|
|
21
18
|
async function getAgentAccessTokenFromStoredCredentials(agentId, adapter, options, getAccessToken) {
|
|
22
19
|
const stored = adapter.findStoredCredentials({
|
|
@@ -33,7 +30,7 @@ async function getAccessTokenWithRefresh(agentId, creds, options, dependencies)
|
|
|
33
30
|
const token = dependencies.adapter.getAccessToken(creds, options);
|
|
34
31
|
if (!token)
|
|
35
32
|
return undefined;
|
|
36
|
-
if (!shouldRefreshToken(
|
|
33
|
+
if (!shouldRefreshToken(dependencies.adapter, creds, options)) {
|
|
37
34
|
return token;
|
|
38
35
|
}
|
|
39
36
|
const result = await refreshAndPersist(agentId, creds, (resolvedAgentId, refreshedCreds, installOptions) => dependencies.installCredentials(resolvedAgentId, refreshedCreds, installOptions), {
|
|
@@ -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
|
/**
|