init
This commit is contained in:
Generated
+1851
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"author": "CryptoLocker",
|
||||
"license": "MIT",
|
||||
"description": "AES encryption logic and Vite plugin for React components",
|
||||
"keywords": [
|
||||
"react",
|
||||
"vite",
|
||||
"encryption",
|
||||
"crypto",
|
||||
"security"
|
||||
],
|
||||
"name": "@crypto-locker/core",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"main": "./dist/crypto-locker.umd.cjs",
|
||||
"module": "./dist/crypto-locker.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/crypto-locker.js",
|
||||
"require": "./dist/crypto-locker.umd.cjs"
|
||||
},
|
||||
"./plugin": "./src/plugin.ts"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"react": ">=18.0.0",
|
||||
"react-dom": ">=18.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"crypto-js": "^4.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/react": "^19.1.0",
|
||||
"@types/react-dom": "^19.1.0",
|
||||
"@vitejs/plugin-react": "^4.5.2",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"typescript": "^5.8.0",
|
||||
"vite": "^6.3.5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export { useCryptoLocker } from "./useCryptoLocker";
|
||||
export type { CryptoLockerStatus } from "./useCryptoLocker";
|
||||
export type {
|
||||
CryptoLockerModule,
|
||||
CryptoLockerPluginOptions,
|
||||
} from "./types";
|
||||
@@ -0,0 +1,69 @@
|
||||
import type { Plugin } from "vite";
|
||||
import type { CryptoLockerPluginOptions } from "./types";
|
||||
|
||||
const DEFAULT_INCLUDE = /\.secret\.[jt]sx?$/;
|
||||
|
||||
/**
|
||||
* Vite plugin for automatic AES encryption of modules during build/dev.
|
||||
*
|
||||
* Files matching the `include` pattern (default `*.secret.ts`),
|
||||
* are transformed: their source code is compiled to CommonJS,
|
||||
* encrypted via AES with the specified password, and replaced with:
|
||||
*
|
||||
* export const encryptedData = "<ciphertext>";
|
||||
*
|
||||
* The password never ends up in the build artifacts.
|
||||
*/
|
||||
export function cryptoLockerPlugin(
|
||||
options: CryptoLockerPluginOptions,
|
||||
): Plugin {
|
||||
const { password } = options;
|
||||
|
||||
const pattern =
|
||||
options.include != null
|
||||
? typeof options.include === "string"
|
||||
? new RegExp(options.include)
|
||||
: options.include
|
||||
: DEFAULT_INCLUDE;
|
||||
|
||||
if (!password) {
|
||||
throw new Error(
|
||||
"[crypto-locker] password is required. " +
|
||||
"Set LOCKER_PASSWORD env variable or pass it in plugin options.",
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
name: "vite-plugin-crypto-locker",
|
||||
enforce: "pre",
|
||||
|
||||
async transform(code: string, id: string) {
|
||||
// Ignore files that do not match the pattern
|
||||
if (!pattern.test(id)) return null;
|
||||
|
||||
// Dynamic import of dependencies (available via Vite/Node)
|
||||
const { transform: esbuildTransform } = await import("esbuild");
|
||||
const CryptoJS = (await import("crypto-js")).default;
|
||||
|
||||
// 1. Compile TS/TSX → CJS JS via esbuild
|
||||
// Use "transform" for JSX to avoid imports to "react/jsx-runtime"
|
||||
const { code: jsCode } = await esbuildTransform(code, {
|
||||
loader: id.match(/\.tsx$/) ? "tsx" : "ts",
|
||||
format: "cjs",
|
||||
target: "es2020",
|
||||
jsx: "transform",
|
||||
jsxFactory: "React.createElement",
|
||||
jsxFragment: "React.Fragment"
|
||||
});
|
||||
|
||||
// 2. Encrypt the compiled JavaScript code
|
||||
const encrypted = CryptoJS.AES.encrypt(jsCode, password).toString();
|
||||
|
||||
// 3. Return the transformed module
|
||||
return {
|
||||
code: `export const encryptedData = ${JSON.stringify(encrypted)};\n`,
|
||||
map: null,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
/** Module transformed by the Vite plugin (contains the encrypted string). */
|
||||
export interface CryptoLockerModule {
|
||||
encryptedData: string;
|
||||
}
|
||||
|
||||
|
||||
/** Vite plugin options. */
|
||||
export interface CryptoLockerPluginOptions {
|
||||
/** Password for AES encryption during build. */
|
||||
password: string;
|
||||
|
||||
/**
|
||||
* File pattern to encrypt.
|
||||
* @default /\.secret\.[jt]sx?$/
|
||||
*/
|
||||
include?: string | RegExp;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import CryptoJS from "crypto-js";
|
||||
import React from "react";
|
||||
import type { CryptoLockerModule } from "./types";
|
||||
|
||||
export type CryptoLockerStatus = "idle" | "loading" | "success" | "error";
|
||||
|
||||
/**
|
||||
* Hook that provides logic for AES decryption of dynamic modules.
|
||||
* UI, password handling, and validation are fully delegated to the developer.
|
||||
*/
|
||||
export function useCryptoLocker<T = unknown>() {
|
||||
const [status, setStatus] = useState<CryptoLockerStatus>("idle");
|
||||
const [decryptedData, setDecryptedData] = useState<T | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
/**
|
||||
* Decrypts the dynamically loaded module and evaluates its code.
|
||||
*
|
||||
* @param fetchData A function that calls dynamic `import()` for the secret module.
|
||||
* @param password The AES password.
|
||||
*/
|
||||
const unlock = useCallback(
|
||||
async (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
fetchData: () => Promise<any>,
|
||||
password: string
|
||||
): Promise<T> => {
|
||||
setStatus("loading");
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const mod = (await fetchData()) as CryptoLockerModule;
|
||||
const ciphertext = mod.encryptedData;
|
||||
|
||||
if (typeof ciphertext !== "string") {
|
||||
throw new Error("Module does not export 'encryptedData'.");
|
||||
}
|
||||
|
||||
const bytes = CryptoJS.AES.decrypt(ciphertext, password);
|
||||
const plaintextCode = bytes.toString(CryptoJS.enc.Utf8);
|
||||
|
||||
if (!plaintextCode) {
|
||||
throw new Error("Invalid password or empty decryption result.");
|
||||
}
|
||||
|
||||
// Evaluate the decrypted CommonJS code
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const exportsObj: any = {};
|
||||
const moduleObj = { exports: exportsObj };
|
||||
|
||||
// Provide React dependency to the evaluated module
|
||||
const requireFn = (id: string) => {
|
||||
if (id === "react") return React;
|
||||
throw new Error(`Cannot require '${id}' in encrypted module.`);
|
||||
};
|
||||
|
||||
const fn = new Function(
|
||||
"require",
|
||||
"exports",
|
||||
"module",
|
||||
"React",
|
||||
plaintextCode
|
||||
);
|
||||
fn(requireFn, exportsObj, moduleObj, React);
|
||||
|
||||
const parsed = (moduleObj.exports.default ?? moduleObj.exports) as T;
|
||||
|
||||
setDecryptedData(() => parsed);
|
||||
setStatus("success");
|
||||
return parsed;
|
||||
} catch (err: unknown) {
|
||||
console.error("CryptoLocker decryption failed:", err);
|
||||
const errMsg = err instanceof Error ? err.message : "Decryption failed";
|
||||
setError(errMsg);
|
||||
setStatus("error");
|
||||
setDecryptedData(null);
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
/**
|
||||
* Resets the hook state to idle, clearing any decrypted data and errors.
|
||||
*/
|
||||
const reset = useCallback(() => {
|
||||
setStatus("idle");
|
||||
setDecryptedData(null);
|
||||
setError(null);
|
||||
}, []);
|
||||
|
||||
return { unlock, reset, status, decryptedData, error };
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const CryptoJS = require("crypto-js");
|
||||
const ciphertext = "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";
|
||||
const password = process.env.LOCKER_PASSWORD || "secret123";
|
||||
const bytes = CryptoJS.AES.decrypt(ciphertext, password);
|
||||
const plaintextCode = bytes.toString(CryptoJS.enc.Utf8);
|
||||
console.log(plaintextCode ? "Decrypted!" : "Failed to decrypt!");
|
||||
if (plaintextCode) console.log(plaintextCode.slice(0, 100));
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"lib": [
|
||||
"ES2020",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { resolve } from "path";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react({ jsxRuntime: "automatic" })],
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, "src/index.ts"),
|
||||
name: "CryptoLocker",
|
||||
fileName: "crypto-locker",
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ["react", "react-dom", "react/jsx-runtime", "crypto-js"],
|
||||
output: {
|
||||
globals: {
|
||||
react: "React",
|
||||
"react-dom": "ReactDOM",
|
||||
"react/jsx-runtime": "jsxRuntime",
|
||||
"crypto-js": "CryptoJS",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user