@djangocfg/crypto
Advanced tools
+2
-2
| { | ||
| "name": "@djangocfg/crypto", | ||
| "version": "2.1.235", | ||
| "version": "2.1.236", | ||
| "description": "Client-side AES-256-GCM decryption for Django-CFG encrypted API responses using Web Crypto API", | ||
@@ -69,3 +69,3 @@ "keywords": [ | ||
| "devDependencies": { | ||
| "@djangocfg/typescript-config": "^2.1.235", | ||
| "@djangocfg/typescript-config": "^2.1.236", | ||
| "@types/node": "^24.7.2", | ||
@@ -72,0 +72,0 @@ "@types/react": "^19.1.0", |
-300
| "use client"; | ||
| "use strict"; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
| // src/react/index.ts | ||
| var react_exports = {}; | ||
| __export(react_exports, { | ||
| useDecrypt: () => useDecrypt, | ||
| useDecryptionClient: () => useDecryptionClient, | ||
| useIsEncrypted: () => useIsEncrypted, | ||
| useLazyDecrypt: () => useLazyDecrypt | ||
| }); | ||
| module.exports = __toCommonJS(react_exports); | ||
| // src/react/hooks.ts | ||
| var import_react = require("react"); | ||
| // src/key-derivation.ts | ||
| async function deriveKey(password, salt, iterations = 1e5, keyLength = 32) { | ||
| const encoder = new TextEncoder(); | ||
| const passwordBuffer = encoder.encode(password); | ||
| const keyMaterial = await crypto.subtle.importKey( | ||
| "raw", | ||
| passwordBuffer, | ||
| "PBKDF2", | ||
| false, | ||
| ["deriveBits", "deriveKey"] | ||
| ); | ||
| return crypto.subtle.deriveKey( | ||
| { | ||
| name: "PBKDF2", | ||
| salt: salt.buffer, | ||
| iterations, | ||
| hash: "SHA-256" | ||
| }, | ||
| keyMaterial, | ||
| { name: "AES-GCM", length: keyLength * 8 }, | ||
| false, | ||
| ["decrypt"] | ||
| ); | ||
| } | ||
| __name(deriveKey, "deriveKey"); | ||
| async function buildSalt(keyPrefix = "djangocfg_encryption", userId, sessionId) { | ||
| const parts = [keyPrefix]; | ||
| if (sessionId) { | ||
| parts.push(`session:${sessionId}`); | ||
| } else if (userId !== void 0) { | ||
| parts.push(`user:${userId}`); | ||
| } else { | ||
| parts.push("global"); | ||
| } | ||
| const saltInput = parts.join(":"); | ||
| const encoder = new TextEncoder(); | ||
| const inputBuffer = encoder.encode(saltInput); | ||
| const hashBuffer = await crypto.subtle.digest("SHA-256", inputBuffer); | ||
| return new Uint8Array(hashBuffer).slice(0, 16); | ||
| } | ||
| __name(buildSalt, "buildSalt"); | ||
| async function deriveKeyFromConfig(config) { | ||
| const { | ||
| secretKey, | ||
| userId, | ||
| sessionId, | ||
| iterations = 1e5, | ||
| keyPrefix = "djangocfg_encryption" | ||
| } = config; | ||
| const salt = await buildSalt(keyPrefix, userId, sessionId); | ||
| return deriveKey(secretKey, salt, iterations); | ||
| } | ||
| __name(deriveKeyFromConfig, "deriveKeyFromConfig"); | ||
| // src/types.ts | ||
| function isEncryptedField(value) { | ||
| if (typeof value !== "object" || value === null) return false; | ||
| const obj = value; | ||
| return obj.encrypted === true && typeof obj.algorithm === "string" && typeof obj.iv === "string" && typeof obj.data === "string" && typeof obj.auth_tag === "string"; | ||
| } | ||
| __name(isEncryptedField, "isEncryptedField"); | ||
| function isEncryptedResponse(value) { | ||
| if (typeof value !== "object" || value === null) return false; | ||
| const obj = value; | ||
| 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"; | ||
| } | ||
| __name(isEncryptedResponse, "isEncryptedResponse"); | ||
| // src/decryption.ts | ||
| function base64ToBytes(base64) { | ||
| const binary = atob(base64); | ||
| const bytes = new Uint8Array(binary.length); | ||
| for (let i = 0; i < binary.length; i++) { | ||
| bytes[i] = binary.charCodeAt(i); | ||
| } | ||
| return bytes; | ||
| } | ||
| __name(base64ToBytes, "base64ToBytes"); | ||
| async function decryptAES256GCM(ciphertext, key, iv, authTag) { | ||
| const combined = new Uint8Array(ciphertext.length + authTag.length); | ||
| combined.set(ciphertext); | ||
| combined.set(authTag, ciphertext.length); | ||
| const decrypted = await crypto.subtle.decrypt( | ||
| { | ||
| name: "AES-GCM", | ||
| iv: iv.buffer, | ||
| tagLength: 128 | ||
| // 16 bytes = 128 bits | ||
| }, | ||
| key, | ||
| combined | ||
| ); | ||
| return new Uint8Array(decrypted); | ||
| } | ||
| __name(decryptAES256GCM, "decryptAES256GCM"); | ||
| async function decryptField(field, key) { | ||
| if (field.algorithm !== "AES-256-GCM") { | ||
| throw new Error(`Unsupported algorithm: ${field.algorithm}`); | ||
| } | ||
| const iv = base64ToBytes(field.iv); | ||
| const ciphertext = base64ToBytes(field.data); | ||
| const authTag = base64ToBytes(field.auth_tag); | ||
| const decrypted = await decryptAES256GCM(ciphertext, key, iv, authTag); | ||
| const text = new TextDecoder().decode(decrypted); | ||
| return JSON.parse(text); | ||
| } | ||
| __name(decryptField, "decryptField"); | ||
| async function decryptObject(data, key) { | ||
| if (data === null || data === void 0) { | ||
| return data; | ||
| } | ||
| if (isEncryptedField(data)) { | ||
| return decryptField(data, key); | ||
| } | ||
| if (Array.isArray(data)) { | ||
| const decrypted = await Promise.all( | ||
| data.map((item) => decryptObject(item, key)) | ||
| ); | ||
| return decrypted; | ||
| } | ||
| if (typeof data === "object") { | ||
| const result = {}; | ||
| const entries = Object.entries(data); | ||
| for (const [objKey, value] of entries) { | ||
| result[objKey] = await decryptObject(value, key); | ||
| } | ||
| return result; | ||
| } | ||
| return data; | ||
| } | ||
| __name(decryptObject, "decryptObject"); | ||
| async function createDecryptionClient(config) { | ||
| const key = await deriveKeyFromConfig(config); | ||
| return { | ||
| /** | ||
| * Decrypt a single encrypted field. | ||
| */ | ||
| decryptField: /* @__PURE__ */ __name((field) => decryptField(field, key), "decryptField"), | ||
| /** | ||
| * Recursively decrypt all encrypted fields in an object. | ||
| */ | ||
| decryptObject: /* @__PURE__ */ __name((data) => decryptObject(data, key), "decryptObject"), | ||
| /** | ||
| * Check if a value is an encrypted field. | ||
| */ | ||
| isEncryptedField, | ||
| /** | ||
| * Check if a value is an encrypted response. | ||
| */ | ||
| isEncryptedResponse | ||
| }; | ||
| } | ||
| __name(createDecryptionClient, "createDecryptionClient"); | ||
| // src/react/hooks.ts | ||
| function useDecrypt(encryptedData, config) { | ||
| const [state, setState] = (0, import_react.useState)({ | ||
| data: void 0, | ||
| isLoading: true, | ||
| error: void 0, | ||
| isDecrypted: false | ||
| }); | ||
| const keyRef = (0, import_react.useRef)(null); | ||
| const configRef = (0, import_react.useRef)(config); | ||
| const configChanged = configRef.current.secretKey !== config.secretKey || configRef.current.userId !== config.userId || configRef.current.sessionId !== config.sessionId; | ||
| if (configChanged) { | ||
| configRef.current = config; | ||
| keyRef.current = null; | ||
| } | ||
| (0, import_react.useEffect)(() => { | ||
| let cancelled = false; | ||
| async function decrypt() { | ||
| try { | ||
| setState((s) => ({ ...s, isLoading: true, error: void 0 })); | ||
| if (!keyRef.current) { | ||
| keyRef.current = await deriveKeyFromConfig(config); | ||
| } | ||
| const decrypted = await decryptObject(encryptedData, keyRef.current); | ||
| if (!cancelled) { | ||
| setState({ | ||
| data: decrypted, | ||
| isLoading: false, | ||
| error: void 0, | ||
| isDecrypted: true | ||
| }); | ||
| } | ||
| } catch (err) { | ||
| if (!cancelled) { | ||
| setState({ | ||
| data: void 0, | ||
| isLoading: false, | ||
| error: err instanceof Error ? err : new Error("Decryption failed"), | ||
| isDecrypted: false | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| __name(decrypt, "decrypt"); | ||
| decrypt(); | ||
| return () => { | ||
| cancelled = true; | ||
| }; | ||
| }, [encryptedData, config.secretKey, config.userId, config.sessionId]); | ||
| return state; | ||
| } | ||
| __name(useDecrypt, "useDecrypt"); | ||
| function useDecryptionClient(config) { | ||
| const [client, setClient] = (0, import_react.useState)(null); | ||
| (0, import_react.useEffect)(() => { | ||
| createDecryptionClient(config).then(setClient); | ||
| }, [config.secretKey, config.userId, config.sessionId]); | ||
| return client; | ||
| } | ||
| __name(useDecryptionClient, "useDecryptionClient"); | ||
| function useLazyDecrypt(config) { | ||
| const [state, setState] = (0, import_react.useState)({ | ||
| data: void 0, | ||
| isLoading: false, | ||
| error: void 0, | ||
| isDecrypted: false | ||
| }); | ||
| const keyRef = (0, import_react.useRef)(null); | ||
| const decrypt = (0, import_react.useCallback)( | ||
| async (encryptedData) => { | ||
| try { | ||
| setState((s) => ({ ...s, isLoading: true, error: void 0 })); | ||
| if (!keyRef.current) { | ||
| keyRef.current = await deriveKeyFromConfig(config); | ||
| } | ||
| const decrypted = await decryptObject(encryptedData, keyRef.current); | ||
| setState({ | ||
| data: decrypted, | ||
| isLoading: false, | ||
| error: void 0, | ||
| isDecrypted: true | ||
| }); | ||
| return decrypted; | ||
| } catch (err) { | ||
| const error = err instanceof Error ? err : new Error("Decryption failed"); | ||
| setState({ | ||
| data: void 0, | ||
| isLoading: false, | ||
| error, | ||
| isDecrypted: false | ||
| }); | ||
| return void 0; | ||
| } | ||
| }, | ||
| [config.secretKey, config.userId, config.sessionId] | ||
| ); | ||
| const reset = (0, import_react.useCallback)(() => { | ||
| setState({ | ||
| data: void 0, | ||
| isLoading: false, | ||
| error: void 0, | ||
| isDecrypted: false | ||
| }); | ||
| }, []); | ||
| return { ...state, decrypt, reset }; | ||
| } | ||
| __name(useLazyDecrypt, "useLazyDecrypt"); | ||
| function useIsEncrypted(value) { | ||
| return (0, import_react.useMemo)(() => isEncryptedField(value), [value]); | ||
| } | ||
| __name(useIsEncrypted, "useIsEncrypted"); | ||
| //# sourceMappingURL=react.cjs.map |
-279
| "use client"; | ||
| var __defProp = Object.defineProperty; | ||
| var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); | ||
| // src/react/hooks.ts | ||
| import { useCallback, useEffect, useMemo, useRef, useState } from "react"; | ||
| // src/key-derivation.ts | ||
| async function deriveKey(password, salt, iterations = 1e5, keyLength = 32) { | ||
| const encoder = new TextEncoder(); | ||
| const passwordBuffer = encoder.encode(password); | ||
| const keyMaterial = await crypto.subtle.importKey( | ||
| "raw", | ||
| passwordBuffer, | ||
| "PBKDF2", | ||
| false, | ||
| ["deriveBits", "deriveKey"] | ||
| ); | ||
| return crypto.subtle.deriveKey( | ||
| { | ||
| name: "PBKDF2", | ||
| salt: salt.buffer, | ||
| iterations, | ||
| hash: "SHA-256" | ||
| }, | ||
| keyMaterial, | ||
| { name: "AES-GCM", length: keyLength * 8 }, | ||
| false, | ||
| ["decrypt"] | ||
| ); | ||
| } | ||
| __name(deriveKey, "deriveKey"); | ||
| async function buildSalt(keyPrefix = "djangocfg_encryption", userId, sessionId) { | ||
| const parts = [keyPrefix]; | ||
| if (sessionId) { | ||
| parts.push(`session:${sessionId}`); | ||
| } else if (userId !== void 0) { | ||
| parts.push(`user:${userId}`); | ||
| } else { | ||
| parts.push("global"); | ||
| } | ||
| const saltInput = parts.join(":"); | ||
| const encoder = new TextEncoder(); | ||
| const inputBuffer = encoder.encode(saltInput); | ||
| const hashBuffer = await crypto.subtle.digest("SHA-256", inputBuffer); | ||
| return new Uint8Array(hashBuffer).slice(0, 16); | ||
| } | ||
| __name(buildSalt, "buildSalt"); | ||
| async function deriveKeyFromConfig(config) { | ||
| const { | ||
| secretKey, | ||
| userId, | ||
| sessionId, | ||
| iterations = 1e5, | ||
| keyPrefix = "djangocfg_encryption" | ||
| } = config; | ||
| const salt = await buildSalt(keyPrefix, userId, sessionId); | ||
| return deriveKey(secretKey, salt, iterations); | ||
| } | ||
| __name(deriveKeyFromConfig, "deriveKeyFromConfig"); | ||
| // src/types.ts | ||
| function isEncryptedField(value) { | ||
| if (typeof value !== "object" || value === null) return false; | ||
| const obj = value; | ||
| return obj.encrypted === true && typeof obj.algorithm === "string" && typeof obj.iv === "string" && typeof obj.data === "string" && typeof obj.auth_tag === "string"; | ||
| } | ||
| __name(isEncryptedField, "isEncryptedField"); | ||
| function isEncryptedResponse(value) { | ||
| if (typeof value !== "object" || value === null) return false; | ||
| const obj = value; | ||
| 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"; | ||
| } | ||
| __name(isEncryptedResponse, "isEncryptedResponse"); | ||
| // src/decryption.ts | ||
| function base64ToBytes(base64) { | ||
| const binary = atob(base64); | ||
| const bytes = new Uint8Array(binary.length); | ||
| for (let i = 0; i < binary.length; i++) { | ||
| bytes[i] = binary.charCodeAt(i); | ||
| } | ||
| return bytes; | ||
| } | ||
| __name(base64ToBytes, "base64ToBytes"); | ||
| async function decryptAES256GCM(ciphertext, key, iv, authTag) { | ||
| const combined = new Uint8Array(ciphertext.length + authTag.length); | ||
| combined.set(ciphertext); | ||
| combined.set(authTag, ciphertext.length); | ||
| const decrypted = await crypto.subtle.decrypt( | ||
| { | ||
| name: "AES-GCM", | ||
| iv: iv.buffer, | ||
| tagLength: 128 | ||
| // 16 bytes = 128 bits | ||
| }, | ||
| key, | ||
| combined | ||
| ); | ||
| return new Uint8Array(decrypted); | ||
| } | ||
| __name(decryptAES256GCM, "decryptAES256GCM"); | ||
| async function decryptField(field, key) { | ||
| if (field.algorithm !== "AES-256-GCM") { | ||
| throw new Error(`Unsupported algorithm: ${field.algorithm}`); | ||
| } | ||
| const iv = base64ToBytes(field.iv); | ||
| const ciphertext = base64ToBytes(field.data); | ||
| const authTag = base64ToBytes(field.auth_tag); | ||
| const decrypted = await decryptAES256GCM(ciphertext, key, iv, authTag); | ||
| const text = new TextDecoder().decode(decrypted); | ||
| return JSON.parse(text); | ||
| } | ||
| __name(decryptField, "decryptField"); | ||
| async function decryptObject(data, key) { | ||
| if (data === null || data === void 0) { | ||
| return data; | ||
| } | ||
| if (isEncryptedField(data)) { | ||
| return decryptField(data, key); | ||
| } | ||
| if (Array.isArray(data)) { | ||
| const decrypted = await Promise.all( | ||
| data.map((item) => decryptObject(item, key)) | ||
| ); | ||
| return decrypted; | ||
| } | ||
| if (typeof data === "object") { | ||
| const result = {}; | ||
| const entries = Object.entries(data); | ||
| for (const [objKey, value] of entries) { | ||
| result[objKey] = await decryptObject(value, key); | ||
| } | ||
| return result; | ||
| } | ||
| return data; | ||
| } | ||
| __name(decryptObject, "decryptObject"); | ||
| async function createDecryptionClient(config) { | ||
| const key = await deriveKeyFromConfig(config); | ||
| return { | ||
| /** | ||
| * Decrypt a single encrypted field. | ||
| */ | ||
| decryptField: /* @__PURE__ */ __name((field) => decryptField(field, key), "decryptField"), | ||
| /** | ||
| * Recursively decrypt all encrypted fields in an object. | ||
| */ | ||
| decryptObject: /* @__PURE__ */ __name((data) => decryptObject(data, key), "decryptObject"), | ||
| /** | ||
| * Check if a value is an encrypted field. | ||
| */ | ||
| isEncryptedField, | ||
| /** | ||
| * Check if a value is an encrypted response. | ||
| */ | ||
| isEncryptedResponse | ||
| }; | ||
| } | ||
| __name(createDecryptionClient, "createDecryptionClient"); | ||
| // src/react/hooks.ts | ||
| function useDecrypt(encryptedData, config) { | ||
| const [state, setState] = useState({ | ||
| data: void 0, | ||
| isLoading: true, | ||
| error: void 0, | ||
| isDecrypted: false | ||
| }); | ||
| const keyRef = useRef(null); | ||
| const configRef = useRef(config); | ||
| const configChanged = configRef.current.secretKey !== config.secretKey || configRef.current.userId !== config.userId || configRef.current.sessionId !== config.sessionId; | ||
| if (configChanged) { | ||
| configRef.current = config; | ||
| keyRef.current = null; | ||
| } | ||
| useEffect(() => { | ||
| let cancelled = false; | ||
| async function decrypt() { | ||
| try { | ||
| setState((s) => ({ ...s, isLoading: true, error: void 0 })); | ||
| if (!keyRef.current) { | ||
| keyRef.current = await deriveKeyFromConfig(config); | ||
| } | ||
| const decrypted = await decryptObject(encryptedData, keyRef.current); | ||
| if (!cancelled) { | ||
| setState({ | ||
| data: decrypted, | ||
| isLoading: false, | ||
| error: void 0, | ||
| isDecrypted: true | ||
| }); | ||
| } | ||
| } catch (err) { | ||
| if (!cancelled) { | ||
| setState({ | ||
| data: void 0, | ||
| isLoading: false, | ||
| error: err instanceof Error ? err : new Error("Decryption failed"), | ||
| isDecrypted: false | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| __name(decrypt, "decrypt"); | ||
| decrypt(); | ||
| return () => { | ||
| cancelled = true; | ||
| }; | ||
| }, [encryptedData, config.secretKey, config.userId, config.sessionId]); | ||
| return state; | ||
| } | ||
| __name(useDecrypt, "useDecrypt"); | ||
| function useDecryptionClient(config) { | ||
| const [client, setClient] = useState(null); | ||
| useEffect(() => { | ||
| createDecryptionClient(config).then(setClient); | ||
| }, [config.secretKey, config.userId, config.sessionId]); | ||
| return client; | ||
| } | ||
| __name(useDecryptionClient, "useDecryptionClient"); | ||
| function useLazyDecrypt(config) { | ||
| const [state, setState] = useState({ | ||
| data: void 0, | ||
| isLoading: false, | ||
| error: void 0, | ||
| isDecrypted: false | ||
| }); | ||
| const keyRef = useRef(null); | ||
| const decrypt = useCallback( | ||
| async (encryptedData) => { | ||
| try { | ||
| setState((s) => ({ ...s, isLoading: true, error: void 0 })); | ||
| if (!keyRef.current) { | ||
| keyRef.current = await deriveKeyFromConfig(config); | ||
| } | ||
| const decrypted = await decryptObject(encryptedData, keyRef.current); | ||
| setState({ | ||
| data: decrypted, | ||
| isLoading: false, | ||
| error: void 0, | ||
| isDecrypted: true | ||
| }); | ||
| return decrypted; | ||
| } catch (err) { | ||
| const error = err instanceof Error ? err : new Error("Decryption failed"); | ||
| setState({ | ||
| data: void 0, | ||
| isLoading: false, | ||
| error, | ||
| isDecrypted: false | ||
| }); | ||
| return void 0; | ||
| } | ||
| }, | ||
| [config.secretKey, config.userId, config.sessionId] | ||
| ); | ||
| const reset = useCallback(() => { | ||
| setState({ | ||
| data: void 0, | ||
| isLoading: false, | ||
| error: void 0, | ||
| isDecrypted: false | ||
| }); | ||
| }, []); | ||
| return { ...state, decrypt, reset }; | ||
| } | ||
| __name(useLazyDecrypt, "useLazyDecrypt"); | ||
| function useIsEncrypted(value) { | ||
| return useMemo(() => isEncryptedField(value), [value]); | ||
| } | ||
| __name(useIsEncrypted, "useIsEncrypted"); | ||
| export { | ||
| useDecrypt, | ||
| useDecryptionClient, | ||
| useIsEncrypted, | ||
| useLazyDecrypt | ||
| }; | ||
| //# sourceMappingURL=react.mjs.map |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
176069
-9.18%18
-10%1863
-23.3%