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.
@@ -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(token, creds, options) {
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
- let expired = isTokenExpired(token);
16
- if (expired === undefined) {
17
- expired = isCredentialExpired(creds.data);
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(token, creds, options)) {
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
- * Check if a JWT token is expired.
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
- * @param token - The JWT token string
26
- * @param bufferSeconds - Buffer before actual expiry (default: 60s)
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 isTokenExpired(token, bufferSeconds = 60) {
26
+ function jwtExpiryMs(token) {
30
27
  const parts = token.split(".");
31
28
  if (parts.length !== 3)
32
- return undefined; // Not a JWT
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; // No exp claim
44
- const nowSeconds = Date.now() / 1000;
45
- return nowSeconds > exp - bufferSeconds;
40
+ return undefined;
41
+ return exp * 1000;
46
42
  }
47
43
  catch {
48
- return undefined; // Invalid JWT
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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "axauth",
3
3
  "author": "Łukasz Jerciński",
4
4
  "license": "MIT",
5
- "version": "3.2.0",
5
+ "version": "3.3.0",
6
6
  "description": "Authentication management library and CLI for AI coding agents",
7
7
  "repository": {
8
8
  "type": "git",