axvault 1.8.1 → 1.8.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.
- package/README.md +120 -36
- package/dist/cli.js +7 -7
- package/dist/commands/credential.d.ts +1 -1
- package/dist/commands/credential.js +26 -32
- package/dist/commands/init.js +12 -11
- package/dist/commands/serve.js +1 -0
- package/dist/db/migrations.d.ts +3 -2
- package/dist/db/migrations.js +25 -120
- package/dist/db/repositories/api-key-utilities.d.ts +3 -3
- package/dist/db/repositories/api-key-utilities.js +9 -10
- package/dist/db/repositories/audit-log.d.ts +3 -5
- package/dist/db/repositories/audit-log.js +7 -8
- package/dist/db/repositories/credentials-queries.d.ts +9 -5
- package/dist/db/repositories/credentials-queries.js +28 -12
- package/dist/db/repositories/credentials.d.ts +31 -14
- package/dist/db/repositories/credentials.js +39 -21
- package/dist/db/repositories/list-credentials-paginated.d.ts +26 -0
- package/dist/db/repositories/list-credentials-paginated.js +69 -0
- package/dist/db/repositories/parse-credential-row.d.ts +2 -9
- package/dist/db/repositories/parse-credential-row.js +2 -17
- package/dist/db/types.d.ts +3 -12
- package/dist/db/types.js +1 -1
- package/dist/handlers/delete-credential.d.ts +2 -3
- package/dist/handlers/delete-credential.js +8 -11
- package/dist/handlers/get-credential.d.ts +6 -3
- package/dist/handlers/get-credential.js +35 -78
- package/dist/handlers/list-credentials.d.ts +19 -3
- package/dist/handlers/list-credentials.js +83 -8
- package/dist/handlers/put-credential.d.ts +10 -3
- package/dist/handlers/put-credential.js +25 -78
- package/dist/handlers/refresh-credential-on-read.d.ts +26 -0
- package/dist/handlers/refresh-credential-on-read.js +145 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/credential-name.d.ts +10 -0
- package/dist/lib/credential-name.js +12 -0
- package/dist/lib/format.d.ts +1 -7
- package/dist/lib/format.js +7 -55
- package/dist/middleware/validate-parameters.d.ts +3 -3
- package/dist/middleware/validate-parameters.js +7 -14
- package/dist/server/routes.js +3 -3
- package/package.json +9 -9
- package/dist/refresh/check-refresh.d.ts +0 -29
- package/dist/refresh/check-refresh.js +0 -51
- package/dist/refresh/log-refresh.d.ts +0 -17
- package/dist/refresh/log-refresh.js +0 -35
- package/dist/refresh/refresh-manager.d.ts +0 -54
- package/dist/refresh/refresh-manager.js +0 -137
package/README.md
CHANGED
|
@@ -2,41 +2,70 @@
|
|
|
2
2
|
|
|
3
3
|
Remote credential storage server for a╳kit.
|
|
4
4
|
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Node.js 22.14+
|
|
8
|
+
- `pnpm` (for `pnpm dlx axvault`) or `npx` (for `npx -y axvault`)
|
|
9
|
+
- `jq` for scripting against `--json` output
|
|
10
|
+
|
|
11
|
+
If `axvault` is not installed globally, prefix commands with `npx -y axvault` (or `pnpm dlx axvault`).
|
|
12
|
+
|
|
5
13
|
## Quick start
|
|
6
14
|
|
|
7
15
|
```bash
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
16
|
+
umask 077
|
|
17
|
+
printf 'AXVAULT_ENCRYPTION_KEY=' > .env
|
|
18
|
+
openssl rand -base64 32 >> .env
|
|
19
|
+
chmod 600 .env
|
|
20
|
+
set -a
|
|
21
|
+
. ./.env
|
|
22
|
+
set +a
|
|
23
|
+
mkdir -p ./data
|
|
24
|
+
npx -y axvault init
|
|
25
|
+
npx -y axvault serve
|
|
11
26
|
```
|
|
12
27
|
|
|
28
|
+
Keep the `.env` file and reuse the same key between restarts to avoid losing access to existing credentials.
|
|
29
|
+
|
|
30
|
+
In a new shell, you must re-export the variables from `.env` before running `axvault` commands (the server does not load `.env` automatically).
|
|
31
|
+
|
|
13
32
|
In another shell, create an API key:
|
|
14
33
|
|
|
15
34
|
```bash
|
|
16
|
-
axvault key create --name "Admin" --read "*" --write "*" --grant "*"
|
|
35
|
+
npx -y axvault key create --name "Admin" --read "*" --write "*" --grant "*"
|
|
17
36
|
```
|
|
18
37
|
|
|
19
38
|
Add `--verbose` to commands like `init`, `serve`, `key revoke`, `key update`, and
|
|
20
39
|
`credential delete` to see status output.
|
|
21
40
|
|
|
41
|
+
## Output formats
|
|
42
|
+
|
|
43
|
+
List commands output TSV by default. Use `--json` on `key create`, `key list`,
|
|
44
|
+
`key update`, and `credential list` for structured output (note: `key create
|
|
45
|
+
--json` includes the secret key).
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx -y axvault key list --json | jq -r '.[].id'
|
|
49
|
+
```
|
|
50
|
+
|
|
22
51
|
## Pipeline examples
|
|
23
52
|
|
|
24
53
|
### Extract key IDs
|
|
25
54
|
|
|
26
55
|
```bash
|
|
27
|
-
axvault key list | tail -n +2 | cut -f1
|
|
56
|
+
npx -y axvault key list | tail -n +2 | cut -f1
|
|
28
57
|
```
|
|
29
58
|
|
|
30
|
-
### Count credentials by
|
|
59
|
+
### Count credentials by creation date
|
|
31
60
|
|
|
32
61
|
```bash
|
|
33
|
-
axvault credential list |
|
|
62
|
+
npx -y axvault credential list --json | jq -r '.[].createdAt | split("T")[0]' | sort | uniq -c | sort -rn
|
|
34
63
|
```
|
|
35
64
|
|
|
36
|
-
### List credential
|
|
65
|
+
### List credential names in the `claude` namespace
|
|
37
66
|
|
|
38
67
|
```bash
|
|
39
|
-
axvault credential list | tail -n +2 | awk -F'\t' '$1
|
|
68
|
+
npx -y axvault credential list | tail -n +2 | awk -F'\t' '$1 ~ /^claude\./ {print $1}'
|
|
40
69
|
```
|
|
41
70
|
|
|
42
71
|
## Agent Rule
|
|
@@ -49,8 +78,8 @@ Add to your `CLAUDE.md` or `AGENTS.md`:
|
|
|
49
78
|
Run `npx -y axvault --help` to learn available options.
|
|
50
79
|
|
|
51
80
|
Use `axvault` when you need to initialize the vault database, manage API keys,
|
|
52
|
-
or list/delete stored credentials.
|
|
53
|
-
you can pipe
|
|
81
|
+
or list/delete stored credentials. List commands output TSV by default; add
|
|
82
|
+
`--json` for structured output you can pipe into `jq`.
|
|
54
83
|
```
|
|
55
84
|
|
|
56
85
|
## Configuration
|
|
@@ -71,7 +100,7 @@ you can pipe them into standard Unix tools.
|
|
|
71
100
|
The `serve` command accepts flags that override environment variables:
|
|
72
101
|
|
|
73
102
|
```bash
|
|
74
|
-
axvault serve \
|
|
103
|
+
npx -y axvault serve \
|
|
75
104
|
--port 8080 \
|
|
76
105
|
--host 0.0.0.0 \
|
|
77
106
|
--db-path /data/vault.db \
|
|
@@ -81,6 +110,11 @@ axvault serve \
|
|
|
81
110
|
|
|
82
111
|
Setting `--refresh-threshold 0` disables automatic credential refresh.
|
|
83
112
|
|
|
113
|
+
## Confirmation flags
|
|
114
|
+
|
|
115
|
+
Destructive commands require confirmation: `axvault key revoke` and `axvault
|
|
116
|
+
credential delete` require `--force` (alias `--yes`).
|
|
117
|
+
|
|
84
118
|
## API Keys
|
|
85
119
|
|
|
86
120
|
API keys control access to the credential API. Each key has configurable permissions:
|
|
@@ -93,17 +127,17 @@ API keys control access to the credential API. Each key has configurable permiss
|
|
|
93
127
|
|
|
94
128
|
```bash
|
|
95
129
|
# Full access (read, write, and grant)
|
|
96
|
-
axvault key create --name "Admin" --read "*" --write "*" --grant "*"
|
|
130
|
+
npx -y axvault key create --name "Admin" --read "*" --write "*" --grant "*"
|
|
97
131
|
|
|
98
132
|
# Read/write access only
|
|
99
|
-
axvault key create --name "CI Pipeline" --read "*" --write "*"
|
|
133
|
+
npx -y axvault key create --name "CI Pipeline" --read "*" --write "*"
|
|
100
134
|
|
|
101
135
|
# Restricted access
|
|
102
|
-
axvault key create --name "Claude Reader" --read "claude
|
|
103
|
-
axvault key create --name "Deploy Script" --write "claude
|
|
136
|
+
npx -y axvault key create --name "Claude Reader" --read "claude.work,claude.ci"
|
|
137
|
+
npx -y axvault key create --name "Deploy Script" --write "claude.prod,codex.prod"
|
|
104
138
|
|
|
105
139
|
# Grant-only key (for delegation, does not allow direct read/write)
|
|
106
|
-
axvault key create --name "Issuer" --grant "claude
|
|
140
|
+
npx -y axvault key create --name "Issuer" --grant "claude.work,claude.ci"
|
|
107
141
|
```
|
|
108
142
|
|
|
109
143
|
The command outputs metadata to stderr and the secret key to stdout for easy piping:
|
|
@@ -122,12 +156,12 @@ Save this key securely - it cannot be retrieved later.
|
|
|
122
156
|
axv_sk_0123456789abcdef0123456789abcdef
|
|
123
157
|
```
|
|
124
158
|
|
|
125
|
-
To copy directly to clipboard: `axvault key create --name "My Key" --read "*" | pbcopy`
|
|
159
|
+
To copy directly to clipboard: `npx -y axvault key create --name "My Key" --read "*" | pbcopy`
|
|
126
160
|
|
|
127
161
|
### List Keys
|
|
128
162
|
|
|
129
163
|
```bash
|
|
130
|
-
axvault key list
|
|
164
|
+
npx -y axvault key list
|
|
131
165
|
```
|
|
132
166
|
|
|
133
167
|
### Update a Key
|
|
@@ -136,22 +170,22 @@ Modify an existing key's permissions:
|
|
|
136
170
|
|
|
137
171
|
```bash
|
|
138
172
|
# Add read access for new credentials
|
|
139
|
-
axvault key update k_a1b2c3d4e5f6 --add-read "gemini
|
|
173
|
+
npx -y axvault key update k_a1b2c3d4e5f6 --add-read "gemini.prod"
|
|
140
174
|
|
|
141
175
|
# Remove write access
|
|
142
|
-
axvault key update k_a1b2c3d4e5f6 --remove-write "claude
|
|
176
|
+
npx -y axvault key update k_a1b2c3d4e5f6 --remove-write "claude.test"
|
|
143
177
|
|
|
144
178
|
# Add grant permissions
|
|
145
|
-
axvault key update k_a1b2c3d4e5f6 --add-grant "claude
|
|
179
|
+
npx -y axvault key update k_a1b2c3d4e5f6 --add-grant "claude.work"
|
|
146
180
|
|
|
147
181
|
# Multiple changes at once
|
|
148
|
-
axvault key update k_a1b2c3d4e5f6 --add-read "codex
|
|
182
|
+
npx -y axvault key update k_a1b2c3d4e5f6 --add-read "codex.ci" --remove-write "claude.dev"
|
|
149
183
|
```
|
|
150
184
|
|
|
151
185
|
### Revoke a Key
|
|
152
186
|
|
|
153
187
|
```bash
|
|
154
|
-
axvault key revoke k_a1b2c3d4e5f6 --force
|
|
188
|
+
npx -y axvault key revoke k_a1b2c3d4e5f6 --force
|
|
155
189
|
```
|
|
156
190
|
|
|
157
191
|
This command requires `--force` or `--yes` to confirm.
|
|
@@ -243,7 +277,7 @@ docker exec axvault node /app/node_modules/axvault/bin/axvault key create --name
|
|
|
243
277
|
### Store a Credential
|
|
244
278
|
|
|
245
279
|
```bash
|
|
246
|
-
curl -X PUT https://vault.example.com/api/v1/credentials/claude
|
|
280
|
+
curl -X PUT https://vault.example.com/api/v1/credentials/claude.prod \
|
|
247
281
|
-H "Authorization: Bearer <api_key>" \
|
|
248
282
|
-H "Content-Type: application/json" \
|
|
249
283
|
-d '{
|
|
@@ -254,25 +288,75 @@ curl -X PUT https://vault.example.com/api/v1/credentials/claude/prod \
|
|
|
254
288
|
}'
|
|
255
289
|
```
|
|
256
290
|
|
|
291
|
+
Returns 201 with the stored credential's metadata:
|
|
292
|
+
|
|
293
|
+
```json
|
|
294
|
+
{
|
|
295
|
+
"name": "claude.prod",
|
|
296
|
+
"createdAt": "2026-01-15T10:30:00.000Z",
|
|
297
|
+
"updatedAt": "2026-01-15T10:30:00.000Z"
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
257
301
|
The `type` field is required and must be one of:
|
|
258
302
|
|
|
259
303
|
- `"oauth-credentials"` — Full OAuth with refresh_token (eligible for auto-refresh)
|
|
260
304
|
- `"oauth-token"` — Long-lived OAuth token like `CLAUDE_CODE_OAUTH_TOKEN` (static)
|
|
261
305
|
- `"api-key"` — API key (static)
|
|
262
306
|
|
|
263
|
-
The `provider` field is optional
|
|
307
|
+
The `provider` field is optional for single-provider agents. For OpenCode (multi-provider), `provider` is required (e.g., `"anthropic"`, `"openai"`, `"gemini"`).
|
|
308
|
+
|
|
309
|
+
### List Credentials
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
curl https://vault.example.com/api/v1/credentials \
|
|
313
|
+
-H "Authorization: Bearer <api_key>"
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Returns metadata for all accessible credentials. Supports opt-in cursor-based pagination:
|
|
317
|
+
|
|
318
|
+
| Parameter | Description | Default |
|
|
319
|
+
| --------- | -------------------------------------------------------- | ----------- |
|
|
320
|
+
| `limit` | Results per page (1–1000; values above 1000 are clamped) | All results |
|
|
321
|
+
| `cursor` | Name from previous page's `nextCursor` | — |
|
|
322
|
+
|
|
323
|
+
Without `limit`, all accessible credentials are returned (backward-compatible). `cursor` requires `limit` — providing `cursor` without `limit` returns 400.
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
# First page
|
|
327
|
+
curl 'https://vault.example.com/api/v1/credentials?limit=50' \
|
|
328
|
+
-H "Authorization: Bearer <api_key>"
|
|
329
|
+
|
|
330
|
+
# Next page (cursor is the last name from the previous response)
|
|
331
|
+
curl 'https://vault.example.com/api/v1/credentials?limit=50&cursor=claude.prod' \
|
|
332
|
+
-H "Authorization: Bearer <api_key>"
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Response includes `nextCursor` when `limit` is set and more results are available:
|
|
336
|
+
|
|
337
|
+
```json
|
|
338
|
+
{
|
|
339
|
+
"credentials": [
|
|
340
|
+
{ "name": "claude.staging", "createdAt": "...", "updatedAt": "..." },
|
|
341
|
+
{ "name": "gemini.ci", "createdAt": "...", "updatedAt": "..." }
|
|
342
|
+
],
|
|
343
|
+
"nextCursor": "gemini.ci"
|
|
344
|
+
}
|
|
345
|
+
```
|
|
264
346
|
|
|
265
347
|
### Retrieve a Credential
|
|
266
348
|
|
|
267
349
|
```bash
|
|
268
|
-
curl https://vault.example.com/api/v1/credentials/claude
|
|
350
|
+
curl https://vault.example.com/api/v1/credentials/claude.prod \
|
|
269
351
|
-H "Authorization: Bearer <api_key>"
|
|
270
352
|
```
|
|
271
353
|
|
|
272
354
|
### Delete a Credential
|
|
273
355
|
|
|
356
|
+
Returns 204 No Content on success.
|
|
357
|
+
|
|
274
358
|
```bash
|
|
275
|
-
curl -X DELETE https://vault.example.com/api/v1/credentials/claude
|
|
359
|
+
curl -X DELETE https://vault.example.com/api/v1/credentials/claude.prod \
|
|
276
360
|
-H "Authorization: Bearer <api_key>"
|
|
277
361
|
```
|
|
278
362
|
|
|
@@ -352,13 +436,13 @@ Alternatively, use separate environment variables:
|
|
|
352
436
|
|
|
353
437
|
### Common Errors
|
|
354
438
|
|
|
355
|
-
| Error | Cause | Solution
|
|
356
|
-
| ---------------- | ------------------------------------------ |
|
|
357
|
-
| `not-configured` | Missing `AXVAULT_URL` or `AXVAULT_API_KEY` | Set both environment variables or use `AXVAULT` JSON
|
|
358
|
-
| `unauthorized` | Invalid API key | Verify key with `axvault key list`, create new if needed |
|
|
359
|
-
| `forbidden` | No access to credential | Add credential to key's read access: `axvault key update <id> --add-read <path>` |
|
|
360
|
-
| `not-found` | Credential doesn't exist | Store credential first: `axauth vault push --agent <agent> --name <name>`
|
|
361
|
-
| `unreachable` | Network issue or server down | Check vault URL, verify server is running
|
|
439
|
+
| Error | Cause | Solution |
|
|
440
|
+
| ---------------- | ------------------------------------------ | --------------------------------------------------------------------------------------- |
|
|
441
|
+
| `not-configured` | Missing `AXVAULT_URL` or `AXVAULT_API_KEY` | Set both environment variables or use `AXVAULT` JSON |
|
|
442
|
+
| `unauthorized` | Invalid API key | Verify key with `npx -y axvault key list`, create new if needed |
|
|
443
|
+
| `forbidden` | No access to credential | Add credential to key's read access: `npx -y axvault key update <id> --add-read <path>` |
|
|
444
|
+
| `not-found` | Credential doesn't exist | Store credential first: `axauth vault push --agent <agent> --name <name>` |
|
|
445
|
+
| `unreachable` | Network issue or server down | Check vault URL, verify server is running |
|
|
362
446
|
|
|
363
447
|
### Debugging
|
|
364
448
|
|
|
@@ -371,7 +455,7 @@ Alternatively, use separate environment variables:
|
|
|
371
455
|
2. Verify API key permissions:
|
|
372
456
|
|
|
373
457
|
```bash
|
|
374
|
-
axvault key list # on server
|
|
458
|
+
npx -y axvault key list # on server
|
|
375
459
|
```
|
|
376
460
|
|
|
377
461
|
3. Check credential exists:
|
package/dist/cli.js
CHANGED
|
@@ -29,10 +29,10 @@ Examples:
|
|
|
29
29
|
axvault serve --port 8080
|
|
30
30
|
|
|
31
31
|
# Create an API key with read access (at least one of --read/--write required)
|
|
32
|
-
axvault key create --name "CI Pipeline" --read "claude
|
|
32
|
+
axvault key create --name "CI Pipeline" --read "claude.work,codex.ci"
|
|
33
33
|
|
|
34
34
|
# Create a write-only API key
|
|
35
|
-
axvault key create --name "Uploader" --write "claude
|
|
35
|
+
axvault key create --name "Uploader" --write "claude.backups"
|
|
36
36
|
|
|
37
37
|
# Create an admin API key with full access
|
|
38
38
|
axvault key create --name "Admin" --read "*" --write "*" --grant "*"
|
|
@@ -44,7 +44,7 @@ Examples:
|
|
|
44
44
|
axvault key list | tail -n +2 | cut -f1
|
|
45
45
|
|
|
46
46
|
# Update an API key's permissions
|
|
47
|
-
axvault key update k_abc123def456 --add-read "claude
|
|
47
|
+
axvault key update k_abc123def456 --add-read "claude.new"
|
|
48
48
|
|
|
49
49
|
# Revoke an API key
|
|
50
50
|
axvault key revoke k_abc123def456 --force
|
|
@@ -53,7 +53,7 @@ Examples:
|
|
|
53
53
|
axvault credential list
|
|
54
54
|
|
|
55
55
|
# Delete a credential
|
|
56
|
-
axvault credential delete claude
|
|
56
|
+
axvault credential delete claude.work --force`);
|
|
57
57
|
program
|
|
58
58
|
.command("init")
|
|
59
59
|
.description("Initialize database and configuration")
|
|
@@ -79,8 +79,8 @@ keyCommand
|
|
|
79
79
|
.command("create")
|
|
80
80
|
.description("Create a new API key")
|
|
81
81
|
.requiredOption("-n, --name <name>", "Name for the API key")
|
|
82
|
-
.option("-r, --read <access>", "Comma-separated read access list (e.g., 'claude
|
|
83
|
-
.option("-w, --write <access>", "Comma-separated write access list (e.g., 'claude
|
|
82
|
+
.option("-r, --read <access>", "Comma-separated read access list (e.g., 'claude.work,codex.ci' or '*')")
|
|
83
|
+
.option("-w, --write <access>", "Comma-separated write access list (e.g., 'claude.ci' or '*')")
|
|
84
84
|
.option("-g, --grant <access>", "Comma-separated grant access list (can delegate these to other keys)")
|
|
85
85
|
.option("--json", "Output as JSON")
|
|
86
86
|
.option("--db-path <path>", "Database file path")
|
|
@@ -128,7 +128,7 @@ credentialCommand
|
|
|
128
128
|
credentialCommand
|
|
129
129
|
.command("delete")
|
|
130
130
|
.description("Delete a credential")
|
|
131
|
-
.argument("<
|
|
131
|
+
.argument("<name>", "Credential name (e.g., claude.work)")
|
|
132
132
|
.option("-f, --force", "Confirm destructive action")
|
|
133
133
|
.option("-y, --yes", "Alias for --force")
|
|
134
134
|
.option("-v, --verbose", "Enable verbose output")
|
|
@@ -12,5 +12,5 @@ interface CredentialDeleteOptions {
|
|
|
12
12
|
verbose?: boolean;
|
|
13
13
|
}
|
|
14
14
|
export declare function handleCredentialList(options: CredentialListOptions): void;
|
|
15
|
-
export declare function handleCredentialDelete(
|
|
15
|
+
export declare function handleCredentialDelete(credentialName: string, options: CredentialDeleteOptions): void;
|
|
16
16
|
export {};
|
|
@@ -5,50 +5,46 @@ import { getServerConfig } from "../config.js";
|
|
|
5
5
|
import { closeDatabase, getDatabase } from "../db/client.js";
|
|
6
6
|
import { runMigrations } from "../db/migrations.js";
|
|
7
7
|
import { deleteCredential, listCredentials, } from "../db/repositories/credentials.js";
|
|
8
|
-
import { containsControlChars,
|
|
9
|
-
|
|
8
|
+
import { containsControlChars, formatRelativeTime, getErrorMessage, sanitizeForTsv, } from "../lib/format.js";
|
|
9
|
+
import { CREDENTIAL_NAME_FORMAT_DESCRIPTION, isValidCredentialName, } from "../lib/credential-name.js";
|
|
10
|
+
/** Print credentials as TSV table (opaque schema - no type/expires columns) */
|
|
10
11
|
function printCredentialTable(credentials) {
|
|
11
|
-
console.log("
|
|
12
|
+
console.log("NAME\tCREATED\tUPDATED");
|
|
12
13
|
for (const cred of credentials) {
|
|
13
|
-
const agent = sanitizeForTsv(cred.agent);
|
|
14
14
|
const name = sanitizeForTsv(cred.name);
|
|
15
|
-
const
|
|
16
|
-
const expires = formatExpiresAt(cred.expiresAt);
|
|
15
|
+
const created = formatRelativeTime(cred.createdAt);
|
|
17
16
|
const updated = formatRelativeTime(cred.updatedAt);
|
|
18
|
-
console.log(`${
|
|
17
|
+
console.log(`${name}\t${created}\t${updated}`);
|
|
19
18
|
}
|
|
20
19
|
}
|
|
21
20
|
/**
|
|
22
|
-
* Parse credential
|
|
21
|
+
* Parse and validate credential name.
|
|
23
22
|
*
|
|
24
23
|
* Control characters are checked BEFORE trimming to prevent silent bypass
|
|
25
|
-
* (e.g., "\
|
|
24
|
+
* (e.g., "\nwork" would otherwise become "work" after trim).
|
|
26
25
|
*/
|
|
27
|
-
function
|
|
26
|
+
function parseCredentialName(name) {
|
|
28
27
|
// Reject inputs containing control characters BEFORE trimming
|
|
29
|
-
if (containsControlChars(
|
|
28
|
+
if (containsControlChars(name)) {
|
|
30
29
|
return {
|
|
31
30
|
ok: false,
|
|
32
|
-
message:
|
|
31
|
+
message: "Invalid credential name: contains control characters.",
|
|
33
32
|
};
|
|
34
33
|
}
|
|
35
|
-
const trimmed =
|
|
36
|
-
|
|
37
|
-
if (parts.length !== 2) {
|
|
34
|
+
const trimmed = name.trim();
|
|
35
|
+
if (!trimmed) {
|
|
38
36
|
return {
|
|
39
37
|
ok: false,
|
|
40
|
-
message:
|
|
38
|
+
message: "Invalid credential name: name is required.",
|
|
41
39
|
};
|
|
42
40
|
}
|
|
43
|
-
|
|
44
|
-
const name = parts[1]?.trim();
|
|
45
|
-
if (!agent || !name) {
|
|
41
|
+
if (!isValidCredentialName(trimmed)) {
|
|
46
42
|
return {
|
|
47
43
|
ok: false,
|
|
48
|
-
message: `Invalid credential
|
|
44
|
+
message: `Invalid credential name format. Must be ${CREDENTIAL_NAME_FORMAT_DESCRIPTION}.`,
|
|
49
45
|
};
|
|
50
46
|
}
|
|
51
|
-
return { ok: true,
|
|
47
|
+
return { ok: true, name: trimmed };
|
|
52
48
|
}
|
|
53
49
|
export function handleCredentialList(options) {
|
|
54
50
|
try {
|
|
@@ -57,19 +53,17 @@ export function handleCredentialList(options) {
|
|
|
57
53
|
runMigrations(database);
|
|
58
54
|
const credentials = listCredentials(database);
|
|
59
55
|
if (options.json) {
|
|
56
|
+
// Opaque schema: type/provider/expiresAt not available without decrypting
|
|
60
57
|
const output = credentials.map((cred) => ({
|
|
61
|
-
agent: cred.agent,
|
|
62
58
|
name: cred.name,
|
|
63
|
-
type: cred.type,
|
|
64
59
|
createdAt: cred.createdAt.toISOString(),
|
|
65
60
|
updatedAt: cred.updatedAt.toISOString(),
|
|
66
|
-
expiresAt: formatDateForJson(cred.expiresAt),
|
|
67
61
|
}));
|
|
68
62
|
console.log(JSON.stringify(output, undefined, 2));
|
|
69
63
|
}
|
|
70
64
|
else if (credentials.length === 0) {
|
|
71
65
|
console.error("No credentials stored.");
|
|
72
|
-
console.error("Store credentials via the API: PUT /api/v1/credentials/:
|
|
66
|
+
console.error("Store credentials via the API: PUT /api/v1/credentials/:name");
|
|
73
67
|
}
|
|
74
68
|
else {
|
|
75
69
|
printCredentialTable(credentials);
|
|
@@ -83,9 +77,9 @@ export function handleCredentialList(options) {
|
|
|
83
77
|
closeDatabase();
|
|
84
78
|
}
|
|
85
79
|
}
|
|
86
|
-
export function handleCredentialDelete(
|
|
87
|
-
//
|
|
88
|
-
const parsed =
|
|
80
|
+
export function handleCredentialDelete(credentialName, options) {
|
|
81
|
+
// Validate credential name first (before config parsing)
|
|
82
|
+
const parsed = parseCredentialName(credentialName);
|
|
89
83
|
if (!parsed.ok) {
|
|
90
84
|
console.error(`Error: ${parsed.message}`);
|
|
91
85
|
process.exitCode = 2;
|
|
@@ -97,19 +91,19 @@ export function handleCredentialDelete(path, options) {
|
|
|
97
91
|
process.exitCode = 2;
|
|
98
92
|
return;
|
|
99
93
|
}
|
|
100
|
-
const {
|
|
94
|
+
const { name } = parsed;
|
|
101
95
|
try {
|
|
102
96
|
const config = getServerConfig(options);
|
|
103
97
|
const database = getDatabase(config.databasePath);
|
|
104
98
|
runMigrations(database);
|
|
105
|
-
const deleted = deleteCredential(database,
|
|
99
|
+
const deleted = deleteCredential(database, name);
|
|
106
100
|
if (deleted) {
|
|
107
101
|
if (options.verbose) {
|
|
108
|
-
console.error(`Deleted credential: ${sanitizeForTsv(
|
|
102
|
+
console.error(`Deleted credential: ${sanitizeForTsv(name)}`);
|
|
109
103
|
}
|
|
110
104
|
}
|
|
111
105
|
else {
|
|
112
|
-
console.error(`Error: Credential not found: ${sanitizeForTsv(
|
|
106
|
+
console.error(`Error: Credential not found: ${sanitizeForTsv(name)}`);
|
|
113
107
|
process.exitCode = 1;
|
|
114
108
|
}
|
|
115
109
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -30,22 +30,23 @@ export function handleInit(options) {
|
|
|
30
30
|
const versionBefore = getSchemaVersion(database);
|
|
31
31
|
runMigrations(database);
|
|
32
32
|
const versionAfter = getSchemaVersion(database);
|
|
33
|
-
if (verbose)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
33
|
+
if (!verbose)
|
|
34
|
+
return;
|
|
35
|
+
if (versionBefore === 0) {
|
|
36
|
+
console.error(`Database initialized at: ${config.databasePath}`);
|
|
37
|
+
console.error(`Schema version: ${versionAfter}`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (versionBefore === versionAfter) {
|
|
41
|
+
console.error(`Database already at version ${versionAfter} (current: ${CURRENT_VERSION})`);
|
|
42
|
+
return;
|
|
44
43
|
}
|
|
44
|
+
console.error(`Database migrated from v${versionBefore} to v${versionAfter}`);
|
|
45
45
|
}
|
|
46
46
|
catch (error) {
|
|
47
47
|
const message = error instanceof Error ? error.message : String(error);
|
|
48
48
|
console.error(`Failed to initialize database: ${message}`);
|
|
49
|
+
console.error("If this is a schema version mismatch, delete the database file and re-run `axvault init`.");
|
|
49
50
|
process.exitCode = 1;
|
|
50
51
|
}
|
|
51
52
|
finally {
|
package/dist/commands/serve.js
CHANGED
|
@@ -57,6 +57,7 @@ export async function handleServe(options) {
|
|
|
57
57
|
catch (error) {
|
|
58
58
|
const message = error instanceof Error ? error.message : String(error);
|
|
59
59
|
console.error(`Failed to initialize database: ${message}`);
|
|
60
|
+
console.error(`If this is a schema version mismatch, delete '${config.databasePath}' and restart.`);
|
|
60
61
|
// eslint-disable-next-line unicorn/no-process-exit -- CLI startup failure
|
|
61
62
|
process.exit(1);
|
|
62
63
|
}
|
package/dist/db/migrations.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Database schema migrations.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Simple schema with opaque credential storage.
|
|
5
|
+
* Credentials are identified by name only - the blob contains all metadata.
|
|
5
6
|
*/
|
|
6
7
|
import type Database from "better-sqlite3";
|
|
7
|
-
declare const CURRENT_VERSION =
|
|
8
|
+
declare const CURRENT_VERSION = 1;
|
|
8
9
|
/** Run all pending migrations */
|
|
9
10
|
declare function runMigrations(database: Database.Database): void;
|
|
10
11
|
/** Get current schema version */
|