Skip to content

Commit f3e0dc3

Browse files
Merge pull request #5 from oracle/electron-prompt
Adapt electron prompt code for credential password
2 parents bf1b595 + c7c07a8 commit f3e0dc3

File tree

12 files changed

+331
-33
lines changed

12 files changed

+331
-33
lines changed

electron/app/js/i18next.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ i18n.use(i18nextBackend);
4646

4747
// initialize if not already initialized
4848
if (!i18n.isInitialized) {
49-
i18n.init(i18nextOptions);
49+
i18n['whenReady'] = i18n.init(i18nextOptions);
5050
}
5151

5252
module.exports = i18n;

electron/app/js/project.js

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
55
*/
66
const {app, dialog} = require('electron');
7-
const prompt = require('electron-prompt');
7+
const {getCredentialPassphrase} = require('./promptUtils');
88
const {copyFile, mkdir, readFile, writeFile} = require('fs/promises');
99
const path = require('path');
1010
const uuid = require('uuid');
@@ -691,21 +691,17 @@ async function _createCredentialManager(targetWindow, projectFileJsonContent) {
691691
let credentialStorePolicy = _getProjectCredentialStorePolicy(projectFileJsonContent);
692692
return new Promise((resolve, reject) => {
693693
if (credentialStorePolicy === 'passphrase') {
694-
prompt({
695-
title: i18n.t('dialog-passphrase-prompt-title'),
696-
label: i18n.t('dialog-passphrase-prompt-label'),
697-
inputAttrs: { type: 'password', required: true },
698-
resizable: true,
699-
alwaysOnTop: true
700-
}, targetWindow).then(passphrase => {
701-
if (passphrase) {
702-
const credentialManager = new EncryptedCredentialManager(passphrase);
703-
_setCredentialManager(targetWindow, credentialManager);
704-
resolve(credentialManager);
705-
} else {
706-
reject(new Error('Passphrase is required but the user did not provide one.'));
707-
}
708-
}).catch(err => reject(new Error(`Failed to create passphrase credential manager: ${err}`)));
694+
getCredentialPassphrase(targetWindow)
695+
.then(passphrase => {
696+
if (passphrase) {
697+
const credentialManager = new EncryptedCredentialManager(passphrase);
698+
_setCredentialManager(targetWindow, credentialManager);
699+
resolve(credentialManager);
700+
} else {
701+
reject(new Error('Passphrase is required but the user did not provide one.'));
702+
}
703+
})
704+
.catch(err => reject(new Error(`Failed to create passphrase credential manager: ${err}`)));
709705
} else {
710706
let credentialManager;
711707
if (credentialStorePolicy === 'native') {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!DOCTYPE html>
2+
<!--
3+
Copyright (c) 2021, Oracle and/or its affiliates.
4+
Licensed under The Universal Permissive License (UPL), Version 1.0 as shown at https://oss.oracle.com/licenses/upl/
5+
-->
6+
<html lang="en-us">
7+
<head>
8+
<title id="title">...</title>
9+
<link href="prompt.css" rel="stylesheet" />
10+
</head>
11+
<body>
12+
<div id="container">
13+
<form id="form">
14+
<div id="label">...</div>
15+
<div id="data-container">
16+
<input id="data" required type="password">
17+
</div>
18+
<div id="buttons">
19+
<button id="cancel">...</button>
20+
<button type="submit" id="ok">...</button>
21+
</div>
22+
</form>
23+
</div>
24+
<script src="credential-passphrase.js"></script>
25+
</body>
26+
</html>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2021, Oracle and/or its affiliates.
4+
* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
5+
*/
6+
let promptId = null;
7+
let promptOptions = null;
8+
9+
function promptError(error) {
10+
if (error instanceof Error) {
11+
error = error.message;
12+
}
13+
14+
window.api.ipc.sendSync('prompt-error:' + promptId, error);
15+
}
16+
17+
function promptCancel() {
18+
window.api.ipc.sendSync('prompt-post-data:' + promptId, null);
19+
}
20+
21+
function promptSubmit() {
22+
const dataElement = document.querySelector('#data');
23+
const data = dataElement.value;
24+
window.api.ipc.sendSync('prompt-post-data:' + promptId, data);
25+
}
26+
27+
function promptRegister() {
28+
promptId = document.location.hash.replace('#', '');
29+
30+
try {
31+
promptOptions = JSON.parse(window.api.ipc.sendSync('prompt-get-options:' + promptId));
32+
} catch (error) {
33+
return promptError(error);
34+
}
35+
36+
document.querySelector('#form').addEventListener('submit', promptSubmit);
37+
document.querySelector('#cancel').addEventListener('click', promptCancel);
38+
39+
const dataElement = document.querySelector('#data');
40+
dataElement.value = promptOptions.value ? promptOptions.value : '';
41+
42+
dataElement.addEventListener('keyup', event => {
43+
if (event.key === 'Escape') {
44+
promptCancel();
45+
}
46+
});
47+
48+
dataElement.addEventListener('keypress', event => {
49+
if (event.key === 'Enter') {
50+
event.preventDefault();
51+
document.querySelector('#ok').click();
52+
}
53+
});
54+
55+
dataElement.focus();
56+
dataElement.select();
57+
58+
window.api.i18n.ready.then(() => {
59+
document.querySelector('#ok').textContent = window.api.i18n.t('dialog-button-ok');
60+
document.querySelector('#cancel').textContent = window.api.i18n.t('dialog-button-cancel');
61+
document.querySelector('#label').textContent = window.api.i18n.t('dialog-passphrase-prompt-label');
62+
document.querySelector('#title').textContent = window.api.i18n.t('dialog-passphrase-prompt-title');
63+
64+
const height = document.querySelector('body').offsetHeight;
65+
window.api.ipc.sendSync('prompt-size:' + promptId, height);
66+
});
67+
}
68+
69+
window.addEventListener('error', error => {
70+
if (promptId) {
71+
promptError('An error has occurred on the prompt window: \n' + error);
72+
}
73+
});
74+
75+
document.addEventListener('DOMContentLoaded', promptRegister);

electron/app/js/prompt/preload.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2021, Oracle and/or its affiliates.
4+
* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
5+
*/
6+
const { contextBridge, ipcRenderer } = require('electron');
7+
const i18n = require('../i18next.webui.config');
8+
const osUtils = require('../osUtils');
9+
10+
const language = osUtils.getArgv('--lang');
11+
const i18nReady = i18n.changeLanguage(language);
12+
13+
contextBridge.exposeInMainWorld(
14+
'api',
15+
{
16+
ipc: {
17+
sendSync: (channel, ...args) => {
18+
return ipcRenderer.sendSync(channel, ...args);
19+
}
20+
},
21+
i18n: {
22+
ready: i18nReady,
23+
t: (keys, options) => {
24+
return i18n.t(keys, options);
25+
}
26+
}
27+
}
28+
);

electron/app/js/prompt/prompt.css

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (c) 2021, Oracle and/or its affiliates.
3+
* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
4+
*/
5+
body {
6+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, sans-serif;
7+
line-height: 1.5em;
8+
color: #333;
9+
background-color: #fff;
10+
padding: 0;
11+
margin: 0;
12+
}
13+
14+
#container {
15+
align-items: center;
16+
justify-content: center;
17+
display: flex;
18+
height: 100%;
19+
overflow: auto;
20+
padding: 1em;
21+
}
22+
23+
#form {
24+
width: 100%;
25+
}
26+
27+
#label, .label {
28+
max-width: 100%;
29+
max-height: 100%;
30+
margin-bottom: .8em;
31+
white-space: nowrap;
32+
overflow: hidden;
33+
text-overflow: ellipsis;
34+
}
35+
36+
#data, .data {
37+
box-sizing: border-box;
38+
border-radius: 2px;
39+
background: #fff;
40+
width: 100%;
41+
padding: .4em .5em;
42+
border: 1px solid black;
43+
min-height: 2.5em;
44+
margin: 0 0 1.2em;
45+
}
46+
47+
select#data {
48+
height: 2em;
49+
}
50+
51+
#data-container, .data-container {
52+
text-align: center;
53+
}
54+
55+
#buttons {
56+
text-align: right;
57+
}
58+
59+
#buttons > button,
60+
#buttons > input[type=submit] {
61+
border-radius: 2px;
62+
border: 0;
63+
margin: 0 0 0 .5em;
64+
font-size: .8em;
65+
line-height: 1em;
66+
padding: .6em 1em
67+
}
68+
69+
#ok {
70+
background-color: #3879D9;
71+
color: white;
72+
}
73+
74+
#cancel {
75+
background-color: #DDD;
76+
color: black;
77+
}

electron/app/js/promptUtils.js

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2021, Oracle and/or its affiliates.
4+
* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
5+
*/
6+
const path = require('path');
7+
const { BrowserWindow, ipcMain } = require('electron');
8+
9+
/* global __dirname */
10+
11+
async function getCredentialPassphrase(parentWindow) {
12+
const pageFile = path.join(__dirname, 'prompt', 'credential-passphrase.html');
13+
const preloadFile = path.join(__dirname, 'prompt', 'preload.js');
14+
15+
const WIDTH = 550;
16+
const HEIGHT = 146; // renderer will send IPC to adjust this
17+
const MIN_HEIGHT = 120; // needs to be smaller than content height
18+
19+
return new Promise((resolve, reject) => {
20+
21+
let promptWindow = new BrowserWindow({
22+
width: WIDTH,
23+
height: HEIGHT,
24+
minWidth: WIDTH,
25+
minHeight: MIN_HEIGHT,
26+
resizable: true,
27+
minimizable: false,
28+
fullscreenable: false,
29+
maximizable: false,
30+
parent: parentWindow,
31+
skipTaskbar: true,
32+
alwaysOnTop: false,
33+
useContentSize: true,
34+
modal: Boolean(parentWindow),
35+
menuBarVisible: false,
36+
webPreferences: {
37+
nodeIntegration: false,
38+
contextIsolation: true,
39+
enableRemoteModule: false,
40+
webviewTag: false,
41+
preload: preloadFile
42+
},
43+
});
44+
45+
promptWindow.setMenu(null);
46+
promptWindow.setMenuBarVisibility(false);
47+
48+
const getOptionsListener = event => {
49+
event.returnValue = JSON.stringify({});
50+
};
51+
52+
const sizeListener = (event, value) => {
53+
event.returnValue = null;
54+
promptWindow.setContentSize(WIDTH, value);
55+
promptWindow.center();
56+
};
57+
58+
const id = promptWindow.id.toString();
59+
60+
const cleanup = () => {
61+
ipcMain.removeListener('prompt-get-options:' + id, getOptionsListener);
62+
ipcMain.removeListener('prompt-post-data:' + id, postDataListener);
63+
ipcMain.removeListener('prompt-error:' + id, errorListener);
64+
ipcMain.removeListener('prompt-size:' + id, sizeListener);
65+
66+
if (promptWindow) {
67+
promptWindow.close();
68+
promptWindow = null;
69+
}
70+
};
71+
72+
const postDataListener = (event, value) => {
73+
resolve(value);
74+
event.returnValue = null;
75+
cleanup();
76+
};
77+
78+
const unresponsiveListener = () => {
79+
reject(new Error('Window was unresponsive'));
80+
cleanup();
81+
};
82+
83+
const errorListener = (event, message) => {
84+
reject(new Error(message));
85+
event.returnValue = null;
86+
cleanup();
87+
};
88+
89+
ipcMain.on('prompt-get-options:' + id, getOptionsListener);
90+
ipcMain.on('prompt-post-data:' + id, postDataListener);
91+
ipcMain.on('prompt-error:' + id, errorListener);
92+
ipcMain.on('prompt-size:' + id, sizeListener);
93+
promptWindow.on('unresponsive', unresponsiveListener);
94+
95+
promptWindow.on('closed', () => {
96+
promptWindow = null;
97+
cleanup();
98+
resolve(null);
99+
});
100+
101+
promptWindow.loadFile(pageFile,{hash: id});
102+
});
103+
}
104+
105+
module.exports = {
106+
getCredentialPassphrase
107+
};

electron/app/locales/en/electron.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,6 @@
125125
"dialog-invalid-oracle-home-title": "Invalid Oracle Home Directory",
126126
"dialog-invalid-oracle-home-not-specified": "Oracle Home is not specified.",
127127
"dialog-invalid-oracle-home-not-valid": "Specified Oracle Home {{oracleHome}} is not valid.",
128-
"dialog-passphrase-prompt-title": "Credential Encryption Passphrase",
129-
"dialog-passphrase-prompt-label": "Please enter the passphrase to use to encrypt the project credential.",
130128

131129
"dialog-title-closeProject": "Close Project",
132130

electron/app/locales/en/webui.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@
144144
"user-settings-dialog-quickstart-skip-label": "Skip Showing Introduction at Startup",
145145
"user-settings-dialog-quickstart-skip-help": "Whether or not to skip showing the WebLogic Kubernetes Toolkit UI Introduction at application startup.",
146146

147+
"dialog-passphrase-prompt-title": "Credential Encryption Passphrase",
148+
"dialog-passphrase-prompt-label": "Please enter the passphrase to use to encrypt the project credential.",
149+
147150
"model-design-coming-soon": "Coming Soon...",
148151

149152
"image-page-hints-createImage": "Create Image",

0 commit comments

Comments
 (0)