aegis-lock 2.0.0 → 2.0.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 +71 -31
- package/dist/client.d.ts +5 -6
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +187 -24
- package/dist/client.js.map +1 -1
- package/dist/crypto.d.ts +6 -2
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +101 -17
- package/dist/crypto.js.map +1 -1
- package/dist/types.d.ts +2 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +22 -0
- package/dist/types.js.map +1 -1
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
# aegis-lock
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://badge.fury.io/js/aegis-lock)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
**Database-agnostic client-side AES-256-GCM field-level encryption with Contextual Binding and Blind Indexing.**
|
|
4
7
|
|
|
5
8
|
Encrypt sensitive fields before they leave the browser or server. Decrypt after select. Zero plaintext hits the wire or your database. Works with **Supabase**, **MongoDB**, or any database via custom adapters.
|
|
6
9
|
|
|
10
|
+
> **⚠️ SECURITY WARNING:** `aegis-lock` provides the cryptographic primitives, but **you are responsible for your keys.** Never hardcode `CryptoKey` exports in your source code. Always use a secure Key Management Service (KMS) or strict, vault-backed environment variables to inject your keys at runtime.
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
* Node.js 18+ (or any environment supporting the standard Web Crypto API, such as modern browsers, Cloudflare Workers, or Vercel Edge functions).
|
|
14
|
+
|
|
7
15
|
## Install
|
|
8
16
|
|
|
9
17
|
```bash
|
|
@@ -14,26 +22,41 @@ npm install aegis-lock
|
|
|
14
22
|
|
|
15
23
|
```typescript
|
|
16
24
|
import { createClient } from "@supabase/supabase-js";
|
|
17
|
-
import { AegisClient, SupabaseAdapter, generateKey, exportKey } from "aegis-lock";
|
|
25
|
+
import { AegisClient, SupabaseAdapter, generateKey, generateBidxKey, exportKey } from "aegis-lock";
|
|
18
26
|
|
|
19
27
|
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
|
|
20
28
|
const adapter = new SupabaseAdapter(supabase);
|
|
21
29
|
|
|
30
|
+
// Generate your master encryption key and searchable blind index key
|
|
22
31
|
const key = await generateKey();
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
const bidxKey = await generateBidxKey();
|
|
33
|
+
|
|
34
|
+
// Export and save these securely! e.g., await exportKey(key)
|
|
35
|
+
|
|
36
|
+
const aegis = new AegisClient({
|
|
37
|
+
adapter,
|
|
38
|
+
primaryKeyField: "record_id", // REQUIRED: Binds ciphertext to the row to prevent tampering
|
|
39
|
+
encryptedFields: {
|
|
40
|
+
secure_fields: ["encrypted_content", "ssn", "email"]
|
|
41
|
+
},
|
|
42
|
+
bidxFields: {
|
|
43
|
+
secure_fields: ["email"] // Optional: Creates an 'email_bidx' column for searching
|
|
44
|
+
}
|
|
45
|
+
}, key, bidxKey);
|
|
28
46
|
|
|
29
|
-
// Insert — fields are auto-encrypted
|
|
47
|
+
// Insert — fields are auto-encrypted.
|
|
48
|
+
// Note: You MUST provide the primary key application-side!
|
|
30
49
|
await aegis.insert("secure_fields", {
|
|
31
|
-
record_id: "
|
|
50
|
+
record_id: "uuid-1234-5678",
|
|
51
|
+
email: "alice@example.com",
|
|
32
52
|
encrypted_content: "Top secret",
|
|
33
53
|
});
|
|
34
54
|
|
|
35
|
-
// Select —
|
|
36
|
-
const { data } = await aegis.select("secure_fields"
|
|
55
|
+
// Select — Aegis automatically hashes the email to search the 'email_bidx' column securely
|
|
56
|
+
const { data } = await aegis.select("secure_fields", {
|
|
57
|
+
column: "email",
|
|
58
|
+
value: "alice@example.com"
|
|
59
|
+
});
|
|
37
60
|
```
|
|
38
61
|
|
|
39
62
|
## Quick Start — MongoDB
|
|
@@ -41,6 +64,7 @@ const { data } = await aegis.select("secure_fields");
|
|
|
41
64
|
```typescript
|
|
42
65
|
import { MongoClient } from "mongodb";
|
|
43
66
|
import { AegisClient, MongoDBAdapter, generateKey } from "aegis-lock";
|
|
67
|
+
import { v4 as uuidv4 } from "uuid";
|
|
44
68
|
|
|
45
69
|
const mongo = new MongoClient("mongodb://localhost:27017");
|
|
46
70
|
await mongo.connect();
|
|
@@ -48,11 +72,18 @@ const adapter = new MongoDBAdapter(mongo.db("myapp"));
|
|
|
48
72
|
|
|
49
73
|
const key = await generateKey();
|
|
50
74
|
|
|
51
|
-
const aegis = new AegisClient(
|
|
52
|
-
|
|
53
|
-
|
|
75
|
+
const aegis = new AegisClient({
|
|
76
|
+
adapter,
|
|
77
|
+
primaryKeyField: "_id",
|
|
78
|
+
encryptedFields: {
|
|
79
|
+
users: ["ssn", "credit_card"]
|
|
80
|
+
}
|
|
81
|
+
}, key);
|
|
54
82
|
|
|
55
|
-
|
|
83
|
+
// Insert with a client-generated ID
|
|
84
|
+
await aegis.insert("users", { _id: uuidv4(), name: "Alice", ssn: "123-45-6789" });
|
|
85
|
+
|
|
86
|
+
// Select by an unencrypted field
|
|
56
87
|
const { data } = await aegis.select("users", { column: "name", value: "Alice" });
|
|
57
88
|
// data[0].ssn → "123-45-6789" (decrypted)
|
|
58
89
|
```
|
|
@@ -62,7 +93,7 @@ const { data } = await aegis.select("users", { column: "name", value: "Alice" })
|
|
|
62
93
|
Implement the `DatabaseAdapter` interface for any database:
|
|
63
94
|
|
|
64
95
|
```typescript
|
|
65
|
-
import { DatabaseAdapter
|
|
96
|
+
import { DatabaseAdapter } from "aegis-lock";
|
|
66
97
|
|
|
67
98
|
class MyAdapter implements DatabaseAdapter {
|
|
68
99
|
async insert(table: string, data: Record<string, unknown>) {
|
|
@@ -78,26 +109,35 @@ class MyAdapter implements DatabaseAdapter {
|
|
|
78
109
|
|
|
79
110
|
## API
|
|
80
111
|
|
|
81
|
-
### `new AegisClient(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
112
|
+
### `new AegisClient(config, key, bidxKey?)`
|
|
113
|
+
|
|
114
|
+
* `config` — `AegisConfig` object:
|
|
115
|
+
* `adapter`: any `DatabaseAdapter` (SupabaseAdapter, MongoDBAdapter, or custom)
|
|
116
|
+
* `primaryKeyField`: `string` (The ID field used for cryptographic contextual binding)
|
|
117
|
+
* `encryptedFields`: `Record<tableName, fieldName[]>`
|
|
118
|
+
* `bidxFields`: *(Optional)* `Record<tableName, fieldName[]>`
|
|
119
|
+
|
|
120
|
+
* `key` — CryptoKey from `generateKey()` or `importKey()`
|
|
121
|
+
* `bidxKey` — *(Optional)* CryptoKey from `generateBidxKey()` or `importKey()`
|
|
85
122
|
|
|
86
123
|
### Adapters
|
|
87
|
-
- `new SupabaseAdapter(supabaseClient)`
|
|
88
|
-
- `new MongoDBAdapter(mongoDb)`
|
|
89
124
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
125
|
+
* `new SupabaseAdapter(supabaseClient)`
|
|
126
|
+
* `new MongoDBAdapter(mongoDb)`
|
|
127
|
+
|
|
128
|
+
### Crypto Utilities
|
|
129
|
+
|
|
130
|
+
* `generateKey()` / `generateBidxKey()`
|
|
131
|
+
* `exportKey(key)` / `importKey(base64)`
|
|
93
132
|
|
|
94
|
-
## How It Works
|
|
133
|
+
## How It Works (Security Architecture)
|
|
95
134
|
|
|
96
|
-
|
|
97
|
-
-
|
|
98
|
-
|
|
99
|
-
-
|
|
135
|
+
* **Web Crypto API (AES-256-GCM):** Runs natively in any modern browser or edge runtime.
|
|
136
|
+
* **Field-Level IVs:** Every single encrypted field gets a unique, mathematically random Initialization Vector (IV) stored as a composite string (`iv:ciphertext`) to prevent keystream reuse attacks.
|
|
137
|
+
* **Contextual Binding (AAD):** Ciphertexts are cryptographically bound to the row's `primaryKeyField`. If an attacker copies an encrypted SSN from User A and pastes it into User B's row, the decryption will strictly fail.
|
|
138
|
+
* **Blind Indexing (HMAC-SHA256):** Because AES-GCM is non-deterministic (the same text encrypts differently every time), you cannot query standard encrypted fields. If configured via `bidxFields`, Aegis-Lock automatically generates deterministic, memory-aligned HMAC hashes. This allows you to search for exact matches (like looking up an email address) without exposing the plaintext to the database.
|
|
139
|
+
* **Database-agnostic:** Swap adapters without changing your core application code.
|
|
100
140
|
|
|
101
141
|
## License
|
|
102
142
|
|
|
103
|
-
MIT
|
|
143
|
+
MIT
|
package/dist/client.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import type { DatabaseAdapter } from "./types";
|
|
1
|
+
import type { DatabaseAdapter, AegisConfig } from "./types";
|
|
2
2
|
export declare class AegisClient {
|
|
3
3
|
private adapter;
|
|
4
4
|
private key;
|
|
5
|
+
private bidxKey?;
|
|
5
6
|
private encryptedFields;
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
private bidxFields;
|
|
8
|
+
private primaryKeyField;
|
|
9
|
+
constructor(config: AegisConfig, key: CryptoKey, bidxKey?: CryptoKey);
|
|
8
10
|
insert(table: string, data: Record<string, unknown>): Promise<{
|
|
9
11
|
data: Record<string, unknown>[] | null;
|
|
10
12
|
error: unknown;
|
|
11
13
|
}>;
|
|
12
|
-
/** Select rows, auto-decrypting configured fields. */
|
|
13
14
|
select(table: string, query?: {
|
|
14
15
|
column?: string;
|
|
15
16
|
value?: unknown;
|
|
@@ -18,7 +19,6 @@ export declare class AegisClient {
|
|
|
18
19
|
data: Record<string, unknown>[] | null;
|
|
19
20
|
error: unknown;
|
|
20
21
|
}>;
|
|
21
|
-
/** Select rows WITHOUT decrypting — exposes raw ciphertext. */
|
|
22
22
|
selectRaw(table: string, query?: {
|
|
23
23
|
column?: string;
|
|
24
24
|
value?: unknown;
|
|
@@ -27,7 +27,6 @@ export declare class AegisClient {
|
|
|
27
27
|
data: Record<string, unknown>[] | null;
|
|
28
28
|
error: unknown;
|
|
29
29
|
}>;
|
|
30
|
-
/** Access the underlying database adapter. */
|
|
31
30
|
get db(): DatabaseAdapter;
|
|
32
31
|
}
|
|
33
32
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAG5D,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,GAAG,CAAY;IACvB,OAAO,CAAC,OAAO,CAAC,CAAY;IAC5B,OAAO,CAAC,eAAe,CAA2B;IAClD,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,eAAe,CAAS;gBAGpB,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,SAAS;IAS9D,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;IAyCnD,MAAM,CACV,KAAK,EAAE,MAAM,EACb,KAAK,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;;;;IA6DxD,SAAS,CACb,KAAK,EAAE,MAAM,EACb,KAAK,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;;;;IAK9D,IAAI,EAAE,IAAI,eAAe,CAExB;CACF"}
|
package/dist/client.js
CHANGED
|
@@ -1,31 +1,59 @@
|
|
|
1
|
-
import { encrypt, decrypt } from "./crypto";
|
|
1
|
+
import { encrypt, decrypt, generateBlindIndex } from "./crypto";
|
|
2
2
|
export class AegisClient {
|
|
3
|
-
constructor
|
|
4
|
-
|
|
3
|
+
// Updated constructor to accept the optional BIDX key
|
|
4
|
+
constructor(config, key, bidxKey) {
|
|
5
|
+
this.adapter = config.adapter;
|
|
5
6
|
this.key = key;
|
|
6
|
-
this.
|
|
7
|
+
this.bidxKey = bidxKey;
|
|
8
|
+
this.encryptedFields = config.encryptedFields;
|
|
9
|
+
this.bidxFields = config.bidxFields || {};
|
|
10
|
+
this.primaryKeyField = config.primaryKeyField;
|
|
7
11
|
}
|
|
8
|
-
/** Insert a row, auto-encrypting configured fields. Returns a shared IV for the row. */
|
|
9
12
|
async insert(table, data) {
|
|
10
13
|
const fields = this.encryptedFields[table] || [];
|
|
14
|
+
const bidxFields = this.bidxFields[table] || [];
|
|
11
15
|
const row = { ...data };
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
if (fields.length > 0 || bidxFields.length > 0) {
|
|
17
|
+
const rowId = row[this.primaryKeyField];
|
|
18
|
+
if (!rowId) {
|
|
19
|
+
throw new Error(`Aegis: Cannot encrypt. Missing primary key '${this.primaryKeyField}' in payload.`);
|
|
20
|
+
}
|
|
21
|
+
const aadContext = String(rowId);
|
|
22
|
+
// 1. Handle Blind Indexing FIRST (While row data is still plaintext)
|
|
23
|
+
for (const field of bidxFields) {
|
|
24
|
+
if (row[field] != null && this.bidxKey) {
|
|
25
|
+
row[`${field}_bidx`] = await generateBlindIndex(String(row[field]), // Safe: This is still "alice@example.com"
|
|
26
|
+
this.bidxKey);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// 2. Handle Encryption SECOND (This overwrites the plaintext)
|
|
30
|
+
for (const field of fields) {
|
|
31
|
+
if (row[field] != null) {
|
|
32
|
+
const { ciphertext, iv } = await encrypt(String(row[field]), this.key, aadContext);
|
|
33
|
+
row[field] = `${iv}:${ciphertext}`;
|
|
34
|
+
}
|
|
19
35
|
}
|
|
20
|
-
}
|
|
21
|
-
if (fields.length > 0) {
|
|
22
|
-
row.iv = sharedIv;
|
|
23
36
|
}
|
|
24
37
|
return this.adapter.insert(table, row);
|
|
25
38
|
}
|
|
26
|
-
/** Select rows, auto-decrypting configured fields. */
|
|
27
39
|
async select(table, query) {
|
|
28
|
-
|
|
40
|
+
// Intercept and swap query for Blind Indexing (NEW)
|
|
41
|
+
const bidxFields = this.bidxFields[table] || [];
|
|
42
|
+
let activeQuery = query;
|
|
43
|
+
// Make sure this uses THIS.bidxKey
|
|
44
|
+
// Inside your select method in client.ts
|
|
45
|
+
if (query?.column &&
|
|
46
|
+
query?.value &&
|
|
47
|
+
bidxFields.includes(query.column) &&
|
|
48
|
+
this.bidxKey) {
|
|
49
|
+
const blindIndexValue = await generateBlindIndex(String(query.value), this.bidxKey);
|
|
50
|
+
activeQuery = {
|
|
51
|
+
...query,
|
|
52
|
+
column: `${query.column}_bidx`,
|
|
53
|
+
value: blindIndexValue, // Pass the resulting string
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const { data, error } = await this.adapter.select(table, activeQuery);
|
|
29
57
|
if (error || !data)
|
|
30
58
|
return { data, error };
|
|
31
59
|
const fields = this.encryptedFields[table] || [];
|
|
@@ -33,16 +61,19 @@ export class AegisClient {
|
|
|
33
61
|
return { data, error };
|
|
34
62
|
const decrypted = await Promise.all(data.map(async (row) => {
|
|
35
63
|
const decRow = { ...row };
|
|
36
|
-
const
|
|
37
|
-
if (!
|
|
64
|
+
const rowId = decRow[this.primaryKeyField];
|
|
65
|
+
if (!rowId)
|
|
38
66
|
return decRow;
|
|
67
|
+
const aadContext = String(rowId);
|
|
39
68
|
for (const field of fields) {
|
|
40
|
-
|
|
69
|
+
const payload = decRow[field];
|
|
70
|
+
if (typeof payload === "string" && payload.includes(":")) {
|
|
71
|
+
const [iv, ciphertext] = payload.split(":");
|
|
41
72
|
try {
|
|
42
|
-
decRow[field] = await decrypt({ ciphertext
|
|
73
|
+
decRow[field] = await decrypt({ ciphertext, iv }, this.key, aadContext);
|
|
43
74
|
}
|
|
44
75
|
catch {
|
|
45
|
-
//
|
|
76
|
+
// Tampering detected
|
|
46
77
|
}
|
|
47
78
|
}
|
|
48
79
|
}
|
|
@@ -50,13 +81,145 @@ export class AegisClient {
|
|
|
50
81
|
}));
|
|
51
82
|
return { data: decrypted, error };
|
|
52
83
|
}
|
|
53
|
-
/** Select rows WITHOUT decrypting — exposes raw ciphertext. */
|
|
54
84
|
async selectRaw(table, query) {
|
|
55
85
|
return this.adapter.select(table, query);
|
|
56
86
|
}
|
|
57
|
-
/** Access the underlying database adapter. */
|
|
58
87
|
get db() {
|
|
59
88
|
return this.adapter;
|
|
60
89
|
}
|
|
61
90
|
}
|
|
91
|
+
// export class AegisClient {
|
|
92
|
+
// private adapter: DatabaseAdapter;
|
|
93
|
+
// private key: CryptoKey;
|
|
94
|
+
// private bidxKey?: CryptoKey;
|
|
95
|
+
// private encryptedFields: Record<string, string[]>;
|
|
96
|
+
// private bidxFields: Record<string, string[]>;
|
|
97
|
+
// private primaryKeyField: string;
|
|
98
|
+
// constructor(config: AegisConfig, key: CryptoKey) {
|
|
99
|
+
// this.adapter = config.adapter;
|
|
100
|
+
// this.key = key;
|
|
101
|
+
// this.bidxKey = bidxKey;
|
|
102
|
+
// this.encryptedFields = config.encryptedFields;
|
|
103
|
+
// this.bidxFields = config.bidxFields || {};
|
|
104
|
+
// this.primaryKeyField = config.primaryKeyField;
|
|
105
|
+
// }
|
|
106
|
+
// async insert(table: string, data: Record<string, unknown>) {
|
|
107
|
+
// const fields = this.encryptedFields[table] || [];
|
|
108
|
+
// const row: Record<string, unknown> = { ...data };
|
|
109
|
+
// if (fields.length > 0) {
|
|
110
|
+
// const rowId = row[this.primaryKeyField];
|
|
111
|
+
// if (!rowId) {
|
|
112
|
+
// throw new Error(`Aegis: Cannot encrypt. Missing primary key '${this.primaryKeyField}' in payload.`);
|
|
113
|
+
// }
|
|
114
|
+
// const aadContext = String(rowId);
|
|
115
|
+
// for (const field of fields) {
|
|
116
|
+
// if (row[field] != null) {
|
|
117
|
+
// // Generates unique IV per field and binds to the row ID
|
|
118
|
+
// const { ciphertext, iv } = await encrypt(String(row[field]), this.key, aadContext);
|
|
119
|
+
// row[field] = `${iv}:${ciphertext}`;
|
|
120
|
+
// }
|
|
121
|
+
// }
|
|
122
|
+
// }
|
|
123
|
+
// return this.adapter.insert(table, row);
|
|
124
|
+
// }
|
|
125
|
+
// async select(table: string, query?: { column?: string; value?: unknown; limit?: number }) {
|
|
126
|
+
// const { data, error } = await this.adapter.select(table, query);
|
|
127
|
+
// if (error || !data) return { data, error };
|
|
128
|
+
// const fields = this.encryptedFields[table] || [];
|
|
129
|
+
// if (fields.length === 0) return { data, error };
|
|
130
|
+
// const decrypted = await Promise.all(
|
|
131
|
+
// data.map(async (row: Record<string, unknown>) => {
|
|
132
|
+
// const decRow = { ...row };
|
|
133
|
+
// const rowId = decRow[this.primaryKeyField];
|
|
134
|
+
// if (!rowId) return decRow; // Cannot decrypt without context
|
|
135
|
+
// const aadContext = String(rowId);
|
|
136
|
+
// for (const field of fields) {
|
|
137
|
+
// const payload = decRow[field];
|
|
138
|
+
// if (typeof payload === 'string' && payload.includes(':')) {
|
|
139
|
+
// const [iv, ciphertext] = payload.split(':');
|
|
140
|
+
// try {
|
|
141
|
+
// decRow[field] = await decrypt({ ciphertext, iv }, this.key, aadContext);
|
|
142
|
+
// } catch {
|
|
143
|
+
// // Tampering detected (invalid tag or swapped ciphertext). Leave as raw string.
|
|
144
|
+
// }
|
|
145
|
+
// }
|
|
146
|
+
// }
|
|
147
|
+
// return decRow;
|
|
148
|
+
// })
|
|
149
|
+
// );
|
|
150
|
+
// return { data: decrypted, error };
|
|
151
|
+
// }
|
|
152
|
+
// async selectRaw(table: string, query?: { column?: string; value?: unknown; limit?: number }) {
|
|
153
|
+
// return this.adapter.select(table, query);
|
|
154
|
+
// }
|
|
155
|
+
// get db(): DatabaseAdapter {
|
|
156
|
+
// return this.adapter;
|
|
157
|
+
// }
|
|
158
|
+
// }
|
|
159
|
+
// import type { DatabaseAdapter } from "./types";
|
|
160
|
+
// import { encrypt, decrypt } from "./crypto";
|
|
161
|
+
// export class AegisClient {
|
|
162
|
+
// private adapter: DatabaseAdapter;
|
|
163
|
+
// private key: CryptoKey;
|
|
164
|
+
// private encryptedFields: Record<string, string[]>;
|
|
165
|
+
// constructor(adapter: DatabaseAdapter, key: CryptoKey, encryptedFields: Record<string, string[]>) {
|
|
166
|
+
// this.adapter = adapter;
|
|
167
|
+
// this.key = key;
|
|
168
|
+
// this.encryptedFields = encryptedFields;
|
|
169
|
+
// }
|
|
170
|
+
// /** Insert a row, auto-encrypting configured fields. Returns a shared IV for the row. */
|
|
171
|
+
// async insert(table: string, data: Record<string, unknown>) {
|
|
172
|
+
// const fields = this.encryptedFields[table] || [];
|
|
173
|
+
// const row: Record<string, unknown> = { ...data };
|
|
174
|
+
// // Generate a single IV and reuse it for all fields in this row
|
|
175
|
+
// const ivBuffer = crypto.getRandomValues(new Uint8Array(12));
|
|
176
|
+
// const sharedIv = btoa(String.fromCharCode(...ivBuffer));
|
|
177
|
+
// for (const field of fields) {
|
|
178
|
+
// if (row[field] != null) {
|
|
179
|
+
// const { ciphertext } = await encrypt(String(row[field]), this.key, ivBuffer);
|
|
180
|
+
// row[field] = ciphertext;
|
|
181
|
+
// }
|
|
182
|
+
// }
|
|
183
|
+
// if (fields.length > 0) {
|
|
184
|
+
// row.iv = sharedIv;
|
|
185
|
+
// }
|
|
186
|
+
// return this.adapter.insert(table, row);
|
|
187
|
+
// }
|
|
188
|
+
// /** Select rows, auto-decrypting configured fields. */
|
|
189
|
+
// async select(table: string, query?: { column?: string; value?: unknown; limit?: number }) {
|
|
190
|
+
// const { data, error } = await this.adapter.select(table, query);
|
|
191
|
+
// if (error || !data) return { data, error };
|
|
192
|
+
// const fields = this.encryptedFields[table] || [];
|
|
193
|
+
// if (fields.length === 0) return { data, error };
|
|
194
|
+
// const decrypted = await Promise.all(
|
|
195
|
+
// data.map(async (row: Record<string, unknown>) => {
|
|
196
|
+
// const decRow = { ...row };
|
|
197
|
+
// const iv = row.iv as string;
|
|
198
|
+
// if (!iv) return decRow;
|
|
199
|
+
// for (const field of fields) {
|
|
200
|
+
// if (decRow[field] != null) {
|
|
201
|
+
// try {
|
|
202
|
+
// decRow[field] = await decrypt(
|
|
203
|
+
// { ciphertext: String(decRow[field]), iv },
|
|
204
|
+
// this.key
|
|
205
|
+
// );
|
|
206
|
+
// } catch {
|
|
207
|
+
// // leave as ciphertext if decryption fails
|
|
208
|
+
// }
|
|
209
|
+
// }
|
|
210
|
+
// }
|
|
211
|
+
// return decRow;
|
|
212
|
+
// })
|
|
213
|
+
// );
|
|
214
|
+
// return { data: decrypted, error };
|
|
215
|
+
// }
|
|
216
|
+
// /** Select rows WITHOUT decrypting — exposes raw ciphertext. */
|
|
217
|
+
// async selectRaw(table: string, query?: { column?: string; value?: unknown; limit?: number }) {
|
|
218
|
+
// return this.adapter.select(table, query);
|
|
219
|
+
// }
|
|
220
|
+
// /** Access the underlying database adapter. */
|
|
221
|
+
// get db(): DatabaseAdapter {
|
|
222
|
+
// return this.adapter;
|
|
223
|
+
// }
|
|
224
|
+
// }
|
|
62
225
|
//# sourceMappingURL=client.js.map
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../client.ts"],"names":[],"mappings":"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"}
|
package/dist/crypto.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ import { EncryptedPayload } from "./types";
|
|
|
2
2
|
export declare function generateKey(): Promise<CryptoKey>;
|
|
3
3
|
export declare function exportKey(key: CryptoKey): Promise<string>;
|
|
4
4
|
export declare function importKey(base64: string): Promise<CryptoKey>;
|
|
5
|
-
export declare function encrypt(plaintext: string, key: CryptoKey,
|
|
6
|
-
export declare function decrypt(payload: EncryptedPayload, key: CryptoKey): Promise<string>;
|
|
5
|
+
export declare function encrypt(plaintext: string, key: CryptoKey, aadContext: string): Promise<EncryptedPayload>;
|
|
6
|
+
export declare function decrypt(payload: EncryptedPayload, key: CryptoKey, aadContext: string): Promise<string>;
|
|
7
|
+
/** Generates a dedicated key for deterministic Blind Indexing */
|
|
8
|
+
export declare function generateBidxKey(): Promise<CryptoKey>;
|
|
9
|
+
/** Generates a deterministic hash for database searching */
|
|
10
|
+
export declare function generateBlindIndex(plaintext: string, key: CryptoKey): Promise<string>;
|
|
7
11
|
//# sourceMappingURL=crypto.d.ts.map
|
package/dist/crypto.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AA0B3C,wBAAsB,WAAW,IAAI,OAAO,CAAC,SAAS,CAAC,CAKtD;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAG/D;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CASlE;AAED,wBAAsB,OAAO,CAC3B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,SAAS,EACd,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,gBAAgB,CAAC,CAsB3B;AAED,wBAAsB,OAAO,CAC3B,OAAO,EAAE,gBAAgB,EACzB,GAAG,EAAE,SAAS,EACd,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAsBjB;AAID,iEAAiE;AACjE,wBAAsB,eAAe,IAAI,OAAO,CAAC,SAAS,CAAC,CAK1D;AAED,4DAA4D;AAC5D,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,SAAS,GACb,OAAO,CAAC,MAAM,CAAC,CAkBjB"}
|
package/dist/crypto.js
CHANGED
|
@@ -1,32 +1,116 @@
|
|
|
1
1
|
const ALGO = "AES-GCM";
|
|
2
2
|
const KEY_LENGTH = 256;
|
|
3
|
+
// Memory-safe Base64 conversion helpers
|
|
4
|
+
function bufferToBase64(buffer) {
|
|
5
|
+
let binary = "";
|
|
6
|
+
// Normalize to Uint8Array safely
|
|
7
|
+
const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
|
|
8
|
+
const len = bytes.byteLength;
|
|
9
|
+
for (let i = 0; i < len; i++) {
|
|
10
|
+
binary += String.fromCharCode(bytes[i]);
|
|
11
|
+
}
|
|
12
|
+
return btoa(binary);
|
|
13
|
+
}
|
|
14
|
+
function base64ToBuffer(base64) {
|
|
15
|
+
const binaryString = atob(base64);
|
|
16
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
17
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
18
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
19
|
+
}
|
|
20
|
+
return bytes;
|
|
21
|
+
}
|
|
3
22
|
export async function generateKey() {
|
|
4
|
-
return crypto.subtle.generateKey({ name: ALGO, length: KEY_LENGTH }, true, [
|
|
23
|
+
return crypto.subtle.generateKey({ name: ALGO, length: KEY_LENGTH }, true, [
|
|
24
|
+
"encrypt",
|
|
25
|
+
"decrypt",
|
|
26
|
+
]);
|
|
5
27
|
}
|
|
6
28
|
export async function exportKey(key) {
|
|
7
29
|
const raw = await crypto.subtle.exportKey("raw", key);
|
|
8
|
-
return
|
|
30
|
+
return bufferToBase64(raw);
|
|
9
31
|
}
|
|
10
32
|
export async function importKey(base64) {
|
|
11
|
-
const raw =
|
|
12
|
-
return crypto.subtle.importKey("raw", raw, { name: ALGO, length: KEY_LENGTH }, true, [
|
|
13
|
-
"encrypt",
|
|
14
|
-
"decrypt",
|
|
15
|
-
]);
|
|
33
|
+
const raw = base64ToBuffer(base64);
|
|
34
|
+
return crypto.subtle.importKey("raw", raw, { name: ALGO, length: KEY_LENGTH }, true, ["encrypt", "decrypt"]);
|
|
16
35
|
}
|
|
17
|
-
export async function encrypt(plaintext, key,
|
|
18
|
-
const iv =
|
|
36
|
+
export async function encrypt(plaintext, key, aadContext) {
|
|
37
|
+
const iv = crypto.getRandomValues(new Uint8Array(12)); // ALWAYS unique
|
|
19
38
|
const encoded = new TextEncoder().encode(plaintext);
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
iv: btoa(String.fromCharCode(...iv)),
|
|
39
|
+
const algorithm = {
|
|
40
|
+
name: ALGO,
|
|
41
|
+
iv: iv,
|
|
24
42
|
};
|
|
43
|
+
if (aadContext) {
|
|
44
|
+
algorithm.additionalData = new TextEncoder().encode(aadContext);
|
|
45
|
+
}
|
|
46
|
+
const cipherBuffer = await crypto.subtle.encrypt(algorithm, key, encoded);
|
|
47
|
+
return { ciphertext: bufferToBase64(cipherBuffer), iv: bufferToBase64(iv) };
|
|
25
48
|
}
|
|
26
|
-
export async function decrypt(payload, key) {
|
|
27
|
-
const cipherBytes =
|
|
28
|
-
const iv =
|
|
29
|
-
const
|
|
49
|
+
export async function decrypt(payload, key, aadContext) {
|
|
50
|
+
const cipherBytes = base64ToBuffer(payload.ciphertext);
|
|
51
|
+
const iv = base64ToBuffer(payload.iv);
|
|
52
|
+
const algorithm = {
|
|
53
|
+
name: ALGO,
|
|
54
|
+
iv: iv,
|
|
55
|
+
};
|
|
56
|
+
if (aadContext) {
|
|
57
|
+
algorithm.additionalData = new TextEncoder().encode(aadContext);
|
|
58
|
+
}
|
|
59
|
+
const decrypted = await crypto.subtle.decrypt(algorithm, key, cipherBytes);
|
|
30
60
|
return new TextDecoder().decode(decrypted);
|
|
31
61
|
}
|
|
62
|
+
const HMAC_ALGO = "HMAC";
|
|
63
|
+
/** Generates a dedicated key for deterministic Blind Indexing */
|
|
64
|
+
export async function generateBidxKey() {
|
|
65
|
+
return crypto.subtle.generateKey({ name: HMAC_ALGO, hash: "SHA-256" }, true, [
|
|
66
|
+
"sign",
|
|
67
|
+
"verify",
|
|
68
|
+
]);
|
|
69
|
+
}
|
|
70
|
+
/** Generates a deterministic hash for database searching */
|
|
71
|
+
export async function generateBlindIndex(plaintext, key) {
|
|
72
|
+
const encoder = new TextEncoder();
|
|
73
|
+
const encoded = encoder.encode(plaintext);
|
|
74
|
+
// PRODUCTION FIX: Isolated buffer extraction.
|
|
75
|
+
// This ensures the HMAC signature is deterministic regardless of memory alignment.
|
|
76
|
+
const cleanBuffer = encoded.buffer.slice(encoded.byteOffset, encoded.byteOffset + encoded.byteLength);
|
|
77
|
+
const signature = await crypto.subtle.sign("HMAC", key, cleanBuffer);
|
|
78
|
+
return bufferToBase64(signature);
|
|
79
|
+
}
|
|
80
|
+
// import { EncryptedPayload } from "./types";
|
|
81
|
+
// const ALGO = "AES-GCM";
|
|
82
|
+
// const KEY_LENGTH = 256;
|
|
83
|
+
// export async function generateKey(): Promise<CryptoKey> {
|
|
84
|
+
// return crypto.subtle.generateKey(
|
|
85
|
+
// { name: ALGO, length: KEY_LENGTH },
|
|
86
|
+
// true,
|
|
87
|
+
// ["encrypt", "decrypt"]
|
|
88
|
+
// );
|
|
89
|
+
// }
|
|
90
|
+
// export async function exportKey(key: CryptoKey): Promise<string> {
|
|
91
|
+
// const raw = await crypto.subtle.exportKey("raw", key);
|
|
92
|
+
// return btoa(String.fromCharCode(...new Uint8Array(raw)));
|
|
93
|
+
// }
|
|
94
|
+
// export async function importKey(base64: string): Promise<CryptoKey> {
|
|
95
|
+
// const raw = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
|
|
96
|
+
// return crypto.subtle.importKey("raw", raw, { name: ALGO, length: KEY_LENGTH }, true, [
|
|
97
|
+
// "encrypt",
|
|
98
|
+
// "decrypt",
|
|
99
|
+
// ]);
|
|
100
|
+
// }
|
|
101
|
+
// export async function encrypt(plaintext: string, key: CryptoKey, existingIv?: Uint8Array): Promise<EncryptedPayload> {
|
|
102
|
+
// const iv = existingIv ? new Uint8Array(existingIv) : crypto.getRandomValues(new Uint8Array(12));
|
|
103
|
+
// const encoded = new TextEncoder().encode(plaintext);
|
|
104
|
+
// const cipherBuffer = await crypto.subtle.encrypt({ name: ALGO, iv }, key, encoded);
|
|
105
|
+
// return {
|
|
106
|
+
// ciphertext: btoa(String.fromCharCode(...new Uint8Array(cipherBuffer))),
|
|
107
|
+
// iv: btoa(String.fromCharCode(...iv)),
|
|
108
|
+
// };
|
|
109
|
+
// }
|
|
110
|
+
// export async function decrypt(payload: EncryptedPayload, key: CryptoKey): Promise<string> {
|
|
111
|
+
// const cipherBytes = Uint8Array.from(atob(payload.ciphertext), (c) => c.charCodeAt(0));
|
|
112
|
+
// const iv = Uint8Array.from(atob(payload.iv), (c) => c.charCodeAt(0));
|
|
113
|
+
// const decrypted = await crypto.subtle.decrypt({ name: ALGO, iv }, key, cipherBytes);
|
|
114
|
+
// return new TextDecoder().decode(decrypted);
|
|
115
|
+
// }
|
|
32
116
|
//# sourceMappingURL=crypto.js.map
|
package/dist/crypto.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../crypto.ts"],"names":[],"mappings":"AAEA,MAAM,IAAI,GAAG,SAAS,CAAC;AACvB,MAAM,UAAU,GAAG,GAAG,CAAC;AAEvB,MAAM,CAAC,KAAK,UAAU,
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../crypto.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
/** Generic database adapter interface — implement this for any database. */
|
|
2
1
|
export interface DatabaseAdapter {
|
|
3
|
-
/** Insert a row into a table/collection. Returns the inserted data. */
|
|
4
2
|
insert(table: string, data: Record<string, unknown>): Promise<{
|
|
5
3
|
data: Record<string, unknown>[] | null;
|
|
6
4
|
error: unknown;
|
|
7
5
|
}>;
|
|
8
|
-
/** Select rows from a table/collection with optional filters. */
|
|
9
6
|
select(table: string, query?: {
|
|
10
7
|
column?: string;
|
|
11
8
|
value?: unknown;
|
|
@@ -18,6 +15,8 @@ export interface DatabaseAdapter {
|
|
|
18
15
|
export interface AegisConfig {
|
|
19
16
|
adapter: DatabaseAdapter;
|
|
20
17
|
encryptedFields: Record<string, string[]>;
|
|
18
|
+
primaryKeyField: string;
|
|
19
|
+
bidxFields?: Record<string, string[]>;
|
|
21
20
|
}
|
|
22
21
|
export interface EncryptedPayload {
|
|
23
22
|
ciphertext: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC1H,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC1J;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,eAAe,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1C,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,SAAS,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB"}
|
package/dist/types.js
CHANGED
|
@@ -1,2 +1,24 @@
|
|
|
1
1
|
export {};
|
|
2
|
+
// /** Generic database adapter interface — implement this for any database. */
|
|
3
|
+
// export interface DatabaseAdapter {
|
|
4
|
+
// /** Insert a row into a table/collection. Returns the inserted data. */
|
|
5
|
+
// insert(table: string, data: Record<string, unknown>): Promise<{ data: Record<string, unknown>[] | null; error: unknown }>;
|
|
6
|
+
// /** Select rows from a table/collection with optional filters. */
|
|
7
|
+
// select(
|
|
8
|
+
// table: string,
|
|
9
|
+
// query?: { column?: string; value?: unknown; limit?: number }
|
|
10
|
+
// ): Promise<{ data: Record<string, unknown>[] | null; error: unknown }>;
|
|
11
|
+
// }
|
|
12
|
+
// export interface AegisConfig {
|
|
13
|
+
// adapter: DatabaseAdapter;
|
|
14
|
+
// encryptedFields: Record<string, string[]>;
|
|
15
|
+
// }
|
|
16
|
+
// export interface EncryptedPayload {
|
|
17
|
+
// ciphertext: string;
|
|
18
|
+
// iv: string;
|
|
19
|
+
// }
|
|
20
|
+
// export interface AegisKeyPair {
|
|
21
|
+
// key: CryptoKey;
|
|
22
|
+
// exported: string;
|
|
23
|
+
// }
|
|
2
24
|
//# sourceMappingURL=types.js.map
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":";AAqBA,+EAA+E;AAC/E,qCAAqC;AACrC,4EAA4E;AAC5E,+HAA+H;AAE/H,sEAAsE;AACtE,YAAY;AACZ,qBAAqB;AACrB,mEAAmE;AACnE,4EAA4E;AAC5E,IAAI;AAEJ,iCAAiC;AACjC,8BAA8B;AAC9B,+CAA+C;AAC/C,IAAI;AAEJ,sCAAsC;AACtC,wBAAwB;AACxB,gBAAgB;AAChB,IAAI;AAEJ,kCAAkC;AAClC,oBAAoB;AACpB,sBAAsB;AACtB,IAAI"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aegis-lock",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Database-agnostic client-side AES-256-GCM field-level encryption. Works with Supabase, MongoDB, or any database via pluggable adapters.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
],
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "tsc -p tsconfig.build.json",
|
|
29
|
-
"prepublishOnly": "npm run build"
|
|
29
|
+
"prepublishOnly": "npm run build",
|
|
30
|
+
"test": "jest"
|
|
30
31
|
},
|
|
31
32
|
"keywords": [
|
|
32
33
|
"encryption",
|
|
@@ -53,6 +54,10 @@
|
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
55
56
|
"@supabase/supabase-js": "^2.0.0",
|
|
57
|
+
"@types/jest": "^30.0.0",
|
|
58
|
+
"@types/node": "^25.3.5",
|
|
59
|
+
"jest": "^30.2.0",
|
|
60
|
+
"ts-jest": "^29.4.6",
|
|
56
61
|
"typescript": "^5.9.3"
|
|
57
62
|
}
|
|
58
63
|
}
|