Skip to content

Commit

Permalink
feat: implements wallet auto-lock feature (#678)
Browse files Browse the repository at this point in the history
  • Loading branch information
jinoosss authored Jan 30, 2025
1 parent 1747c1d commit 268ea08
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 51 deletions.
2 changes: 1 addition & 1 deletion packages/adena-extension/public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"background": {
"service_worker": "background.js"
},
"permissions": ["unlimitedStorage", "storage", "tabs"],
"permissions": ["unlimitedStorage", "storage", "tabs", "alarms"],
"content_scripts": [
{
"matches": ["<all_urls>"],
Expand Down
27 changes: 27 additions & 0 deletions packages/adena-extension/src/background.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { AlarmKey } from '@common/constants/alarm-key.constant';
import { MemoryProvider } from '@common/provider/memory/memory-provider';
import { ChromeLocalStorage } from '@common/storage';
import { CommandHandler } from '@inject/message/command-handler';
import { isCommandMessageData } from '@inject/message/command-message';
import { clearInMemoryKey } from '@inject/message/commands/encrypt';
import { MessageHandler } from './inject/message';

const inMemoryProvider = new MemoryProvider();
Expand Down Expand Up @@ -50,6 +52,31 @@ chrome.action.onClicked.addListener(async () => {
});
});

chrome.runtime.onConnect.addListener((port) => {
inMemoryProvider.addConnection();

port.onDisconnect.addListener(() => {
inMemoryProvider.removeConnection();
chrome.alarms.clear(AlarmKey.EXPIRED_PASSWORD);

if (!inMemoryProvider.isActive()) {
chrome.alarms.clear(AlarmKey.EXPIRED_PASSWORD);
chrome.alarms.create(AlarmKey.EXPIRED_PASSWORD, {
delayInMinutes: inMemoryProvider.getExpiredPasswordDurationMinutes(),
});
}
});
});

chrome.alarms.onAlarm.addListener(async (alarm) => {
if (alarm.name === AlarmKey.EXPIRED_PASSWORD) {
await chrome.storage.session.clear();
await clearInMemoryKey(inMemoryProvider);

return;
}
});

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (isCommandMessageData(message)) {
CommandHandler.createHandler(inMemoryProvider, message, sender, sendResponse);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum AlarmKey {
EXPIRED_PASSWORD = 'EXPIRED_PASSWORD',
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,43 @@
const EXPIRED_PASSWORD_DURATION_MIN = 5;

export class MemoryProvider {
private memory: Map<string, any> = new Map();
private activeConnections = 0;
private expiredPasswordDuration = EXPIRED_PASSWORD_DURATION_MIN;

public get = <T = any>(key: string): T | null => {
if (!this.memory.get(key)) {
return null;
}

public get = <T = any>(key: string): T => {
return this.memory.get(key) as T;
};

public set = <T = any>(key: string, value: T): void => {
public set = <T = any>(key: string, value: T | null): void => {
this.memory.set(key, value);
};

public async init(): Promise<void> {
this.memory = new Map();
}

public addConnection(): void {
this.activeConnections++;
}

public removeConnection(): void {
this.activeConnections--;
}

public isActive(): boolean {
return this.activeConnections > 0;
}

public getExpiredPasswordDurationMinutes(): number {
return this.expiredPasswordDuration;
}

public setExpiredPasswordDurationMinutes(duration: number): void {
this.expiredPasswordDuration = duration;
}
}
4 changes: 2 additions & 2 deletions packages/adena-extension/src/common/utils/crypto-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const encryptPassword = async (
password: string,
): Promise<{ encryptedKey: string; encryptedPassword: string }> => {
const result = await sendMessage(CommandMessage.command('encryptPassword', { password }));
if (!result.data) {
if (result.code !== 200) {
throw new Error('Encryption key not initialized.');
}

Expand All @@ -28,7 +28,7 @@ export const decryptPassword = async (iv: string, encryptedPassword: string): Pr
encryptedPassword,
}),
);
if (!result.data) {
if (result.code !== 200 || !result.data?.password) {
throw new Error('Encryption key not initialized.');
}

Expand Down
93 changes: 51 additions & 42 deletions packages/adena-extension/src/inject/message/command-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,60 +15,54 @@ export class CommandHandler {
_: chrome.runtime.MessageSender,
sendResponse: (response?: CommandMessageData) => void,
): Promise<void> => {
if (message.code !== 0) {
return;
}

if (message.command === 'encryptPassword') {
const key = await getInMemoryKey(inMemoryProvider);
if (!key) {
sendResponse({
...message,
code: 500,
});
try {
if (message.code !== 0) {
return;
}

const password = message.data.password;
const resultData = await encryptPassword(key, password);
if (message.command === 'encryptPassword') {
if (!message.data.password) {
throw new Error('Password is required');
}

sendResponse({
...message,
code: 200,
data: resultData,
});
const key = await getInMemoryKey(inMemoryProvider);
if (!key) {
throw new Error('Failed to get in-memory key');
}

return;
}
const password = message.data.password;
const responseData = await encryptPassword(key, password);

if (message.command === 'decryptPassword') {
const key = await getInMemoryKey(inMemoryProvider);
if (!key) {
sendResponse({
...message,
code: 500,
});
sendResponse(makeSuccessResponse(message, responseData));
return;
}

const iv = message.data.iv;
const encryptedPassword = message.data.encryptedPassword;
const decryptedPassword = await decryptPassword(key, iv, encryptedPassword);
if (message.command === 'decryptPassword') {
const key = await getInMemoryKey(inMemoryProvider);
if (!key) {
throw new Error('Failed to in-memory key');
}

const iv = message.data.iv;
const encryptedPassword = message.data.encryptedPassword;
const decryptedPassword = await decryptPassword(key, iv, encryptedPassword);

sendResponse({
...message,
code: 200,
data: {
const responseData = {
password: decryptedPassword,
},
});
return;
}
};

if (message.command === 'clearEncryptKey') {
await clearInMemoryKey(inMemoryProvider);
sendResponse({ ...message, code: 200 });
return;
sendResponse(makeSuccessResponse(message, responseData));
return;
}

if (message.command === 'clearEncryptKey') {
await clearInMemoryKey(inMemoryProvider);
sendResponse(makeSuccessResponse(message));
return;
}
} catch (error) {
console.error(error);
sendResponse(makeInternalErrorResponse(message));
}

if (message.command === 'clearPopup') {
Expand All @@ -79,3 +73,18 @@ export class CommandHandler {
}
};
}

function makeSuccessResponse(message: CommandMessageData, data: any = null): CommandMessageData {
return {
...message,
code: 200,
data,
};
}

function makeInternalErrorResponse(message: CommandMessageData): CommandMessageData {
return {
...message,
code: 500,
};
}
6 changes: 3 additions & 3 deletions packages/adena-extension/src/repositories/wallet/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,15 @@ export class WalletRepository {
};

public getWalletPassword = async (): Promise<string> => {
const encryptedKey = await this.sessionStorage.get('ENCRYPTED_KEY');
const iv = await this.sessionStorage.get('ENCRYPTED_KEY');
const encryptedPassword = await this.sessionStorage.get('ENCRYPTED_PASSWORD');

if (encryptedKey === '' || encryptedPassword === '') {
if (iv === '' || encryptedPassword === '') {
throw new WalletError('NOT_FOUND_PASSWORD');
}

try {
const password = await decryptPassword(encryptedKey, encryptedPassword);
const password = await decryptPassword(iv, encryptedPassword);
this.updateStoragePassword(password);

return password;
Expand Down
1 change: 0 additions & 1 deletion packages/adena-module/src/crypto/libsodium.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export class Argon2id {
options: Argon2idOptions,
): Promise<Uint8Array> {
await sodium.ready;
console.log(sodium);
return sodium.crypto_pwhash(
options.outputLength,
password,
Expand Down

0 comments on commit 268ea08

Please sign in to comment.