axvault 1.3.0 → 1.4.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/commands/key-list.js +3 -1
- package/dist/db/migrations.d.ts +1 -1
- package/dist/db/migrations.js +18 -1
- package/dist/db/repositories/api-key-utilities.d.ts +26 -0
- package/dist/db/repositories/api-key-utilities.js +43 -0
- package/dist/db/repositories/api-keys.d.ts +3 -7
- package/dist/db/repositories/api-keys.js +12 -27
- package/dist/db/types.d.ts +1 -0
- package/dist/lib/format.d.ts +1 -0
- package/dist/lib/format.js +11 -1
- package/package.json +1 -1
|
@@ -16,6 +16,8 @@ export function handleKeyList(options) {
|
|
|
16
16
|
const output = keys.map((key) => ({
|
|
17
17
|
id: key.id,
|
|
18
18
|
name: key.name,
|
|
19
|
+
// eslint-disable-next-line unicorn/no-null -- JSON requires null for missing values
|
|
20
|
+
keyPrefix: key.keyPrefix ?? null,
|
|
19
21
|
readAccess: key.readAccess,
|
|
20
22
|
writeAccess: key.writeAccess,
|
|
21
23
|
grantAccess: key.grantAccess,
|
|
@@ -28,7 +30,7 @@ export function handleKeyList(options) {
|
|
|
28
30
|
console.error("No API keys found.\nCreate one with: axvault key create --name <name> [--read <access>] [--write <access>] [--grant <access>]");
|
|
29
31
|
}
|
|
30
32
|
else {
|
|
31
|
-
console.log("ID\tNAME\tREAD ACCESS\tWRITE ACCESS\tGRANT ACCESS\tLAST USED");
|
|
33
|
+
console.log("ID\tNAME\tKEY\tREAD ACCESS\tWRITE ACCESS\tGRANT ACCESS\tLAST USED");
|
|
32
34
|
for (const key of keys)
|
|
33
35
|
console.log(formatKeyRow(key));
|
|
34
36
|
}
|
package/dist/db/migrations.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Uses a simple version-based migration system.
|
|
5
5
|
*/
|
|
6
6
|
import type Database from "better-sqlite3";
|
|
7
|
-
declare const CURRENT_VERSION =
|
|
7
|
+
declare const CURRENT_VERSION = 6;
|
|
8
8
|
/** Run all pending migrations */
|
|
9
9
|
declare function runMigrations(database: Database.Database): void;
|
|
10
10
|
/** Get current schema version */
|
package/dist/db/migrations.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Uses a simple version-based migration system.
|
|
5
5
|
*/
|
|
6
|
-
const CURRENT_VERSION =
|
|
6
|
+
const CURRENT_VERSION = 6;
|
|
7
7
|
/** Run all pending migrations */
|
|
8
8
|
function runMigrations(database) {
|
|
9
9
|
const version = getSchemaVersion(database);
|
|
@@ -22,6 +22,9 @@ function runMigrations(database) {
|
|
|
22
22
|
if (version < 5) {
|
|
23
23
|
migrateToV5(database);
|
|
24
24
|
}
|
|
25
|
+
if (version < 6) {
|
|
26
|
+
migrateToV6(database);
|
|
27
|
+
}
|
|
25
28
|
}
|
|
26
29
|
/** Get current schema version */
|
|
27
30
|
function getSchemaVersion(database) {
|
|
@@ -148,4 +151,18 @@ function migrateToV5(database) {
|
|
|
148
151
|
setSchemaVersion(database, 5);
|
|
149
152
|
})();
|
|
150
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Migration to version 6: Add key_prefix column to api_keys
|
|
156
|
+
*
|
|
157
|
+
* Stores first 8 characters of the key secret for identification.
|
|
158
|
+
* Existing keys will have NULL prefix (key value not recoverable).
|
|
159
|
+
*/
|
|
160
|
+
function migrateToV6(database) {
|
|
161
|
+
database.transaction(() => {
|
|
162
|
+
database.exec(`
|
|
163
|
+
ALTER TABLE api_keys ADD COLUMN key_prefix TEXT
|
|
164
|
+
`);
|
|
165
|
+
setSchemaVersion(database, 6);
|
|
166
|
+
})();
|
|
167
|
+
}
|
|
151
168
|
export { CURRENT_VERSION, getSchemaVersion, runMigrations };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API key utility functions.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions for key generation and hashing.
|
|
5
|
+
*/
|
|
6
|
+
/** Generate a new API key ID */
|
|
7
|
+
declare function generateKeyId(): string;
|
|
8
|
+
/** Generate a new API key secret */
|
|
9
|
+
declare function generateKeySecret(): string;
|
|
10
|
+
/** Hash an API key for storage */
|
|
11
|
+
declare function hashApiKey(key: string): string;
|
|
12
|
+
/** Extract key prefix for display (axv_sk_ + first 8 hex chars of secret) */
|
|
13
|
+
declare function extractKeyPrefix(key: string): string;
|
|
14
|
+
/** Minimal interface for access checking */
|
|
15
|
+
interface AccessLists {
|
|
16
|
+
readAccess: string[];
|
|
17
|
+
writeAccess: string[];
|
|
18
|
+
grantAccess: string[];
|
|
19
|
+
}
|
|
20
|
+
/** Check if API key has read access to a credential */
|
|
21
|
+
declare function hasReadAccess(apiKey: AccessLists, agent: string, name: string): boolean;
|
|
22
|
+
/** Check if API key has write access to a credential */
|
|
23
|
+
declare function hasWriteAccess(apiKey: AccessLists, agent: string, name: string): boolean;
|
|
24
|
+
/** Check if API key has grant access to a credential */
|
|
25
|
+
declare function hasGrantAccess(apiKey: AccessLists, agent: string, name: string): boolean;
|
|
26
|
+
export { extractKeyPrefix, generateKeyId, generateKeySecret, hashApiKey, hasGrantAccess, hasReadAccess, hasWriteAccess, };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API key utility functions.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions for key generation and hashing.
|
|
5
|
+
*/
|
|
6
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
7
|
+
/** Key prefix for identification (e.g., "axv_sk_01234567") */
|
|
8
|
+
const KEY_PREFIX_LENGTH = 8;
|
|
9
|
+
/** Generate a new API key ID */
|
|
10
|
+
function generateKeyId() {
|
|
11
|
+
return `k_${randomBytes(6).toString("hex")}`;
|
|
12
|
+
}
|
|
13
|
+
/** Generate a new API key secret */
|
|
14
|
+
function generateKeySecret() {
|
|
15
|
+
return `axv_sk_${randomBytes(16).toString("hex")}`;
|
|
16
|
+
}
|
|
17
|
+
/** Hash an API key for storage */
|
|
18
|
+
function hashApiKey(key) {
|
|
19
|
+
return createHash("sha256").update(key).digest("hex");
|
|
20
|
+
}
|
|
21
|
+
/** Extract key prefix for display (axv_sk_ + first 8 hex chars of secret) */
|
|
22
|
+
function extractKeyPrefix(key) {
|
|
23
|
+
// Key format: axv_sk_ + 32 hex chars
|
|
24
|
+
return key.slice(0, 7 + KEY_PREFIX_LENGTH); // "axv_sk_" (7) + 8 chars
|
|
25
|
+
}
|
|
26
|
+
/** Check if access list includes the given credential path */
|
|
27
|
+
function hasAccess(accessList, agent, name) {
|
|
28
|
+
const path = `${agent}/${name}`;
|
|
29
|
+
return accessList.includes("*") || accessList.includes(path);
|
|
30
|
+
}
|
|
31
|
+
/** Check if API key has read access to a credential */
|
|
32
|
+
function hasReadAccess(apiKey, agent, name) {
|
|
33
|
+
return hasAccess(apiKey.readAccess, agent, name);
|
|
34
|
+
}
|
|
35
|
+
/** Check if API key has write access to a credential */
|
|
36
|
+
function hasWriteAccess(apiKey, agent, name) {
|
|
37
|
+
return hasAccess(apiKey.writeAccess, agent, name);
|
|
38
|
+
}
|
|
39
|
+
/** Check if API key has grant access to a credential */
|
|
40
|
+
function hasGrantAccess(apiKey, agent, name) {
|
|
41
|
+
return hasAccess(apiKey.grantAccess, agent, name);
|
|
42
|
+
}
|
|
43
|
+
export { extractKeyPrefix, generateKeyId, generateKeySecret, hashApiKey, hasGrantAccess, hasReadAccess, hasWriteAccess, };
|
|
@@ -9,6 +9,7 @@ interface ApiKeyRecord {
|
|
|
9
9
|
id: string;
|
|
10
10
|
name: string;
|
|
11
11
|
keyHash: string;
|
|
12
|
+
keyPrefix: string | undefined;
|
|
12
13
|
readAccess: string[];
|
|
13
14
|
writeAccess: string[];
|
|
14
15
|
grantAccess: string[];
|
|
@@ -36,17 +37,12 @@ declare function listApiKeys(database: Database.Database): ApiKeyRecord[];
|
|
|
36
37
|
declare function updateLastUsed(database: Database.Database, id: string): void;
|
|
37
38
|
/** Delete an API key */
|
|
38
39
|
declare function deleteApiKey(database: Database.Database, id: string): boolean;
|
|
39
|
-
/** Check if API key has read access to a credential */
|
|
40
|
-
declare function hasReadAccess(apiKey: ApiKeyRecord, agent: string, name: string): boolean;
|
|
41
|
-
/** Check if API key has write access to a credential */
|
|
42
|
-
declare function hasWriteAccess(apiKey: ApiKeyRecord, agent: string, name: string): boolean;
|
|
43
|
-
/** Check if API key has grant access to a credential */
|
|
44
|
-
declare function hasGrantAccess(apiKey: ApiKeyRecord, agent: string, name: string): boolean;
|
|
45
40
|
/** Update an API key's access permissions */
|
|
46
41
|
declare function updateApiKeyAccess(database: Database.Database, id: string, options: {
|
|
47
42
|
readAccess?: string[];
|
|
48
43
|
writeAccess?: string[];
|
|
49
44
|
grantAccess?: string[];
|
|
50
45
|
}): boolean;
|
|
51
|
-
export { createApiKey, deleteApiKey, findApiKeyById, findApiKeyByKey,
|
|
46
|
+
export { createApiKey, deleteApiKey, findApiKeyById, findApiKeyByKey, listApiKeys, updateApiKeyAccess, updateLastUsed, };
|
|
47
|
+
export { hasGrantAccess, hasReadAccess, hasWriteAccess, } from "./api-key-utilities.js";
|
|
52
48
|
export type { ApiKeyRecord, ApiKeyWithSecret };
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Manages API keys with read/write access lists for credentials.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { extractKeyPrefix, generateKeyId, generateKeySecret, hashApiKey, } from "./api-key-utilities.js";
|
|
7
7
|
/** Convert database row to record */
|
|
8
8
|
function rowToRecord(row) {
|
|
9
9
|
return {
|
|
10
10
|
id: row.id,
|
|
11
11
|
name: row.name,
|
|
12
12
|
keyHash: row.key_hash,
|
|
13
|
+
keyPrefix: row.key_prefix ?? undefined,
|
|
13
14
|
readAccess: JSON.parse(row.read_access),
|
|
14
15
|
writeAccess: JSON.parse(row.write_access),
|
|
15
16
|
grantAccess: JSON.parse(row.grant_access),
|
|
@@ -17,26 +18,24 @@ function rowToRecord(row) {
|
|
|
17
18
|
lastUsedAt: row.last_used_at ? new Date(row.last_used_at) : undefined,
|
|
18
19
|
};
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
-
function hashApiKey(key) {
|
|
22
|
-
return createHash("sha256").update(key).digest("hex");
|
|
23
|
-
}
|
|
24
|
-
const SELECT_COLUMNS = `id, name, key_hash, read_access, write_access, grant_access, created_at, last_used_at`;
|
|
21
|
+
const SELECT_COLUMNS = `id, name, key_hash, key_prefix, read_access, write_access, grant_access, created_at, last_used_at`;
|
|
25
22
|
/** Create a new API key */
|
|
26
23
|
function createApiKey(database, options) {
|
|
27
|
-
const id =
|
|
28
|
-
const key =
|
|
24
|
+
const id = generateKeyId();
|
|
25
|
+
const key = generateKeySecret();
|
|
29
26
|
const keyHash = hashApiKey(key);
|
|
27
|
+
const keyPrefix = extractKeyPrefix(key);
|
|
30
28
|
const now = Date.now();
|
|
31
29
|
database
|
|
32
|
-
.prepare(`INSERT INTO api_keys (id, name, key_hash, read_access, write_access, grant_access, created_at)
|
|
33
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`)
|
|
34
|
-
.run(id, options.name, keyHash, JSON.stringify(options.readAccess), JSON.stringify(options.writeAccess), JSON.stringify(options.grantAccess), now);
|
|
30
|
+
.prepare(`INSERT INTO api_keys (id, name, key_hash, key_prefix, read_access, write_access, grant_access, created_at)
|
|
31
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
32
|
+
.run(id, options.name, keyHash, keyPrefix, JSON.stringify(options.readAccess), JSON.stringify(options.writeAccess), JSON.stringify(options.grantAccess), now);
|
|
35
33
|
return {
|
|
36
34
|
id,
|
|
37
35
|
name: options.name,
|
|
38
36
|
key,
|
|
39
37
|
keyHash,
|
|
38
|
+
keyPrefix,
|
|
40
39
|
readAccess: options.readAccess,
|
|
41
40
|
writeAccess: options.writeAccess,
|
|
42
41
|
grantAccess: options.grantAccess,
|
|
@@ -75,21 +74,6 @@ function updateLastUsed(database, id) {
|
|
|
75
74
|
function deleteApiKey(database, id) {
|
|
76
75
|
return (database.prepare(`DELETE FROM api_keys WHERE id = ?`).run(id).changes > 0);
|
|
77
76
|
}
|
|
78
|
-
/** Check if API key has read access to a credential */
|
|
79
|
-
function hasReadAccess(apiKey, agent, name) {
|
|
80
|
-
const path = `${agent}/${name}`;
|
|
81
|
-
return apiKey.readAccess.includes("*") || apiKey.readAccess.includes(path);
|
|
82
|
-
}
|
|
83
|
-
/** Check if API key has write access to a credential */
|
|
84
|
-
function hasWriteAccess(apiKey, agent, name) {
|
|
85
|
-
const path = `${agent}/${name}`;
|
|
86
|
-
return apiKey.writeAccess.includes("*") || apiKey.writeAccess.includes(path);
|
|
87
|
-
}
|
|
88
|
-
/** Check if API key has grant access to a credential */
|
|
89
|
-
function hasGrantAccess(apiKey, agent, name) {
|
|
90
|
-
const path = `${agent}/${name}`;
|
|
91
|
-
return apiKey.grantAccess.includes("*") || apiKey.grantAccess.includes(path);
|
|
92
|
-
}
|
|
93
77
|
/** Update an API key's access permissions */
|
|
94
78
|
function updateApiKeyAccess(database, id, options) {
|
|
95
79
|
const updates = [];
|
|
@@ -112,4 +96,5 @@ function updateApiKeyAccess(database, id, options) {
|
|
|
112
96
|
const sql = `UPDATE api_keys SET ${updates.join(", ")} WHERE id = ?`;
|
|
113
97
|
return database.prepare(sql).run(...values).changes > 0;
|
|
114
98
|
}
|
|
115
|
-
export { createApiKey, deleteApiKey, findApiKeyById, findApiKeyByKey,
|
|
99
|
+
export { createApiKey, deleteApiKey, findApiKeyById, findApiKeyByKey, listApiKeys, updateApiKeyAccess, updateLastUsed, };
|
|
100
|
+
export { hasGrantAccess, hasReadAccess, hasWriteAccess, } from "./api-key-utilities.js";
|
package/dist/db/types.d.ts
CHANGED
package/dist/lib/format.d.ts
CHANGED
package/dist/lib/format.js
CHANGED
|
@@ -66,17 +66,27 @@ const KEY_ID_PATTERN = /^k_[0-9a-f]{12}$/u;
|
|
|
66
66
|
export function isValidKeyId(id) {
|
|
67
67
|
return KEY_ID_PATTERN.test(id);
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Format key prefix for display with redaction suffix.
|
|
71
|
+
* Returns "(unavailable)" for keys created before prefix storage.
|
|
72
|
+
*/
|
|
73
|
+
function formatKeyPrefix(prefix) {
|
|
74
|
+
if (!prefix)
|
|
75
|
+
return "(unavailable)";
|
|
76
|
+
return `${prefix}...`;
|
|
77
|
+
}
|
|
69
78
|
/**
|
|
70
79
|
* Format a single API key row for TSV output.
|
|
71
80
|
*/
|
|
72
81
|
export function formatKeyRow(key) {
|
|
73
82
|
const id = sanitizeForTsv(key.id);
|
|
74
83
|
const name = sanitizeForTsv(key.name);
|
|
84
|
+
const keyPrefix = sanitizeForTsv(formatKeyPrefix(key.keyPrefix));
|
|
75
85
|
const readAccess = sanitizeForTsv(formatAccessList(key.readAccess));
|
|
76
86
|
const writeAccess = sanitizeForTsv(formatAccessList(key.writeAccess));
|
|
77
87
|
const grantAccess = sanitizeForTsv(formatAccessList(key.grantAccess));
|
|
78
88
|
const lastUsed = formatRelativeTime(key.lastUsedAt);
|
|
79
|
-
return `${id}\t${name}\t${readAccess}\t${writeAccess}\t${grantAccess}\t${lastUsed}`;
|
|
89
|
+
return `${id}\t${name}\t${keyPrefix}\t${readAccess}\t${writeAccess}\t${grantAccess}\t${lastUsed}`;
|
|
80
90
|
}
|
|
81
91
|
/**
|
|
82
92
|
* Validate access list entry format.
|