Skip to content

Commit

Permalink
Encrypt accounts key in localstorage
Browse files Browse the repository at this point in the history
  • Loading branch information
ajinkyaraj-23 committed Jan 29, 2025
1 parent e5b5a6c commit dcb2a1b
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 13 deletions.
9 changes: 7 additions & 2 deletions packages/state/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
"prettier": "^3.4.2",
"rimraf": "^6.0.1",
"tsup": "^8.3.5",
"typescript": "^5.7.2"
"typescript": "^5.7.2",
"@types/crypto-js": "^4.1.1",
"@types/uuid": "^9.0.1"
},
"scripts": {
"build": "tsup-node --dts",
Expand Down Expand Up @@ -93,6 +95,9 @@
"redux": "^5.0.1",
"redux-persist": "^6.0.0",
"redux-thunk": "^3.1.0",
"zod": "^3.24.1"
"zod": "^3.24.1",
"@fingerprintjs/fingerprintjs": "^3.4.0",
"crypto-js": "^4.1.1",
"uuid": "^9.0.0"
}
}
2 changes: 2 additions & 0 deletions packages/state/src/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { multisigsSlice } from "./slices/multisigs";
import { networksSlice } from "./slices/networks";
import { protocolSettingsSlice } from "./slices/protocolSettings";
import { tokensSlice } from "./slices/tokens";
import { createEncryptionTransform } from "./transforms/encryption";

let TEST_STORAGE: Storage | undefined;

Expand Down Expand Up @@ -49,6 +50,7 @@ export const makeReducer = (storage_: Storage | undefined) => {
storage,
migrate: createAsyncMigrate(accountsMigrations, { debug: false }),
blacklist: ["password"],
transforms: [createEncryptionTransform()],
};

const rootReducers = combineReducers({
Expand Down
55 changes: 55 additions & 0 deletions packages/state/src/transforms/encryption.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import CryptoJS from 'crypto-js';
import { createTransform } from "redux-persist";

import { getDeviceFingerprint } from "../utils/deviceFingerprint";


// Initialize the key immediately
const encryptionKey = CryptoJS.SHA256(getDeviceFingerprint() + "umami-salt").toString();

export const createEncryptionTransform = () =>
createTransform(
// Transform state on its way to being serialized and persisted.
(inboundState) => {
if (!encryptionKey) {
// Return unencrypted state if key isn't ready yet
return inboundState;
}

const serialized = JSON.stringify(inboundState);
return CryptoJS.AES.encrypt(serialized, encryptionKey).toString();
},
// Transform state being rehydrated
(outboundState) => {
if (!encryptionKey || !outboundState) {
return outboundState;
}

try {
const decrypted = CryptoJS.AES.decrypt(outboundState as string, encryptionKey);
const decryptedString = decrypted.toString(CryptoJS.enc.Utf8);
return JSON.parse(decryptedString);
} catch (error) {
console.error('Failed to decrypt state:', error);
return outboundState;
}
},
{
// Only transform the accounts state
whitelist: ["accounts"]
}
);

const isElectron = () => {
return typeof window !== 'undefined' &&
(window.process?.type === 'renderer' || window.process?.versions?.electron);
};

// In a web browser:
isElectron() // returns false

// In Electron's renderer process:
isElectron() // returns true because process.type === 'renderer'

// In Electron's main process:
isElectron() // returns true because process.versions.electron exists
65 changes: 65 additions & 0 deletions packages/state/src/utils/deviceFingerprint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { v5 as uuidv5 } from 'uuid';

let cachedFingerprint: string | null = null;
const NAMESPACE = "1b671a64-40d5-491e-99b0-da01ff1f3341"; // Static UUID namespace

const isElectron = () => typeof window !== "undefined" &&
((window as any).process?.type === "renderer" || (window as any).process?.versions?.electron);

const getWebFingerprint = (): string => {
// For web browsers
const components = [
window.navigator.userAgent,
window.screen.height,
window.screen.width,
window.screen.colorDepth,
new Date().getTimezoneOffset()
];
return components.join("|");
};

const getElectronFingerprint = (): string => {
// For Electron
if (isElectron()) {
const os = window.require("os");
const components = [
os.hostname(),
os.platform(),
os.arch(),
os.cpus()[0]?.model,
os.totalmem()
];
return components.join("|");
}
throw new Error("Not in Electron environment");
};

export const getDeviceFingerprint = (): string => {
if (cachedFingerprint) {
return cachedFingerprint;
}

try {
// Get raw fingerprint data based on environment
const rawFingerprint = isElectron()
? getElectronFingerprint()
: getWebFingerprint();

// Generate a consistent UUID from the fingerprint data
cachedFingerprint = uuidv5(rawFingerprint, NAMESPACE);
return cachedFingerprint;

} catch (error) {
// Fallback: Generate a random but persistent fingerprint
const storageKey = 'umami-device-id';
let fallbackId = localStorage.getItem(storageKey);

if (!fallbackId) {
fallbackId = uuidv5(Date.now().toString(), NAMESPACE);
localStorage.setItem(storageKey, fallbackId);
}

cachedFingerprint = fallbackId;
return fallbackId;
}
};
55 changes: 44 additions & 11 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit dcb2a1b

Please sign in to comment.