@djangocfg/crypto 2.1.236 → 2.1.238

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/react.cjs ADDED
@@ -0,0 +1,300 @@
1
+ "use client";
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+
22
+ // src/react/index.ts
23
+ var react_exports = {};
24
+ __export(react_exports, {
25
+ useDecrypt: () => useDecrypt,
26
+ useDecryptionClient: () => useDecryptionClient,
27
+ useIsEncrypted: () => useIsEncrypted,
28
+ useLazyDecrypt: () => useLazyDecrypt
29
+ });
30
+ module.exports = __toCommonJS(react_exports);
31
+
32
+ // src/react/hooks.ts
33
+ var import_react = require("react");
34
+
35
+ // src/key-derivation.ts
36
+ async function deriveKey(password, salt, iterations = 1e5, keyLength = 32) {
37
+ const encoder = new TextEncoder();
38
+ const passwordBuffer = encoder.encode(password);
39
+ const keyMaterial = await crypto.subtle.importKey(
40
+ "raw",
41
+ passwordBuffer,
42
+ "PBKDF2",
43
+ false,
44
+ ["deriveBits", "deriveKey"]
45
+ );
46
+ return crypto.subtle.deriveKey(
47
+ {
48
+ name: "PBKDF2",
49
+ salt: salt.buffer,
50
+ iterations,
51
+ hash: "SHA-256"
52
+ },
53
+ keyMaterial,
54
+ { name: "AES-GCM", length: keyLength * 8 },
55
+ false,
56
+ ["decrypt"]
57
+ );
58
+ }
59
+ __name(deriveKey, "deriveKey");
60
+ async function buildSalt(keyPrefix = "djangocfg_encryption", userId, sessionId) {
61
+ const parts = [keyPrefix];
62
+ if (sessionId) {
63
+ parts.push(`session:${sessionId}`);
64
+ } else if (userId !== void 0) {
65
+ parts.push(`user:${userId}`);
66
+ } else {
67
+ parts.push("global");
68
+ }
69
+ const saltInput = parts.join(":");
70
+ const encoder = new TextEncoder();
71
+ const inputBuffer = encoder.encode(saltInput);
72
+ const hashBuffer = await crypto.subtle.digest("SHA-256", inputBuffer);
73
+ return new Uint8Array(hashBuffer).slice(0, 16);
74
+ }
75
+ __name(buildSalt, "buildSalt");
76
+ async function deriveKeyFromConfig(config) {
77
+ const {
78
+ secretKey,
79
+ userId,
80
+ sessionId,
81
+ iterations = 1e5,
82
+ keyPrefix = "djangocfg_encryption"
83
+ } = config;
84
+ const salt = await buildSalt(keyPrefix, userId, sessionId);
85
+ return deriveKey(secretKey, salt, iterations);
86
+ }
87
+ __name(deriveKeyFromConfig, "deriveKeyFromConfig");
88
+
89
+ // src/types.ts
90
+ function isEncryptedField(value) {
91
+ if (typeof value !== "object" || value === null) return false;
92
+ const obj = value;
93
+ return obj.encrypted === true && typeof obj.algorithm === "string" && typeof obj.iv === "string" && typeof obj.data === "string" && typeof obj.auth_tag === "string";
94
+ }
95
+ __name(isEncryptedField, "isEncryptedField");
96
+ function isEncryptedResponse(value) {
97
+ if (typeof value !== "object" || value === null) return false;
98
+ const obj = value;
99
+ return obj.encrypted === true && typeof obj.algorithm === "string" && typeof obj.salt === "string" && typeof obj.iv === "string" && typeof obj.data === "string" && typeof obj.auth_tag === "string";
100
+ }
101
+ __name(isEncryptedResponse, "isEncryptedResponse");
102
+
103
+ // src/decryption.ts
104
+ function base64ToBytes(base64) {
105
+ const binary = atob(base64);
106
+ const bytes = new Uint8Array(binary.length);
107
+ for (let i = 0; i < binary.length; i++) {
108
+ bytes[i] = binary.charCodeAt(i);
109
+ }
110
+ return bytes;
111
+ }
112
+ __name(base64ToBytes, "base64ToBytes");
113
+ async function decryptAES256GCM(ciphertext, key, iv, authTag) {
114
+ const combined = new Uint8Array(ciphertext.length + authTag.length);
115
+ combined.set(ciphertext);
116
+ combined.set(authTag, ciphertext.length);
117
+ const decrypted = await crypto.subtle.decrypt(
118
+ {
119
+ name: "AES-GCM",
120
+ iv: iv.buffer,
121
+ tagLength: 128
122
+ // 16 bytes = 128 bits
123
+ },
124
+ key,
125
+ combined
126
+ );
127
+ return new Uint8Array(decrypted);
128
+ }
129
+ __name(decryptAES256GCM, "decryptAES256GCM");
130
+ async function decryptField(field, key) {
131
+ if (field.algorithm !== "AES-256-GCM") {
132
+ throw new Error(`Unsupported algorithm: ${field.algorithm}`);
133
+ }
134
+ const iv = base64ToBytes(field.iv);
135
+ const ciphertext = base64ToBytes(field.data);
136
+ const authTag = base64ToBytes(field.auth_tag);
137
+ const decrypted = await decryptAES256GCM(ciphertext, key, iv, authTag);
138
+ const text = new TextDecoder().decode(decrypted);
139
+ return JSON.parse(text);
140
+ }
141
+ __name(decryptField, "decryptField");
142
+ async function decryptObject(data, key) {
143
+ if (data === null || data === void 0) {
144
+ return data;
145
+ }
146
+ if (isEncryptedField(data)) {
147
+ return decryptField(data, key);
148
+ }
149
+ if (Array.isArray(data)) {
150
+ const decrypted = await Promise.all(
151
+ data.map((item) => decryptObject(item, key))
152
+ );
153
+ return decrypted;
154
+ }
155
+ if (typeof data === "object") {
156
+ const result = {};
157
+ const entries = Object.entries(data);
158
+ for (const [objKey, value] of entries) {
159
+ result[objKey] = await decryptObject(value, key);
160
+ }
161
+ return result;
162
+ }
163
+ return data;
164
+ }
165
+ __name(decryptObject, "decryptObject");
166
+ async function createDecryptionClient(config) {
167
+ const key = await deriveKeyFromConfig(config);
168
+ return {
169
+ /**
170
+ * Decrypt a single encrypted field.
171
+ */
172
+ decryptField: /* @__PURE__ */ __name((field) => decryptField(field, key), "decryptField"),
173
+ /**
174
+ * Recursively decrypt all encrypted fields in an object.
175
+ */
176
+ decryptObject: /* @__PURE__ */ __name((data) => decryptObject(data, key), "decryptObject"),
177
+ /**
178
+ * Check if a value is an encrypted field.
179
+ */
180
+ isEncryptedField,
181
+ /**
182
+ * Check if a value is an encrypted response.
183
+ */
184
+ isEncryptedResponse
185
+ };
186
+ }
187
+ __name(createDecryptionClient, "createDecryptionClient");
188
+
189
+ // src/react/hooks.ts
190
+ function useDecrypt(encryptedData, config) {
191
+ const [state, setState] = (0, import_react.useState)({
192
+ data: void 0,
193
+ isLoading: true,
194
+ error: void 0,
195
+ isDecrypted: false
196
+ });
197
+ const keyRef = (0, import_react.useRef)(null);
198
+ const configRef = (0, import_react.useRef)(config);
199
+ const configChanged = configRef.current.secretKey !== config.secretKey || configRef.current.userId !== config.userId || configRef.current.sessionId !== config.sessionId;
200
+ if (configChanged) {
201
+ configRef.current = config;
202
+ keyRef.current = null;
203
+ }
204
+ (0, import_react.useEffect)(() => {
205
+ let cancelled = false;
206
+ async function decrypt() {
207
+ try {
208
+ setState((s) => ({ ...s, isLoading: true, error: void 0 }));
209
+ if (!keyRef.current) {
210
+ keyRef.current = await deriveKeyFromConfig(config);
211
+ }
212
+ const decrypted = await decryptObject(encryptedData, keyRef.current);
213
+ if (!cancelled) {
214
+ setState({
215
+ data: decrypted,
216
+ isLoading: false,
217
+ error: void 0,
218
+ isDecrypted: true
219
+ });
220
+ }
221
+ } catch (err) {
222
+ if (!cancelled) {
223
+ setState({
224
+ data: void 0,
225
+ isLoading: false,
226
+ error: err instanceof Error ? err : new Error("Decryption failed"),
227
+ isDecrypted: false
228
+ });
229
+ }
230
+ }
231
+ }
232
+ __name(decrypt, "decrypt");
233
+ decrypt();
234
+ return () => {
235
+ cancelled = true;
236
+ };
237
+ }, [encryptedData, config.secretKey, config.userId, config.sessionId]);
238
+ return state;
239
+ }
240
+ __name(useDecrypt, "useDecrypt");
241
+ function useDecryptionClient(config) {
242
+ const [client, setClient] = (0, import_react.useState)(null);
243
+ (0, import_react.useEffect)(() => {
244
+ createDecryptionClient(config).then(setClient);
245
+ }, [config.secretKey, config.userId, config.sessionId]);
246
+ return client;
247
+ }
248
+ __name(useDecryptionClient, "useDecryptionClient");
249
+ function useLazyDecrypt(config) {
250
+ const [state, setState] = (0, import_react.useState)({
251
+ data: void 0,
252
+ isLoading: false,
253
+ error: void 0,
254
+ isDecrypted: false
255
+ });
256
+ const keyRef = (0, import_react.useRef)(null);
257
+ const decrypt = (0, import_react.useCallback)(
258
+ async (encryptedData) => {
259
+ try {
260
+ setState((s) => ({ ...s, isLoading: true, error: void 0 }));
261
+ if (!keyRef.current) {
262
+ keyRef.current = await deriveKeyFromConfig(config);
263
+ }
264
+ const decrypted = await decryptObject(encryptedData, keyRef.current);
265
+ setState({
266
+ data: decrypted,
267
+ isLoading: false,
268
+ error: void 0,
269
+ isDecrypted: true
270
+ });
271
+ return decrypted;
272
+ } catch (err) {
273
+ const error = err instanceof Error ? err : new Error("Decryption failed");
274
+ setState({
275
+ data: void 0,
276
+ isLoading: false,
277
+ error,
278
+ isDecrypted: false
279
+ });
280
+ return void 0;
281
+ }
282
+ },
283
+ [config.secretKey, config.userId, config.sessionId]
284
+ );
285
+ const reset = (0, import_react.useCallback)(() => {
286
+ setState({
287
+ data: void 0,
288
+ isLoading: false,
289
+ error: void 0,
290
+ isDecrypted: false
291
+ });
292
+ }, []);
293
+ return { ...state, decrypt, reset };
294
+ }
295
+ __name(useLazyDecrypt, "useLazyDecrypt");
296
+ function useIsEncrypted(value) {
297
+ return (0, import_react.useMemo)(() => isEncryptedField(value), [value]);
298
+ }
299
+ __name(useIsEncrypted, "useIsEncrypted");
300
+ //# sourceMappingURL=react.cjs.map
package/dist/react.mjs ADDED
@@ -0,0 +1,279 @@
1
+ "use client";
2
+ var __defProp = Object.defineProperty;
3
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
4
+
5
+ // src/react/hooks.ts
6
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
7
+
8
+ // src/key-derivation.ts
9
+ async function deriveKey(password, salt, iterations = 1e5, keyLength = 32) {
10
+ const encoder = new TextEncoder();
11
+ const passwordBuffer = encoder.encode(password);
12
+ const keyMaterial = await crypto.subtle.importKey(
13
+ "raw",
14
+ passwordBuffer,
15
+ "PBKDF2",
16
+ false,
17
+ ["deriveBits", "deriveKey"]
18
+ );
19
+ return crypto.subtle.deriveKey(
20
+ {
21
+ name: "PBKDF2",
22
+ salt: salt.buffer,
23
+ iterations,
24
+ hash: "SHA-256"
25
+ },
26
+ keyMaterial,
27
+ { name: "AES-GCM", length: keyLength * 8 },
28
+ false,
29
+ ["decrypt"]
30
+ );
31
+ }
32
+ __name(deriveKey, "deriveKey");
33
+ async function buildSalt(keyPrefix = "djangocfg_encryption", userId, sessionId) {
34
+ const parts = [keyPrefix];
35
+ if (sessionId) {
36
+ parts.push(`session:${sessionId}`);
37
+ } else if (userId !== void 0) {
38
+ parts.push(`user:${userId}`);
39
+ } else {
40
+ parts.push("global");
41
+ }
42
+ const saltInput = parts.join(":");
43
+ const encoder = new TextEncoder();
44
+ const inputBuffer = encoder.encode(saltInput);
45
+ const hashBuffer = await crypto.subtle.digest("SHA-256", inputBuffer);
46
+ return new Uint8Array(hashBuffer).slice(0, 16);
47
+ }
48
+ __name(buildSalt, "buildSalt");
49
+ async function deriveKeyFromConfig(config) {
50
+ const {
51
+ secretKey,
52
+ userId,
53
+ sessionId,
54
+ iterations = 1e5,
55
+ keyPrefix = "djangocfg_encryption"
56
+ } = config;
57
+ const salt = await buildSalt(keyPrefix, userId, sessionId);
58
+ return deriveKey(secretKey, salt, iterations);
59
+ }
60
+ __name(deriveKeyFromConfig, "deriveKeyFromConfig");
61
+
62
+ // src/types.ts
63
+ function isEncryptedField(value) {
64
+ if (typeof value !== "object" || value === null) return false;
65
+ const obj = value;
66
+ return obj.encrypted === true && typeof obj.algorithm === "string" && typeof obj.iv === "string" && typeof obj.data === "string" && typeof obj.auth_tag === "string";
67
+ }
68
+ __name(isEncryptedField, "isEncryptedField");
69
+ function isEncryptedResponse(value) {
70
+ if (typeof value !== "object" || value === null) return false;
71
+ const obj = value;
72
+ return obj.encrypted === true && typeof obj.algorithm === "string" && typeof obj.salt === "string" && typeof obj.iv === "string" && typeof obj.data === "string" && typeof obj.auth_tag === "string";
73
+ }
74
+ __name(isEncryptedResponse, "isEncryptedResponse");
75
+
76
+ // src/decryption.ts
77
+ function base64ToBytes(base64) {
78
+ const binary = atob(base64);
79
+ const bytes = new Uint8Array(binary.length);
80
+ for (let i = 0; i < binary.length; i++) {
81
+ bytes[i] = binary.charCodeAt(i);
82
+ }
83
+ return bytes;
84
+ }
85
+ __name(base64ToBytes, "base64ToBytes");
86
+ async function decryptAES256GCM(ciphertext, key, iv, authTag) {
87
+ const combined = new Uint8Array(ciphertext.length + authTag.length);
88
+ combined.set(ciphertext);
89
+ combined.set(authTag, ciphertext.length);
90
+ const decrypted = await crypto.subtle.decrypt(
91
+ {
92
+ name: "AES-GCM",
93
+ iv: iv.buffer,
94
+ tagLength: 128
95
+ // 16 bytes = 128 bits
96
+ },
97
+ key,
98
+ combined
99
+ );
100
+ return new Uint8Array(decrypted);
101
+ }
102
+ __name(decryptAES256GCM, "decryptAES256GCM");
103
+ async function decryptField(field, key) {
104
+ if (field.algorithm !== "AES-256-GCM") {
105
+ throw new Error(`Unsupported algorithm: ${field.algorithm}`);
106
+ }
107
+ const iv = base64ToBytes(field.iv);
108
+ const ciphertext = base64ToBytes(field.data);
109
+ const authTag = base64ToBytes(field.auth_tag);
110
+ const decrypted = await decryptAES256GCM(ciphertext, key, iv, authTag);
111
+ const text = new TextDecoder().decode(decrypted);
112
+ return JSON.parse(text);
113
+ }
114
+ __name(decryptField, "decryptField");
115
+ async function decryptObject(data, key) {
116
+ if (data === null || data === void 0) {
117
+ return data;
118
+ }
119
+ if (isEncryptedField(data)) {
120
+ return decryptField(data, key);
121
+ }
122
+ if (Array.isArray(data)) {
123
+ const decrypted = await Promise.all(
124
+ data.map((item) => decryptObject(item, key))
125
+ );
126
+ return decrypted;
127
+ }
128
+ if (typeof data === "object") {
129
+ const result = {};
130
+ const entries = Object.entries(data);
131
+ for (const [objKey, value] of entries) {
132
+ result[objKey] = await decryptObject(value, key);
133
+ }
134
+ return result;
135
+ }
136
+ return data;
137
+ }
138
+ __name(decryptObject, "decryptObject");
139
+ async function createDecryptionClient(config) {
140
+ const key = await deriveKeyFromConfig(config);
141
+ return {
142
+ /**
143
+ * Decrypt a single encrypted field.
144
+ */
145
+ decryptField: /* @__PURE__ */ __name((field) => decryptField(field, key), "decryptField"),
146
+ /**
147
+ * Recursively decrypt all encrypted fields in an object.
148
+ */
149
+ decryptObject: /* @__PURE__ */ __name((data) => decryptObject(data, key), "decryptObject"),
150
+ /**
151
+ * Check if a value is an encrypted field.
152
+ */
153
+ isEncryptedField,
154
+ /**
155
+ * Check if a value is an encrypted response.
156
+ */
157
+ isEncryptedResponse
158
+ };
159
+ }
160
+ __name(createDecryptionClient, "createDecryptionClient");
161
+
162
+ // src/react/hooks.ts
163
+ function useDecrypt(encryptedData, config) {
164
+ const [state, setState] = useState({
165
+ data: void 0,
166
+ isLoading: true,
167
+ error: void 0,
168
+ isDecrypted: false
169
+ });
170
+ const keyRef = useRef(null);
171
+ const configRef = useRef(config);
172
+ const configChanged = configRef.current.secretKey !== config.secretKey || configRef.current.userId !== config.userId || configRef.current.sessionId !== config.sessionId;
173
+ if (configChanged) {
174
+ configRef.current = config;
175
+ keyRef.current = null;
176
+ }
177
+ useEffect(() => {
178
+ let cancelled = false;
179
+ async function decrypt() {
180
+ try {
181
+ setState((s) => ({ ...s, isLoading: true, error: void 0 }));
182
+ if (!keyRef.current) {
183
+ keyRef.current = await deriveKeyFromConfig(config);
184
+ }
185
+ const decrypted = await decryptObject(encryptedData, keyRef.current);
186
+ if (!cancelled) {
187
+ setState({
188
+ data: decrypted,
189
+ isLoading: false,
190
+ error: void 0,
191
+ isDecrypted: true
192
+ });
193
+ }
194
+ } catch (err) {
195
+ if (!cancelled) {
196
+ setState({
197
+ data: void 0,
198
+ isLoading: false,
199
+ error: err instanceof Error ? err : new Error("Decryption failed"),
200
+ isDecrypted: false
201
+ });
202
+ }
203
+ }
204
+ }
205
+ __name(decrypt, "decrypt");
206
+ decrypt();
207
+ return () => {
208
+ cancelled = true;
209
+ };
210
+ }, [encryptedData, config.secretKey, config.userId, config.sessionId]);
211
+ return state;
212
+ }
213
+ __name(useDecrypt, "useDecrypt");
214
+ function useDecryptionClient(config) {
215
+ const [client, setClient] = useState(null);
216
+ useEffect(() => {
217
+ createDecryptionClient(config).then(setClient);
218
+ }, [config.secretKey, config.userId, config.sessionId]);
219
+ return client;
220
+ }
221
+ __name(useDecryptionClient, "useDecryptionClient");
222
+ function useLazyDecrypt(config) {
223
+ const [state, setState] = useState({
224
+ data: void 0,
225
+ isLoading: false,
226
+ error: void 0,
227
+ isDecrypted: false
228
+ });
229
+ const keyRef = useRef(null);
230
+ const decrypt = useCallback(
231
+ async (encryptedData) => {
232
+ try {
233
+ setState((s) => ({ ...s, isLoading: true, error: void 0 }));
234
+ if (!keyRef.current) {
235
+ keyRef.current = await deriveKeyFromConfig(config);
236
+ }
237
+ const decrypted = await decryptObject(encryptedData, keyRef.current);
238
+ setState({
239
+ data: decrypted,
240
+ isLoading: false,
241
+ error: void 0,
242
+ isDecrypted: true
243
+ });
244
+ return decrypted;
245
+ } catch (err) {
246
+ const error = err instanceof Error ? err : new Error("Decryption failed");
247
+ setState({
248
+ data: void 0,
249
+ isLoading: false,
250
+ error,
251
+ isDecrypted: false
252
+ });
253
+ return void 0;
254
+ }
255
+ },
256
+ [config.secretKey, config.userId, config.sessionId]
257
+ );
258
+ const reset = useCallback(() => {
259
+ setState({
260
+ data: void 0,
261
+ isLoading: false,
262
+ error: void 0,
263
+ isDecrypted: false
264
+ });
265
+ }, []);
266
+ return { ...state, decrypt, reset };
267
+ }
268
+ __name(useLazyDecrypt, "useLazyDecrypt");
269
+ function useIsEncrypted(value) {
270
+ return useMemo(() => isEncryptedField(value), [value]);
271
+ }
272
+ __name(useIsEncrypted, "useIsEncrypted");
273
+ export {
274
+ useDecrypt,
275
+ useDecryptionClient,
276
+ useIsEncrypted,
277
+ useLazyDecrypt
278
+ };
279
+ //# sourceMappingURL=react.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/crypto",
3
- "version": "2.1.236",
3
+ "version": "2.1.238",
4
4
  "description": "Client-side AES-256-GCM decryption for Django-CFG encrypted API responses using Web Crypto API",
5
5
  "keywords": [
6
6
  "crypto",
@@ -66,7 +66,7 @@
66
66
  }
67
67
  },
68
68
  "devDependencies": {
69
- "@djangocfg/typescript-config": "^2.1.236",
69
+ "@djangocfg/typescript-config": "^2.1.238",
70
70
  "@types/node": "^24.7.2",
71
71
  "@types/react": "^19.1.0",
72
72
  "react": "^19.1.0",