axauth 1.7.0 → 1.7.2

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.
@@ -64,6 +64,23 @@ interface RemoveOptions {
64
64
  /** Custom data directory for credentials */
65
65
  dataDir?: string;
66
66
  }
67
+ /**
68
+ * Options for extracting credentials from a custom directory.
69
+ *
70
+ * **Agents without separation** (Claude, Codex, Gemini, Copilot):
71
+ * - `configDir` and `dataDir` are interchangeable
72
+ * - Either option specifies where to look for credentials
73
+ *
74
+ * **Agents with separation** (OpenCode):
75
+ * - `dataDir` must be provided to specify where credentials are stored
76
+ * - If only `configDir` is provided, returns undefined (use extractRawCredentials() for default location)
77
+ */
78
+ interface ExtractOptions {
79
+ /** Custom config directory for settings/preferences */
80
+ configDir?: string;
81
+ /** Custom data directory for credentials */
82
+ dataDir?: string;
83
+ }
67
84
  /**
68
85
  * Options for token extraction.
69
86
  *
@@ -118,12 +135,15 @@ interface AuthAdapter {
118
135
  */
119
136
  extractRawCredentials(): Credentials | undefined;
120
137
  /**
121
- * Extract raw credentials from a specific directory.
138
+ * Extract raw credentials from custom directories.
139
+ *
140
+ * Used by token refresh and CI/CD to read credentials from temp directories.
141
+ * Returns undefined if no credentials found at the specified location.
122
142
  *
123
- * Used by token refresh to read credentials from a temp directory.
124
- * Returns undefined if no credentials found at that location.
143
+ * **Agents without separation**: Look in `configDir` or `dataDir` (interchangeable)
144
+ * **Agents with separation** (OpenCode): Look in `dataDir` for credentials
125
145
  */
126
- extractRawCredentialsFromDirectory?(directory: string): Credentials | undefined;
146
+ extractRawCredentialsFromDirectory?(options: ExtractOptions): Credentials | undefined;
127
147
  /**
128
148
  * Install credentials to storage.
129
149
  *
@@ -163,4 +183,4 @@ interface AuthAdapter {
163
183
  */
164
184
  credentialsToEnvironment(creds: Credentials): Record<string, string>;
165
185
  }
166
- export type { AdapterCapabilities, AuthAdapter, InstallOptions, OperationResult, RemoveOptions, TokenOptions, };
186
+ export type { AdapterCapabilities, AuthAdapter, ExtractOptions, InstallOptions, OperationResult, RemoveOptions, TokenOptions, };
@@ -28,7 +28,11 @@ const claudeCodeAdapter = {
28
28
  return undefined;
29
29
  return { agent: AGENT_ID, ...result };
30
30
  },
31
- extractRawCredentialsFromDirectory(directory) {
31
+ extractRawCredentialsFromDirectory(options) {
32
+ // Claude doesn't separate config/data - use either directory
33
+ const directory = options.dataDir ?? options.configDir;
34
+ if (!directory)
35
+ return undefined;
32
36
  // Unwrap claudeAiOauth wrapper (matches loadFileCreds behavior)
33
37
  return extractCredsFromDirectory(AGENT_ID, directory, CREDS_FILE_NAME, (file) => {
34
38
  const data = file.claudeAiOauth;
@@ -24,7 +24,11 @@ const codexAdapter = {
24
24
  },
25
25
  checkAuth,
26
26
  extractRawCredentials,
27
- extractRawCredentialsFromDirectory(directory) {
27
+ extractRawCredentialsFromDirectory(options) {
28
+ // Codex doesn't separate config/data - use either directory
29
+ const directory = options.dataDir ?? options.configDir;
30
+ if (!directory)
31
+ return undefined;
28
32
  const credentialsPath = path.join(directory, CREDS_FILE_NAME);
29
33
  try {
30
34
  if (!existsSync(credentialsPath))
@@ -49,7 +49,11 @@ const copilotAdapter = {
49
49
  data: { accessToken: result.token, _source: result.source },
50
50
  };
51
51
  },
52
- extractRawCredentialsFromDirectory(directory) {
52
+ extractRawCredentialsFromDirectory(options) {
53
+ // Copilot doesn't separate config/data - use either directory
54
+ const directory = options.dataDir ?? options.configDir;
55
+ if (!directory)
56
+ return undefined;
53
57
  return extractCredsFromDirectory(AGENT_ID, directory, CREDS_FILE_NAME);
54
58
  },
55
59
  installCredentials(creds, options) {
@@ -49,7 +49,11 @@ const geminiAdapter = {
49
49
  },
50
50
  };
51
51
  },
52
- extractRawCredentialsFromDirectory(directory) {
52
+ extractRawCredentialsFromDirectory(options) {
53
+ // Gemini doesn't separate config/data - use either directory
54
+ const directory = options.dataDir ?? options.configDir;
55
+ if (!directory)
56
+ return undefined;
53
57
  return extractCredsFromDirectory(AGENT_ID, directory, "oauth_creds.json");
54
58
  },
55
59
  installCredentials(creds, options) {
@@ -26,6 +26,20 @@ function getDefaultAuthFilePath() {
26
26
  const dataDirectory = resolveAgentDataDirectory(AGENT_ID);
27
27
  return path.join(dataDirectory, CREDS_FILE_NAME);
28
28
  }
29
+ /** Read and parse the auth file, returning undefined on any error */
30
+ function readAuthFile(authFile) {
31
+ if (!existsSync(authFile))
32
+ return undefined;
33
+ try {
34
+ const content = JSON.parse(readFileSync(authFile, "utf8"));
35
+ if (typeof content !== "object" || content === null)
36
+ return undefined;
37
+ return content;
38
+ }
39
+ catch {
40
+ return undefined;
41
+ }
42
+ }
29
43
  /** OpenCode authentication adapter */
30
44
  const opencodeAdapter = {
31
45
  agentId: AGENT_ID,
@@ -36,56 +50,42 @@ const opencodeAdapter = {
36
50
  installApiKey: true,
37
51
  },
38
52
  checkAuth() {
39
- const authFile = getDefaultAuthFilePath();
40
- if (!existsSync(authFile)) {
53
+ const auth = readAuthFile(getDefaultAuthFilePath());
54
+ if (!auth)
55
+ return { agentId: AGENT_ID, authenticated: false };
56
+ const providers = Object.keys(auth);
57
+ const firstProvider = providers[0];
58
+ if (firstProvider === undefined) {
41
59
  return { agentId: AGENT_ID, authenticated: false };
42
60
  }
43
- try {
44
- const auth = JSON.parse(readFileSync(authFile, "utf8"));
45
- const providers = Object.keys(auth);
46
- const firstProvider = providers[0];
47
- if (firstProvider !== undefined) {
48
- const firstAuth = auth[firstProvider];
49
- const method = firstAuth?.type === "oauth"
50
- ? "OAuth"
51
- : firstAuth?.type === "api"
52
- ? "API key"
53
- : "Well-known";
54
- return {
55
- agentId: AGENT_ID,
56
- authenticated: true,
57
- method: providers.length > 1
58
- ? `${method} (${providers.length} providers)`
59
- : method,
60
- details: { providers },
61
- };
62
- }
63
- }
64
- catch {
65
- // Invalid JSON
66
- }
67
- return { agentId: AGENT_ID, authenticated: false };
61
+ const firstAuth = auth[firstProvider];
62
+ const method = firstAuth?.type === "oauth"
63
+ ? "OAuth"
64
+ : firstAuth?.type === "api"
65
+ ? "API key"
66
+ : "Well-known";
67
+ return {
68
+ agentId: AGENT_ID,
69
+ authenticated: true,
70
+ method: providers.length > 1
71
+ ? `${method} (${providers.length} providers)`
72
+ : method,
73
+ details: { providers },
74
+ };
68
75
  },
69
76
  extractRawCredentials() {
70
- const authFile = getDefaultAuthFilePath();
71
- if (!existsSync(authFile)) {
77
+ const auth = readAuthFile(getDefaultAuthFilePath());
78
+ if (!auth || Object.keys(auth).length === 0)
72
79
  return undefined;
73
- }
74
- try {
75
- const auth = JSON.parse(readFileSync(authFile, "utf8"));
76
- const providers = Object.keys(auth);
77
- if (providers.length > 0) {
78
- return { agent: AGENT_ID, type: "oauth", data: auth };
79
- }
80
- }
81
- catch {
82
- // Invalid JSON
83
- }
84
- return undefined;
80
+ return { agent: AGENT_ID, type: "oauth", data: auth };
85
81
  },
86
- extractRawCredentialsFromDirectory(directory) {
82
+ extractRawCredentialsFromDirectory(options) {
83
+ // OpenCode stores credentials in dataDir only - configDir is for settings
84
+ // If dataDir not provided, return undefined (use extractRawCredentials() for default)
85
+ if (!options.dataDir)
86
+ return undefined;
87
87
  // Only return if there's at least one provider
88
- return extractCredsFromDirectory(AGENT_ID, directory, CREDS_FILE_NAME, (data) => (Object.keys(data).length > 0 ? data : undefined));
88
+ return extractCredsFromDirectory(AGENT_ID, options.dataDir, CREDS_FILE_NAME, (data) => (Object.keys(data).length > 0 ? data : undefined));
89
89
  },
90
90
  installCredentials(creds, options) {
91
91
  // Resolve custom directory with validation (OpenCode supports separation)
@@ -96,7 +96,7 @@ async function refreshCredentials(creds, options) {
96
96
  }
97
97
  // 9. Read refreshed credentials from temp directory
98
98
  const { extractRawCredentialsFromDirectory } = await import("./registry.js");
99
- const refreshedCredentials = extractRawCredentialsFromDirectory(creds.agent, credentialsDirectory);
99
+ const refreshedCredentials = extractRawCredentialsFromDirectory(creds.agent, { configDir: credentialsDirectory, dataDir: credentialsDirectory });
100
100
  if (!refreshedCredentials) {
101
101
  return { ok: false, error: "No credentials found after refresh" };
102
102
  }
@@ -5,7 +5,7 @@
5
5
  * delegating to the appropriate adapter based on agent ID.
6
6
  */
7
7
  import type { AgentCli } from "axshared";
8
- import type { AdapterCapabilities, AuthAdapter, InstallOptions, OperationResult, RemoveOptions, TokenOptions } from "./adapter.js";
8
+ import type { AdapterCapabilities, AuthAdapter, ExtractOptions, InstallOptions, OperationResult, RemoveOptions, TokenOptions } from "./adapter.js";
9
9
  import type { AuthStatus } from "./types.js";
10
10
  import { type Credentials } from "./types.js";
11
11
  /**
@@ -60,15 +60,22 @@ declare function checkAllAuth(): AuthStatus[];
60
60
  */
61
61
  declare function extractRawCredentials(agentId: AgentCli): Credentials | undefined;
62
62
  /**
63
- * Extract raw credentials from a specific directory.
63
+ * Extract raw credentials from custom directories.
64
64
  *
65
- * Used by token refresh to read credentials from a temp directory.
65
+ * Used by token refresh and CI/CD to read credentials from temp directories.
66
66
  * Returns undefined if no credentials found or adapter doesn't support it.
67
67
  *
68
68
  * @example
69
- * const creds = extractRawCredentialsFromDirectory("claude", "/tmp/refresh-123");
69
+ * // For agents without separation (Claude, Codex, Gemini, Copilot)
70
+ * const creds = extractRawCredentialsFromDirectory("claude", { configDir: "/tmp/config" });
71
+ *
72
+ * // For agents with separation (OpenCode)
73
+ * const creds = extractRawCredentialsFromDirectory("opencode", {
74
+ * configDir: "/tmp/config",
75
+ * dataDir: "/tmp/data",
76
+ * });
70
77
  */
71
- declare function extractRawCredentialsFromDirectory(agentId: AgentCli, directory: string): Credentials | undefined;
78
+ declare function extractRawCredentialsFromDirectory(agentId: AgentCli, options: ExtractOptions): Credentials | undefined;
72
79
  /**
73
80
  * Install credentials for an agent.
74
81
  *
@@ -159,5 +166,6 @@ declare function installCredentialsFromEnvironmentVariable(agent: AgentCli, opti
159
166
  ok: false;
160
167
  error: string;
161
168
  }>;
169
+ export type { ExtractOptions } from "./adapter.js";
162
170
  export type { InstallFromEnvironmentOptions };
163
171
  export { checkAllAuth, checkAuth, credentialsToEnvironment, extractRawCredentials, extractRawCredentialsFromDirectory, getAccessToken, getAdapter, getAgentAccessToken, getAllAdapters, getCapabilities, getCredentialsEnvironmentVariableName, installCredentials, installCredentialsFromEnvironmentVariable, removeCredentials, };
@@ -86,17 +86,24 @@ function extractRawCredentials(agentId) {
86
86
  return ADAPTERS[agentId].extractRawCredentials();
87
87
  }
88
88
  /**
89
- * Extract raw credentials from a specific directory.
89
+ * Extract raw credentials from custom directories.
90
90
  *
91
- * Used by token refresh to read credentials from a temp directory.
91
+ * Used by token refresh and CI/CD to read credentials from temp directories.
92
92
  * Returns undefined if no credentials found or adapter doesn't support it.
93
93
  *
94
94
  * @example
95
- * const creds = extractRawCredentialsFromDirectory("claude", "/tmp/refresh-123");
95
+ * // For agents without separation (Claude, Codex, Gemini, Copilot)
96
+ * const creds = extractRawCredentialsFromDirectory("claude", { configDir: "/tmp/config" });
97
+ *
98
+ * // For agents with separation (OpenCode)
99
+ * const creds = extractRawCredentialsFromDirectory("opencode", {
100
+ * configDir: "/tmp/config",
101
+ * dataDir: "/tmp/data",
102
+ * });
96
103
  */
97
- function extractRawCredentialsFromDirectory(agentId, directory) {
104
+ function extractRawCredentialsFromDirectory(agentId, options) {
98
105
  const adapter = ADAPTERS[agentId];
99
- return adapter.extractRawCredentialsFromDirectory?.(directory);
106
+ return adapter.extractRawCredentialsFromDirectory?.(options);
100
107
  }
101
108
  /**
102
109
  * Install credentials for an agent.
package/dist/crypto.d.ts CHANGED
@@ -1,39 +1,24 @@
1
1
  /**
2
2
  * Encryption utilities for credential export/import.
3
3
  *
4
- * Uses AES-256-GCM with PBKDF2 key derivation.
4
+ * Re-exports core crypto from axshared and adds axauth-specific functionality.
5
5
  */
6
+ import { type EncryptedData, type EncryptedDataBase64 } from "axshared";
7
+ export { encrypt, fromBase64, toBase64 } from "axshared";
6
8
  /**
7
9
  * Derive default password from predictable seed.
8
10
  * Provides obfuscation, not security - attacker would need to understand derivation logic.
9
11
  */
10
12
  declare const DEFAULT_PASSWORD: string;
11
- /** Encrypted data structure */
12
- interface EncryptedData {
13
- ciphertext: Buffer;
14
- salt: Buffer;
15
- iv: Buffer;
16
- tag: Buffer;
17
- }
18
- /** Serialized encrypted file format */
19
- interface EncryptedFile {
13
+ /** Serialized encrypted file format with axauth metadata */
14
+ interface EncryptedFile extends EncryptedDataBase64 {
20
15
  version: 1;
21
16
  agent: string;
22
- ciphertext: string;
23
- salt: string;
24
- iv: string;
25
- tag: string;
26
17
  }
27
- /** Encrypt data with password */
28
- declare function encrypt(data: string, password: string): EncryptedData;
29
- /** Convert encrypted data to base64 for JSON serialization */
30
- declare function toBase64(encrypted: EncryptedData): Omit<EncryptedFile, "version" | "agent">;
31
- /** Convert base64 strings back to buffers */
32
- declare function fromBase64(file: Omit<EncryptedFile, "version" | "agent">): EncryptedData;
33
18
  /**
34
19
  * Try decrypting with default password first, then prompt for custom.
35
20
  * Returns decrypted string on success.
36
21
  */
37
22
  declare function tryDecrypt(encrypted: EncryptedData, promptFunction: () => Promise<string>): Promise<string>;
38
- export { DEFAULT_PASSWORD, encrypt, fromBase64, toBase64, tryDecrypt };
23
+ export { DEFAULT_PASSWORD, tryDecrypt };
39
24
  export type { EncryptedFile };
package/dist/crypto.js CHANGED
@@ -1,15 +1,11 @@
1
1
  /**
2
2
  * Encryption utilities for credential export/import.
3
3
  *
4
- * Uses AES-256-GCM with PBKDF2 key derivation.
4
+ * Re-exports core crypto from axshared and adds axauth-specific functionality.
5
5
  */
6
- import { createCipheriv, createDecipheriv, createHash, pbkdf2Sync, randomBytes, } from "node:crypto";
7
- const ALGORITHM = "aes-256-gcm";
8
- const ITERATIONS = 100_000;
9
- const KEY_LENGTH = 32;
10
- const SALT_LENGTH = 16;
11
- const IV_LENGTH = 12;
12
- const TAG_LENGTH = 16;
6
+ import { createHash } from "node:crypto";
7
+ import { decrypt, } from "axshared";
8
+ export { encrypt, fromBase64, toBase64 } from "axshared";
13
9
  /**
14
10
  * Derive default password from predictable seed.
15
11
  * Provides obfuscation, not security - attacker would need to understand derivation logic.
@@ -18,48 +14,6 @@ const DEFAULT_PASSWORD = createHash("sha256")
18
14
  .update(["axauth", "credentials", "default", "v1"].join("."))
19
15
  .digest("base64")
20
16
  .slice(0, 32);
21
- /** Encrypt data with password */
22
- function encrypt(data, password) {
23
- const salt = randomBytes(SALT_LENGTH);
24
- const key = pbkdf2Sync(password, salt, ITERATIONS, KEY_LENGTH, "sha256");
25
- const iv = randomBytes(IV_LENGTH);
26
- const cipher = createCipheriv(ALGORITHM, key, iv, {
27
- authTagLength: TAG_LENGTH,
28
- });
29
- const ciphertext = Buffer.concat([
30
- cipher.update(data, "utf8"),
31
- cipher.final(),
32
- ]);
33
- return { ciphertext, salt, iv, tag: cipher.getAuthTag() };
34
- }
35
- /** Decrypt data with password. Throws on wrong password. */
36
- function decrypt(encrypted, password) {
37
- const key = pbkdf2Sync(password, encrypted.salt, ITERATIONS, KEY_LENGTH, "sha256");
38
- const decipher = createDecipheriv(ALGORITHM, key, encrypted.iv, {
39
- authTagLength: TAG_LENGTH,
40
- });
41
- decipher.setAuthTag(encrypted.tag);
42
- return (decipher.update(encrypted.ciphertext).toString("utf8") +
43
- decipher.final("utf8"));
44
- }
45
- /** Convert encrypted data to base64 for JSON serialization */
46
- function toBase64(encrypted) {
47
- return {
48
- ciphertext: encrypted.ciphertext.toString("base64"),
49
- salt: encrypted.salt.toString("base64"),
50
- iv: encrypted.iv.toString("base64"),
51
- tag: encrypted.tag.toString("base64"),
52
- };
53
- }
54
- /** Convert base64 strings back to buffers */
55
- function fromBase64(file) {
56
- return {
57
- ciphertext: Buffer.from(file.ciphertext, "base64"),
58
- salt: Buffer.from(file.salt, "base64"),
59
- iv: Buffer.from(file.iv, "base64"),
60
- tag: Buffer.from(file.tag, "base64"),
61
- };
62
- }
63
17
  /**
64
18
  * Try decrypting with default password first, then prompt for custom.
65
19
  * Returns decrypted string on success.
@@ -75,4 +29,4 @@ async function tryDecrypt(encrypted, promptFunction) {
75
29
  return decrypt(encrypted, password);
76
30
  }
77
31
  }
78
- export { DEFAULT_PASSWORD, encrypt, fromBase64, toBase64, tryDecrypt };
32
+ export { DEFAULT_PASSWORD, tryDecrypt };
package/dist/index.d.ts CHANGED
@@ -34,6 +34,6 @@ export type { AgentCli } from "axshared";
34
34
  export { AGENT_CLIS } from "axshared";
35
35
  export type { AdapterCapabilities, AuthAdapter, InstallOptions, OperationResult, RemoveOptions, } from "./auth/adapter.js";
36
36
  export type { AuthStatus, Credentials } from "./auth/types.js";
37
- export { checkAllAuth, checkAuth, credentialsToEnvironment, extractRawCredentials, extractRawCredentialsFromDirectory, getAccessToken, getAgentAccessToken, getCredentialsEnvironmentVariableName, installCredentials, installCredentialsFromEnvironmentVariable, removeCredentials, getAdapter, getAllAdapters, getCapabilities, type InstallFromEnvironmentOptions, } from "./auth/registry.js";
37
+ export { checkAllAuth, checkAuth, credentialsToEnvironment, extractRawCredentials, extractRawCredentialsFromDirectory, getAccessToken, getAgentAccessToken, getCredentialsEnvironmentVariableName, installCredentials, installCredentialsFromEnvironmentVariable, removeCredentials, getAdapter, getAllAdapters, getCapabilities, type ExtractOptions, type InstallFromEnvironmentOptions, } from "./auth/registry.js";
38
38
  export { isCredentialExpired, isTokenExpired, } from "./auth/is-token-expired.js";
39
39
  export { refreshAndPersist, refreshCredentials, type RefreshAndPersistResult, type RefreshOptions, type RefreshResult, } from "./auth/refresh-credentials.js";
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "axauth",
3
3
  "author": "Łukasz Jerciński",
4
4
  "license": "MIT",
5
- "version": "1.7.0",
5
+ "version": "1.7.2",
6
6
  "description": "Authentication management library and CLI for AI coding agents",
7
7
  "repository": {
8
8
  "type": "git",
@@ -69,10 +69,10 @@
69
69
  "dependencies": {
70
70
  "@commander-js/extra-typings": "^14.0.0",
71
71
  "@inquirer/password": "^5.0.3",
72
- "axconfig": "^3.2.0",
73
- "axshared": "^1.7.0",
72
+ "axconfig": "^3.4.2",
73
+ "axshared": "^1.8.0",
74
74
  "commander": "^14.0.2",
75
- "zod": "^4.3.4"
75
+ "zod": "^4.3.5"
76
76
  },
77
77
  "devDependencies": {
78
78
  "@eslint/compat": "^2.0.0",
@@ -80,18 +80,18 @@
80
80
  "@total-typescript/ts-reset": "^0.6.1",
81
81
  "@types/node": "^25.0.3",
82
82
  "@vitest/coverage-v8": "^4.0.16",
83
- "@vitest/eslint-plugin": "^1.6.4",
83
+ "@vitest/eslint-plugin": "^1.6.5",
84
84
  "eslint": "^9.39.2",
85
85
  "eslint-config-prettier": "^10.1.8",
86
86
  "eslint-plugin-unicorn": "^62.0.0",
87
87
  "fta-check": "^1.5.1",
88
88
  "fta-cli": "^3.0.0",
89
89
  "globals": "^16.5.0",
90
- "knip": "^5.78.0",
90
+ "knip": "^5.80.0",
91
91
  "prettier": "3.7.4",
92
92
  "semantic-release": "^25.0.2",
93
93
  "typescript": "^5.9.3",
94
- "typescript-eslint": "^8.51.0",
94
+ "typescript-eslint": "^8.52.0",
95
95
  "vitest": "^4.0.16"
96
96
  }
97
97
  }