axvault 1.6.0 → 1.7.1
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 +56 -3
- package/dist/cli.js +14 -2
- package/dist/commands/credential.d.ts +3 -0
- package/dist/commands/credential.js +9 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +15 -11
- package/dist/commands/key-revoke.d.ts +3 -0
- package/dist/commands/key-revoke.js +9 -1
- package/dist/commands/key-update.d.ts +1 -0
- package/dist/commands/key-update.js +3 -3
- package/dist/commands/serve.d.ts +1 -0
- package/dist/commands/serve.js +8 -3
- package/dist/refresh/check-refresh.d.ts +4 -27
- package/dist/refresh/check-refresh.js +7 -64
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -2,6 +2,57 @@
|
|
|
2
2
|
|
|
3
3
|
Remote credential storage server for a╳kit.
|
|
4
4
|
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
export AXVAULT_ENCRYPTION_KEY="your-secret-key-minimum-32-chars!"
|
|
9
|
+
axvault init
|
|
10
|
+
axvault serve
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
In another shell, create an API key:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
axvault key create --name "Admin" --read "*" --write "*" --grant "*"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Add `--verbose` to commands like `init`, `serve`, `key revoke`, `key update`, and
|
|
20
|
+
`credential delete` to see status output.
|
|
21
|
+
|
|
22
|
+
## Pipeline examples
|
|
23
|
+
|
|
24
|
+
### Extract key IDs
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
axvault key list | tail -n +2 | cut -f1
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Count credentials by type
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
axvault credential list | tail -n +2 | cut -f3 | sort | uniq -c | sort -rn
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### List credential paths for a single agent
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
axvault credential list | tail -n +2 | awk -F'\t' '$1 == "claude" {print $1 "/" $2}'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Agent Rule
|
|
43
|
+
|
|
44
|
+
Add to your `CLAUDE.md` or `AGENTS.md`:
|
|
45
|
+
|
|
46
|
+
```markdown
|
|
47
|
+
# Rule: `axvault` Usage
|
|
48
|
+
|
|
49
|
+
Run `npx -y axvault --help` to learn available options.
|
|
50
|
+
|
|
51
|
+
Use `axvault` when you need to initialize the vault database, manage API keys,
|
|
52
|
+
or list/delete stored credentials. It outputs TSV tables for list commands, so
|
|
53
|
+
you can pipe them into standard Unix tools.
|
|
54
|
+
```
|
|
55
|
+
|
|
5
56
|
## Configuration
|
|
6
57
|
|
|
7
58
|
### Environment Variables
|
|
@@ -100,9 +151,11 @@ axvault key update k_a1b2c3d4e5f6 --add-read "codex/ci" --remove-write "claude/d
|
|
|
100
151
|
### Revoke a Key
|
|
101
152
|
|
|
102
153
|
```bash
|
|
103
|
-
axvault key revoke k_a1b2c3d4e5f6
|
|
154
|
+
axvault key revoke k_a1b2c3d4e5f6 --force
|
|
104
155
|
```
|
|
105
156
|
|
|
157
|
+
This command requires `--force` or `--yes` to confirm.
|
|
158
|
+
|
|
106
159
|
### Container Deployments
|
|
107
160
|
|
|
108
161
|
#### Running the Container
|
|
@@ -222,13 +275,13 @@ curl -X DELETE https://vault.example.com/api/v1/credentials/claude/prod \
|
|
|
222
275
|
|
|
223
276
|
## Auto-Refresh
|
|
224
277
|
|
|
225
|
-
axvault automatically refreshes `oauth-credentials` type credentials that are near expiration when they are retrieved. This behavior is controlled by the refresh threshold setting. Only credentials
|
|
278
|
+
axvault automatically refreshes `oauth-credentials` type credentials that are near expiration when they are retrieved. This behavior is controlled by the refresh threshold setting. Only credentials containing a refresh token field are eligible for auto-refresh. Supported field names: `refresh_token` (standard OAuth), `refreshToken` (Claude), `refresh` (OpenCode).
|
|
226
279
|
|
|
227
280
|
### Access Control Note
|
|
228
281
|
|
|
229
282
|
Auto-refresh is a server-side maintenance operation that occurs transparently during credential retrieval. Read-only API keys can trigger refresh because:
|
|
230
283
|
|
|
231
|
-
- The refresh uses the credential's own
|
|
284
|
+
- The refresh uses the credential's own refresh token (already authorized by the token owner)
|
|
232
285
|
- The credential's identity and ownership remain unchanged
|
|
233
286
|
- Only token values and expiry timestamps are updated
|
|
234
287
|
- This prevents wasteful repeated refreshes and rate limit issues
|
package/dist/cli.js
CHANGED
|
@@ -40,21 +40,25 @@ Examples:
|
|
|
40
40
|
# List all API keys
|
|
41
41
|
axvault key list
|
|
42
42
|
|
|
43
|
+
# Extract key IDs for scripting (pipeline example)
|
|
44
|
+
axvault key list | tail -n +2 | cut -f1
|
|
45
|
+
|
|
43
46
|
# Update an API key's permissions
|
|
44
47
|
axvault key update k_abc123def456 --add-read "claude/new"
|
|
45
48
|
|
|
46
49
|
# Revoke an API key
|
|
47
|
-
axvault key revoke k_abc123def456
|
|
50
|
+
axvault key revoke k_abc123def456 --force
|
|
48
51
|
|
|
49
52
|
# List all stored credentials
|
|
50
53
|
axvault credential list
|
|
51
54
|
|
|
52
55
|
# Delete a credential
|
|
53
|
-
axvault credential delete claude/work`);
|
|
56
|
+
axvault credential delete claude/work --force`);
|
|
54
57
|
program
|
|
55
58
|
.command("init")
|
|
56
59
|
.description("Initialize database and configuration")
|
|
57
60
|
.option("--db-path <path>", "Database file path")
|
|
61
|
+
.option("-v, --verbose", "Enable verbose output")
|
|
58
62
|
.action(handleInit);
|
|
59
63
|
program
|
|
60
64
|
.command("serve")
|
|
@@ -64,6 +68,7 @@ program
|
|
|
64
68
|
.option("--db-path <path>", "Database file path")
|
|
65
69
|
.option("--refresh-threshold <seconds>", "Refresh credentials expiring within this many seconds (0 to disable)")
|
|
66
70
|
.option("--refresh-timeout <ms>", "Timeout for refresh operations in milliseconds")
|
|
71
|
+
.option("-v, --verbose", "Enable verbose output")
|
|
67
72
|
.action(handleServe);
|
|
68
73
|
// API key management commands
|
|
69
74
|
const keyCommand = program
|
|
@@ -90,6 +95,9 @@ keyCommand
|
|
|
90
95
|
.command("revoke")
|
|
91
96
|
.description("Revoke an API key")
|
|
92
97
|
.argument("<id>", "API key ID (e.g., k_abc123def456)")
|
|
98
|
+
.option("-f, --force", "Confirm destructive action")
|
|
99
|
+
.option("-y, --yes", "Alias for --force")
|
|
100
|
+
.option("-v, --verbose", "Enable verbose output")
|
|
93
101
|
.option("--db-path <path>", "Database file path")
|
|
94
102
|
.action(handleKeyRevoke);
|
|
95
103
|
keyCommand
|
|
@@ -103,6 +111,7 @@ keyCommand
|
|
|
103
111
|
.option("--remove-write <access>", "Remove write access entries")
|
|
104
112
|
.option("--remove-grant <access>", "Remove grant access entries")
|
|
105
113
|
.option("--json", "Output as JSON")
|
|
114
|
+
.option("-v, --verbose", "Enable verbose output")
|
|
106
115
|
.option("--db-path <path>", "Database file path")
|
|
107
116
|
.action(handleKeyUpdate);
|
|
108
117
|
// Credential management commands
|
|
@@ -120,6 +129,9 @@ credentialCommand
|
|
|
120
129
|
.command("delete")
|
|
121
130
|
.description("Delete a credential")
|
|
122
131
|
.argument("<path>", "Credential path (agent/name, e.g., claude/work)")
|
|
132
|
+
.option("-f, --force", "Confirm destructive action")
|
|
133
|
+
.option("-y, --yes", "Alias for --force")
|
|
134
|
+
.option("-v, --verbose", "Enable verbose output")
|
|
123
135
|
.option("--db-path <path>", "Database file path")
|
|
124
136
|
.action(handleCredentialDelete);
|
|
125
137
|
await program.parseAsync(process.argv);
|
|
@@ -7,6 +7,9 @@ interface CredentialListOptions {
|
|
|
7
7
|
}
|
|
8
8
|
interface CredentialDeleteOptions {
|
|
9
9
|
dbPath?: string;
|
|
10
|
+
force?: boolean;
|
|
11
|
+
yes?: boolean;
|
|
12
|
+
verbose?: boolean;
|
|
10
13
|
}
|
|
11
14
|
export declare function handleCredentialList(options: CredentialListOptions): void;
|
|
12
15
|
export declare function handleCredentialDelete(path: string, options: CredentialDeleteOptions): void;
|
|
@@ -91,6 +91,12 @@ export function handleCredentialDelete(path, options) {
|
|
|
91
91
|
process.exitCode = 2;
|
|
92
92
|
return;
|
|
93
93
|
}
|
|
94
|
+
if (!options.force && !options.yes) {
|
|
95
|
+
console.error("Error: Deleting a credential is destructive. Re-run with --force or --yes to confirm.");
|
|
96
|
+
console.error("Try 'axvault credential delete --help' for more information.");
|
|
97
|
+
process.exitCode = 2;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
94
100
|
const { agent, name } = parsed;
|
|
95
101
|
try {
|
|
96
102
|
const config = getServerConfig(options);
|
|
@@ -98,7 +104,9 @@ export function handleCredentialDelete(path, options) {
|
|
|
98
104
|
runMigrations(database);
|
|
99
105
|
const deleted = deleteCredential(database, agent, name);
|
|
100
106
|
if (deleted) {
|
|
101
|
-
|
|
107
|
+
if (options.verbose) {
|
|
108
|
+
console.error(`Deleted credential: ${sanitizeForTsv(agent)}/${sanitizeForTsv(name)}`);
|
|
109
|
+
}
|
|
102
110
|
}
|
|
103
111
|
else {
|
|
104
112
|
console.error(`Error: Credential not found: ${sanitizeForTsv(agent)}/${sanitizeForTsv(name)}`);
|
package/dist/commands/init.d.ts
CHANGED
package/dist/commands/init.js
CHANGED
|
@@ -7,13 +7,15 @@ import { getServerConfig } from "../config.js";
|
|
|
7
7
|
import { closeDatabase, getDatabase } from "../db/client.js";
|
|
8
8
|
import { CURRENT_VERSION, getSchemaVersion, runMigrations, } from "../db/migrations.js";
|
|
9
9
|
export function handleInit(options) {
|
|
10
|
-
const config = getServerConfig(options);
|
|
10
|
+
const config = getServerConfig({ dbPath: options.dbPath });
|
|
11
|
+
const verbose = options.verbose === true;
|
|
11
12
|
// Ensure data directory exists
|
|
12
13
|
const dataDirectory = path.dirname(config.databasePath);
|
|
13
14
|
if (!existsSync(dataDirectory)) {
|
|
14
15
|
try {
|
|
15
16
|
mkdirSync(dataDirectory, { recursive: true });
|
|
16
|
-
|
|
17
|
+
if (verbose)
|
|
18
|
+
console.error(`Created data directory: ${dataDirectory}`);
|
|
17
19
|
}
|
|
18
20
|
catch (error) {
|
|
19
21
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -28,15 +30,17 @@ export function handleInit(options) {
|
|
|
28
30
|
const versionBefore = getSchemaVersion(database);
|
|
29
31
|
runMigrations(database);
|
|
30
32
|
const versionAfter = getSchemaVersion(database);
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
if (verbose) {
|
|
34
|
+
if (versionBefore === 0) {
|
|
35
|
+
console.error(`Database initialized at: ${config.databasePath}`);
|
|
36
|
+
console.error(`Schema version: ${versionAfter}`);
|
|
37
|
+
}
|
|
38
|
+
else if (versionBefore < versionAfter) {
|
|
39
|
+
console.error(`Database migrated from v${versionBefore} to v${versionAfter}`);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.error(`Database already at version ${versionAfter} (current: ${CURRENT_VERSION})`);
|
|
43
|
+
}
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
46
|
catch (error) {
|
|
@@ -24,13 +24,21 @@ export function handleKeyRevoke(id, options) {
|
|
|
24
24
|
process.exitCode = 2;
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
|
+
if (!options.force && !options.yes) {
|
|
28
|
+
console.error("Error: Revoking an API key is destructive. Re-run with --force or --yes to confirm.");
|
|
29
|
+
console.error("Try 'axvault key revoke --help' for more information.");
|
|
30
|
+
process.exitCode = 2;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
27
33
|
try {
|
|
28
34
|
const config = getServerConfig(options);
|
|
29
35
|
const database = getDatabase(config.databasePath);
|
|
30
36
|
runMigrations(database);
|
|
31
37
|
const deleted = deleteApiKey(database, trimmedId);
|
|
32
38
|
if (deleted) {
|
|
33
|
-
|
|
39
|
+
if (options.verbose) {
|
|
40
|
+
console.error(`Revoked API key: ${sanitizeForTsv(trimmedId)}`);
|
|
41
|
+
}
|
|
34
42
|
}
|
|
35
43
|
else {
|
|
36
44
|
console.error(`Error: API key not found: ${sanitizeForTsv(trimmedId)}`);
|
|
@@ -76,7 +76,7 @@ export function handleKeyUpdate(id, options) {
|
|
|
76
76
|
process.exitCode = 1;
|
|
77
77
|
return;
|
|
78
78
|
}
|
|
79
|
-
outputKeyDetails(updatedKey, options.json ?? false);
|
|
79
|
+
outputKeyDetails(updatedKey, options.json ?? false, options.verbose ?? false);
|
|
80
80
|
}
|
|
81
81
|
catch (error) {
|
|
82
82
|
console.error(`Error: Failed to update API key: ${getErrorMessage(error)}`);
|
|
@@ -87,7 +87,7 @@ export function handleKeyUpdate(id, options) {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
/** Output key details in JSON or human-readable format */
|
|
90
|
-
function outputKeyDetails(key, json) {
|
|
90
|
+
function outputKeyDetails(key, json, verbose) {
|
|
91
91
|
if (json) {
|
|
92
92
|
console.log(JSON.stringify({
|
|
93
93
|
id: key.id,
|
|
@@ -99,7 +99,7 @@ function outputKeyDetails(key, json) {
|
|
|
99
99
|
lastUsedAt: formatDateForJson(key.lastUsedAt),
|
|
100
100
|
}, undefined, 2));
|
|
101
101
|
}
|
|
102
|
-
else {
|
|
102
|
+
else if (verbose) {
|
|
103
103
|
console.error(`Updated API key: ${sanitizeForTsv(key.name)}`);
|
|
104
104
|
console.error(`ID: ${sanitizeForTsv(key.id)}`);
|
|
105
105
|
console.error(`Read access: ${sanitizeForTsv(formatAccessList(key.readAccess))}`);
|
package/dist/commands/serve.d.ts
CHANGED
package/dist/commands/serve.js
CHANGED
|
@@ -10,9 +10,12 @@ import { createHealthRouter, createCredentialRouter, } from "../server/routes.js
|
|
|
10
10
|
import { createServer } from "../server/server.js";
|
|
11
11
|
export async function handleServe(options) {
|
|
12
12
|
let config;
|
|
13
|
+
const verbose = options.verbose === true;
|
|
13
14
|
try {
|
|
14
15
|
config = getServerConfig({
|
|
15
|
-
|
|
16
|
+
port: options.port,
|
|
17
|
+
host: options.host,
|
|
18
|
+
dbPath: options.dbPath,
|
|
16
19
|
refreshThresholdSeconds: options.refreshThreshold,
|
|
17
20
|
refreshTimeoutMs: options.refreshTimeout,
|
|
18
21
|
});
|
|
@@ -35,7 +38,8 @@ export async function handleServe(options) {
|
|
|
35
38
|
if (!existsSync(dataDirectory)) {
|
|
36
39
|
try {
|
|
37
40
|
mkdirSync(dataDirectory, { recursive: true });
|
|
38
|
-
|
|
41
|
+
if (verbose)
|
|
42
|
+
console.error(`Created data directory: ${dataDirectory}`);
|
|
39
43
|
}
|
|
40
44
|
catch (error) {
|
|
41
45
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -66,7 +70,8 @@ export async function handleServe(options) {
|
|
|
66
70
|
]);
|
|
67
71
|
// Graceful shutdown handler
|
|
68
72
|
const shutdown = () => {
|
|
69
|
-
|
|
73
|
+
if (verbose)
|
|
74
|
+
console.error("Shutting down...");
|
|
70
75
|
server.stop().then(() => {
|
|
71
76
|
closeDatabase();
|
|
72
77
|
// eslint-disable-next-line unicorn/no-process-exit -- CLI graceful shutdown
|
|
@@ -4,26 +4,11 @@
|
|
|
4
4
|
* Extracted from refresh-manager to reduce complexity.
|
|
5
5
|
*/
|
|
6
6
|
import { type Credentials } from "axauth";
|
|
7
|
-
/**
|
|
8
|
-
* Check if credential is refreshable (has refresh_token or refresh).
|
|
9
|
-
*
|
|
10
|
-
* Detects credentials that have a refresh token field. This is a local
|
|
11
|
-
* check only - the actual refresh is performed by axauth which spawns
|
|
12
|
-
* the agent subprocess. The agent handles its own credential format
|
|
13
|
-
* (e.g., OpenCode uses `refresh` instead of `refresh_token`).
|
|
14
|
-
*
|
|
15
|
-
* Returns false for non-object data to avoid TypeError from `in` operator.
|
|
16
|
-
*
|
|
17
|
-
* Supports both standard OAuth field names and OpenCode field names:
|
|
18
|
-
* - `refresh_token`: Standard OAuth
|
|
19
|
-
* - `refresh`: OpenCode format
|
|
20
|
-
*/
|
|
21
|
-
declare function isRefreshable(data: unknown): data is Record<string, unknown>;
|
|
22
7
|
/**
|
|
23
8
|
* Check if credential needs refresh based on expiry.
|
|
24
9
|
*
|
|
25
10
|
* Only returns true if:
|
|
26
|
-
* 1. Credential data is an object with
|
|
11
|
+
* 1. Credential data is an object with refresh token (required for axauth refresh)
|
|
27
12
|
* 2. Credential has expiry info that is within threshold
|
|
28
13
|
*
|
|
29
14
|
* @param data - Decrypted credential data (accepts unknown for safety)
|
|
@@ -35,17 +20,9 @@ declare function needsRefresh(data: unknown, thresholdSeconds: number): boolean;
|
|
|
35
20
|
* Map vault credential data to axauth Credentials type.
|
|
36
21
|
*
|
|
37
22
|
* @param agent - Agent CLI name
|
|
38
|
-
* @param data - Decrypted credential data from vault (must have
|
|
23
|
+
* @param data - Decrypted credential data from vault (must have refresh token)
|
|
39
24
|
* @returns Credentials object for axauth, or undefined if not mappable
|
|
40
25
|
*/
|
|
41
26
|
declare function toAxauthCredentials(agent: string, data: Record<string, unknown>): Credentials | undefined;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
*
|
|
45
|
-
* Looks for common expiry fields in order of preference:
|
|
46
|
-
* - `expiry_date`: Google OAuth (milliseconds)
|
|
47
|
-
* - `expires_at`: Generic OAuth (seconds or milliseconds)
|
|
48
|
-
* - `expires`: OpenCode format (milliseconds)
|
|
49
|
-
*/
|
|
50
|
-
declare function extractExpiryDate(data: Record<string, unknown>): Date | undefined;
|
|
51
|
-
export { extractExpiryDate, isRefreshable, needsRefresh, toAxauthCredentials };
|
|
27
|
+
export { extractExpiryDate, isRefreshable } from "axauth";
|
|
28
|
+
export { needsRefresh, toAxauthCredentials };
|
|
@@ -4,42 +4,14 @@
|
|
|
4
4
|
* Extracted from refresh-manager to reduce complexity.
|
|
5
5
|
*/
|
|
6
6
|
import { AGENT_CLIS } from "axshared";
|
|
7
|
-
import { isCredentialExpired } from "axauth";
|
|
7
|
+
import { isCredentialExpired, isRefreshable } from "axauth";
|
|
8
8
|
/** Valid agent CLI names - sourced from axshared for consistency */
|
|
9
9
|
const VALID_AGENTS = new Set(AGENT_CLIS);
|
|
10
|
-
/**
|
|
11
|
-
* Check if credential is refreshable (has refresh_token or refresh).
|
|
12
|
-
*
|
|
13
|
-
* Detects credentials that have a refresh token field. This is a local
|
|
14
|
-
* check only - the actual refresh is performed by axauth which spawns
|
|
15
|
-
* the agent subprocess. The agent handles its own credential format
|
|
16
|
-
* (e.g., OpenCode uses `refresh` instead of `refresh_token`).
|
|
17
|
-
*
|
|
18
|
-
* Returns false for non-object data to avoid TypeError from `in` operator.
|
|
19
|
-
*
|
|
20
|
-
* Supports both standard OAuth field names and OpenCode field names:
|
|
21
|
-
* - `refresh_token`: Standard OAuth
|
|
22
|
-
* - `refresh`: OpenCode format
|
|
23
|
-
*/
|
|
24
|
-
function isRefreshable(data) {
|
|
25
|
-
if (typeof data !== "object" || data === null)
|
|
26
|
-
return false;
|
|
27
|
-
const record = data;
|
|
28
|
-
// Check for standard OAuth field name
|
|
29
|
-
if ("refresh_token" in record && typeof record.refresh_token === "string") {
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
// Check for OpenCode field name
|
|
33
|
-
if ("refresh" in record && typeof record.refresh === "string") {
|
|
34
|
-
return true;
|
|
35
|
-
}
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
10
|
/**
|
|
39
11
|
* Check if credential needs refresh based on expiry.
|
|
40
12
|
*
|
|
41
13
|
* Only returns true if:
|
|
42
|
-
* 1. Credential data is an object with
|
|
14
|
+
* 1. Credential data is an object with refresh token (required for axauth refresh)
|
|
43
15
|
* 2. Credential has expiry info that is within threshold
|
|
44
16
|
*
|
|
45
17
|
* @param data - Decrypted credential data (accepts unknown for safety)
|
|
@@ -47,7 +19,7 @@ function isRefreshable(data) {
|
|
|
47
19
|
* @returns true if refresh needed, false otherwise
|
|
48
20
|
*/
|
|
49
21
|
function needsRefresh(data, thresholdSeconds) {
|
|
50
|
-
// Must be an object with
|
|
22
|
+
// Must be an object with refresh token to be refreshable
|
|
51
23
|
if (!isRefreshable(data))
|
|
52
24
|
return false;
|
|
53
25
|
const expired = isCredentialExpired(data, thresholdSeconds);
|
|
@@ -58,7 +30,7 @@ function needsRefresh(data, thresholdSeconds) {
|
|
|
58
30
|
* Map vault credential data to axauth Credentials type.
|
|
59
31
|
*
|
|
60
32
|
* @param agent - Agent CLI name
|
|
61
|
-
* @param data - Decrypted credential data from vault (must have
|
|
33
|
+
* @param data - Decrypted credential data from vault (must have refresh token)
|
|
62
34
|
* @returns Credentials object for axauth, or undefined if not mappable
|
|
63
35
|
*/
|
|
64
36
|
function toAxauthCredentials(agent, data) {
|
|
@@ -72,35 +44,6 @@ function toAxauthCredentials(agent, data) {
|
|
|
72
44
|
data,
|
|
73
45
|
};
|
|
74
46
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
*/
|
|
79
|
-
const SECONDS_THRESHOLD = 10_000_000_000;
|
|
80
|
-
/**
|
|
81
|
-
* Extract expiry date from refreshed credentials.
|
|
82
|
-
*
|
|
83
|
-
* Looks for common expiry fields in order of preference:
|
|
84
|
-
* - `expiry_date`: Google OAuth (milliseconds)
|
|
85
|
-
* - `expires_at`: Generic OAuth (seconds or milliseconds)
|
|
86
|
-
* - `expires`: OpenCode format (milliseconds)
|
|
87
|
-
*/
|
|
88
|
-
function extractExpiryDate(data) {
|
|
89
|
-
// Google OAuth uses expiry_date (milliseconds)
|
|
90
|
-
if (typeof data.expiry_date === "number") {
|
|
91
|
-
return new Date(data.expiry_date);
|
|
92
|
-
}
|
|
93
|
-
// Generic OAuth uses expires_at (seconds or milliseconds)
|
|
94
|
-
if (typeof data.expires_at === "number") {
|
|
95
|
-
const ts = data.expires_at < SECONDS_THRESHOLD
|
|
96
|
-
? data.expires_at * 1000
|
|
97
|
-
: data.expires_at;
|
|
98
|
-
return new Date(ts);
|
|
99
|
-
}
|
|
100
|
-
// OpenCode uses expires (milliseconds)
|
|
101
|
-
if (typeof data.expires === "number") {
|
|
102
|
-
return new Date(data.expires);
|
|
103
|
-
}
|
|
104
|
-
return undefined;
|
|
105
|
-
}
|
|
106
|
-
export { extractExpiryDate, isRefreshable, needsRefresh, toAxauthCredentials };
|
|
47
|
+
// Re-export from axauth for convenience
|
|
48
|
+
export { extractExpiryDate, isRefreshable } from "axauth";
|
|
49
|
+
export { needsRefresh, toAxauthCredentials };
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "axvault",
|
|
3
3
|
"author": "Łukasz Jerciński",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.7.1",
|
|
6
6
|
"description": "Remote credential storage server for axkit",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -49,9 +49,9 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@commander-js/extra-typings": "^14.0.0",
|
|
52
|
-
"axauth": "^1.
|
|
53
|
-
"axshared": "
|
|
54
|
-
"better-sqlite3": "^12.6.
|
|
52
|
+
"axauth": "^2.1.0",
|
|
53
|
+
"axshared": "2.0.0",
|
|
54
|
+
"better-sqlite3": "^12.6.2",
|
|
55
55
|
"commander": "^14.0.2",
|
|
56
56
|
"express": "^5.2.1"
|
|
57
57
|
},
|
|
@@ -78,14 +78,14 @@
|
|
|
78
78
|
"@total-typescript/ts-reset": "^0.6.1",
|
|
79
79
|
"@types/better-sqlite3": "^7.6.13",
|
|
80
80
|
"@types/express": "^5.0.6",
|
|
81
|
-
"@types/node": "^25.0.
|
|
81
|
+
"@types/node": "^25.0.9",
|
|
82
82
|
"@vitest/coverage-v8": "^4.0.17",
|
|
83
83
|
"eslint": "^9.39.2",
|
|
84
|
-
"eslint-config-axkit": "^1.
|
|
84
|
+
"eslint-config-axkit": "^1.1.0",
|
|
85
85
|
"fta-check": "^1.5.1",
|
|
86
86
|
"fta-cli": "^3.0.0",
|
|
87
87
|
"knip": "^5.81.0",
|
|
88
|
-
"prettier": "3.
|
|
88
|
+
"prettier": "3.8.0",
|
|
89
89
|
"semantic-release": "^25.0.2",
|
|
90
90
|
"typescript": "^5.9.3",
|
|
91
91
|
"vitest": "^4.0.17"
|