diff --git a/shared/enclave/server/views/passphrase.html b/shared/enclave/server/views/passphrase.html new file mode 100644 index 00000000..11f395ef --- /dev/null +++ b/shared/enclave/server/views/passphrase.html @@ -0,0 +1,154 @@ + + + + TREZOR - Enter Passphrase + + + + + +

Enter your passphrase

+ +
+ + + +
+ + + + + + + + + + + \ No newline at end of file diff --git a/shared/enclave/server/views/passphrase.ts b/shared/enclave/server/views/passphrase.ts new file mode 100644 index 00000000..e701c90d --- /dev/null +++ b/shared/enclave/server/views/passphrase.ts @@ -0,0 +1,8 @@ +import showPrompt from './showPrompt'; +import template from './passphrase.html'; + +const EVENT = 'enclave:passphrase'; + +export function showPassphrasePrompt(): Promise { + return showPrompt(template, EVENT); +} diff --git a/shared/enclave/server/views/pin.html b/shared/enclave/server/views/pin.html index 291937b4..c2250161 100644 --- a/shared/enclave/server/views/pin.html +++ b/shared/enclave/server/views/pin.html @@ -1,226 +1,248 @@ - - TREZOR - Enter PIN - - - -

Enter your PIN

-

Look at your device for the number positions

-
- - - + + TREZOR - Enter PIN + + - - - + +

Enter your PIN

+

Look at your device for the number positions

- - - -
+
+ + + -
- - + + + - -
+ + + +
- + + + + - - + close.addEventListener('click', function () { + var window = remote.getCurrentWindow(); + window.close(); + }); + - + - - - + .pin-buttons { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + margin: 0 auto; + width: 180px; + height: 180px; + } + + .pin-button { + position: relative; + width: 54px; + height: 54px; + border-radius: 4px; + border: 1px solid #FFF; + background: rgba(255, 255, 255, 0.1); + transition: all 120ms ease; + opacity: 0.4; + } + + .pin-button:hover { + opacity: 0.8; + } + + .pin-button:active { + opacity: 1; + } + + .pin-button:focus { + opacity: 1; + } + + .pin-button:after { + content: ''; + position: absolute; + width: 8px; + height: 8px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + border-radius: 100%; + background: #FFF; + } + + .pin-controls { + position: relative; + width: 180px; + margin: 0 auto; + } + + .pin-input { + width: 100%; + background: none; + border: none; + border-bottom: 1px solid #FFF; + opacity: 0.7; + font-size: 32px; + padding: 0 4px; + margin-bottom: 10px; + letter-spacing: 3px; + color: #FFF; + } + + .pin-clear { + position: absolute; + right: 0; + top: 11px; + padding: 4px; + border-radius: 100%; + border: 2px solid; + opacity: 0.6; + } + + .pin-clear:hover { + opacity: 0.8; + } + + .pin-clear:active { + opacity: 1; + } + + .pin-unlock { + width: 100%; + padding: 6px; + opacity: 0.6; + text-align: center; + font-size: 10px; + border: 1px solid #FFF; + border-radius: 2px; + text-transform: uppercase; + letter-spacing: 1.2px; + } + + .pin-unlock:hover { + opacity: 0.8; + } + + .pin-unlock:active { + opacity: 1; + } + + .close { + position: absolute; + top: 10px; + right: 10px; + width: 14px; + height: 14px; + opacity: 0.3; + overflow: hidden; + } + + .close:hover, + .close:focus { + opacity: 1; + } + + button { + display: block; + background: none; + border: none; + padding: 0; + cursor: pointer; + color: #FFF; + outline: none; + } + + + + \ No newline at end of file diff --git a/shared/enclave/server/views/pin.ts b/shared/enclave/server/views/pin.ts index fc54d842..ebe9b038 100644 --- a/shared/enclave/server/views/pin.ts +++ b/shared/enclave/server/views/pin.ts @@ -1,40 +1,8 @@ -import { BrowserWindow, ipcMain, IpcMessageEvent } from 'electron'; +import showPrompt from './showPrompt'; import template from './pin.html'; const EVENT = 'enclave:pin'; export function showPinPrompt(): Promise { - return new Promise((resolve, reject) => { - const scriptNonce = Math.floor(Math.random() * 1000000000000); - const html = template - .replace(/\$scriptNonce/g, scriptNonce.toString()) - .replace(/\$EVENT/g, EVENT); - - let hasResolved = false; - - const window = new BrowserWindow({ - width: 320, - height: 380, - frame: false, - backgroundColor: '#21252B', - darkTheme: true - }); - - window.on('closed', () => { - if (hasResolved) { - return; - } - reject(new Error('ENCLAVE_TREZOR_CANCELED')); - }); - - ipcMain.once(EVENT, (_: IpcMessageEvent, pin: string) => { - resolve(pin); - hasResolved = true; - window.close(); - }); - - window.loadURL(`data:text/html;charset=UTF-8,${encodeURIComponent(html)}`); - window.show(); - window.focus(); - }); + return showPrompt(template, EVENT); } diff --git a/shared/enclave/server/views/showPrompt.ts b/shared/enclave/server/views/showPrompt.ts new file mode 100644 index 00000000..0631dfb5 --- /dev/null +++ b/shared/enclave/server/views/showPrompt.ts @@ -0,0 +1,45 @@ +import { BrowserWindow, ipcMain, IpcMessageEvent } from 'electron'; + +export default function showPrompt(template: string, event: string): Promise { + return new Promise((resolve, reject) => { + const scriptNonce = Math.floor(Math.random() * 1000000000000); + const html = template + .replace(/\$scriptNonce/g, scriptNonce.toString()) + .replace(/\$EVENT/g, event); + + let hasResolved = false; + + const window = new BrowserWindow({ + width: 320, + height: 380, + frame: false, + backgroundColor: '#21252B', + darkTheme: true + }); + + window.on('closed', () => { + if (hasResolved) { + return; + } + reject(new Error('ENCLAVE_TREZOR_CANCELED')); + }); + + ipcMain.once(event, (_: IpcMessageEvent, value: string) => { + try { + resolve(value); + hasResolved = true; + window.close(); + } catch (e) { + /** + * @desc The window.close call sometimes fails + * if the window has already been destroyed. + */ + console.error(e); + } + }); + + window.loadURL(`data:text/html;charset=UTF-8,${encodeURIComponent(html)}`); + window.show(); + window.focus(); + }); +} diff --git a/shared/enclave/server/wallets/trezor.ts b/shared/enclave/server/wallets/trezor.ts index 02f8684b..f54b6f35 100644 --- a/shared/enclave/server/wallets/trezor.ts +++ b/shared/enclave/server/wallets/trezor.ts @@ -7,6 +7,7 @@ import { WalletLib } from 'shared/enclave/types'; import { padLeftEven } from 'libs/values'; import { stripHexPrefixAndLower } from 'libs/formatters'; import { showPinPrompt } from '../views/pin'; +import { showPassphrasePrompt } from '../views/passphrase'; const deviceList = new DeviceList({ debug: false }); @@ -35,6 +36,16 @@ async function getSession() { cb(err); }); }); + device.on('passphrase', (cb: (err?: Error, passphrase?: string) => void) => { + showPassphrasePrompt() + .then(passphrase => { + cb(undefined, passphrase); + }) + .catch(err => { + console.error('Passphrase entry failed', err); + cb(err); + }); + }); currentSession = session; return currentSession;