diff --git a/packages/adena-extension/public/manifest.json b/packages/adena-extension/public/manifest.json index c5a7329c..3572482e 100644 --- a/packages/adena-extension/public/manifest.json +++ b/packages/adena-extension/public/manifest.json @@ -13,7 +13,7 @@ "background": { "service_worker": "background.js" }, - "permissions": ["unlimitedStorage", "storage", "tabs"], + "permissions": ["unlimitedStorage", "storage", "tabs", "alarms"], "content_scripts": [ { "matches": [""], diff --git a/packages/adena-extension/src/background.ts b/packages/adena-extension/src/background.ts index a04b96a4..28be1281 100644 --- a/packages/adena-extension/src/background.ts +++ b/packages/adena-extension/src/background.ts @@ -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(); @@ -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); diff --git a/packages/adena-extension/src/common/constants/alarm-key.constant.ts b/packages/adena-extension/src/common/constants/alarm-key.constant.ts new file mode 100644 index 00000000..acb3247b --- /dev/null +++ b/packages/adena-extension/src/common/constants/alarm-key.constant.ts @@ -0,0 +1,3 @@ +export enum AlarmKey { + EXPIRED_PASSWORD = 'EXPIRED_PASSWORD', +} diff --git a/packages/adena-extension/src/common/provider/memory/memory-provider.ts b/packages/adena-extension/src/common/provider/memory/memory-provider.ts index 9495ceee..0d3a89ee 100644 --- a/packages/adena-extension/src/common/provider/memory/memory-provider.ts +++ b/packages/adena-extension/src/common/provider/memory/memory-provider.ts @@ -1,15 +1,43 @@ +const EXPIRED_PASSWORD_DURATION_MIN = 5; + export class MemoryProvider { private memory: Map = new Map(); + private activeConnections = 0; + private expiredPasswordDuration = EXPIRED_PASSWORD_DURATION_MIN; + + public get = (key: string): T | null => { + if (!this.memory.get(key)) { + return null; + } - public get = (key: string): T => { return this.memory.get(key) as T; }; - public set = (key: string, value: T): void => { + public set = (key: string, value: T | null): void => { this.memory.set(key, value); }; public async init(): Promise { 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; + } } diff --git a/packages/adena-extension/src/common/utils/crypto-utils.ts b/packages/adena-extension/src/common/utils/crypto-utils.ts index ae1a2533..d7f2e59d 100644 --- a/packages/adena-extension/src/common/utils/crypto-utils.ts +++ b/packages/adena-extension/src/common/utils/crypto-utils.ts @@ -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.'); } @@ -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.'); } diff --git a/packages/adena-extension/src/inject/message/command-handler.ts b/packages/adena-extension/src/inject/message/command-handler.ts index 136e11eb..ea0a063c 100644 --- a/packages/adena-extension/src/inject/message/command-handler.ts +++ b/packages/adena-extension/src/inject/message/command-handler.ts @@ -15,60 +15,54 @@ export class CommandHandler { _: chrome.runtime.MessageSender, sendResponse: (response?: CommandMessageData) => void, ): Promise => { - 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') { @@ -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, + }; +} diff --git a/packages/adena-extension/src/repositories/wallet/wallet.ts b/packages/adena-extension/src/repositories/wallet/wallet.ts index abee6e34..f33ee579 100644 --- a/packages/adena-extension/src/repositories/wallet/wallet.ts +++ b/packages/adena-extension/src/repositories/wallet/wallet.ts @@ -62,15 +62,15 @@ export class WalletRepository { }; public getWalletPassword = async (): Promise => { - 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; diff --git a/packages/adena-module/src/crypto/libsodium.ts b/packages/adena-module/src/crypto/libsodium.ts index fb9f0c29..02a9f485 100644 --- a/packages/adena-module/src/crypto/libsodium.ts +++ b/packages/adena-module/src/crypto/libsodium.ts @@ -40,7 +40,6 @@ export class Argon2id { options: Argon2idOptions, ): Promise { await sodium.ready; - console.log(sodium); return sodium.crypto_pwhash( options.outputLength, password,