Platform independent simple password manager application (Electron & NodeJS)
Electron is a framework for creating native applications with web technologies including JavaScript, HTML, and CSS. Electron enables create desktop applications with pure JavaScript by providing a runtime with rich native (operating system) APIs. It allows for the development of desktop GUI applications using Node.js runtime for the backend and Chromium for the frontend component.
As a sample platform independent desktop application, I created the JPwdManager, a simple platform independent password manager application.
An Electron application is essentially a Node.js application. The starting point is a package.json that is identical to that of a Node.js module.
The script specified by the main field is the startup script of your app, which will run the main process. Our package.json file is as follows:
{
"name": "jpwdmanager",
"version": "1.0.1",
"description": "platform independent simple password manager app",
"main": "main.js",
"scripts": {
"start": "electron .",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ahmetcanozturk/JPwdManager.git"
},
"keywords": [
"password manager",
"javascript",
"nodejs",
"electron"
],
"author": "Ahmetcan Ozturk",
"license": "GPL-3.0",
"bugs": {
"url": "https://github.com/ahmetcanozturk/JPwdManager/issues"
},
"homepage": "https://github.com/ahmetcanozturk/JPwdManager#readme",
"devDependencies": {
"electron": "^3.0.6"
},
"dependencies": {}
}
In order to build the application first install Electron itself.
npm install --save-dev electron
Electron apps are developed using the same principles and methods found in Node.js development. All APIs and features found in Electron are accessible through the electron module.
The lifecycle of the application is managed through electron.app, windows can be created using the electron.BrowserWindow class. The main.js file is the entry file for the application. It might wait for the application to be ready and open a window.
let window = null
app.once('ready', () => {
window = new BrowserWindow({
width: 800,
height: 600,
show: false
})
window.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
}))
window.once('ready-to-show', () => {
window.show();
})
});
The main.js create windows and handle all the system events our application might encounter.
Our main.js is as follows:
const {app, BrowserWindow, Menu, dialog} = require('electron');
const path = require('path');
const url = require('url');
let window = null
app.once('ready', () => {
window = new BrowserWindow({
width: 800,
height: 600,
backgroundColor: "#6a6a6a",
show: false
})
var mtemplate = [
{ label: "JPwdManager", submenu: [
{ type: 'normal', label: 'Quit', accelerator: 'Command+Q', click: () => { app.quit(); } }
]
},
{
label: 'Edit',
submenu: [{role: 'undo'},{role: 'redo'},{type: 'separator'},{role: 'cut'},{role: 'copy'},{role: 'paste'},{role: 'pasteandmatchstyle'},{role: 'delete'},{role: 'selectall'}]
},
{
label: 'View',
submenu: [{role: 'reload'},{role: 'forcereload'},{role: 'toggledevtools'},{type: 'separator'},{role: 'resetzoom'},{role: 'zoomin'},{role: 'zoomout'},{type: 'separator'},{role: 'togglefullscreen'}]
},
{
role: 'window',
submenu: [{role: 'minimize'},{role: 'close'}]
},
{
role: "Help", submenu: [{ label: "About", click: () => {
dialog.showMessageBox(window, {title: "About", message: "written by Ahmetcan Ozturk\nhttps://github.com/ahmetcanozturk/"});
} }]
}
];
const menu = Menu.buildFromTemplate(mtemplate);
Menu.setApplicationMenu(menu);
window.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
}))
window.once('ready-to-show', () => {
window.show();
})
});
app.on('window-all-closed', () => {
app.quit();
});
index.html is the main html page to show.
The template.js file contain the user interface templates.
The templateHelper.js file contain the methods for user interface constructiona and user interface interactions.
exports.templateContent = (element, index) => {
var content = templates.pwdItemTemplate.replace('%KEY%', cryp.decrypt(element.key)).replace('%VALUE%', cryp.decrypt(element.value)).replace('%HDNKEY%', cryp.decrypt(element.key));
content = content.replace('%ITEMID%', "item".concat("_", index.toString()));
content = content.replace('%KEYID%', "key".concat("_", index.toString()));
content = content.replace('%HDNKEYID%', "hdn".concat("_", index.toString()));
content = content.replace('%VALUEID%', "value".concat("_", index.toString()));
content = content.replace('%GENBTNID%', "gbtn".concat("_", index.toString()));
content = content.replace('%SAVEBTNID%', "sbtn".concat("_", index.toString()));
content = content.replace('%DELBTNID%', "dbtn".concat("_", index.toString()));
return content;
}
exports.templateBinder = (wutil) => {
$(".value").bind("focus", function() {
wutil.showPassword(this);
});
$(".value").bind("blur", function() {
wutil.hidePassword(this);
});
$(".generate").bind("click", function() {
wutil.generatePassword(this);
});
$(".save").bind("click", function() {
wutil.updateItem(this);
});
$(".delete" ).bind("click", function() {
wutil.deleteItem(this);
});
}
The windowUtil.js file contain the user interface methods. The window.js file requires windowsUtil and templateHelper contain user interface on window load methods.
JPwdManager encrypts the password to be stored using specified algorithm specified in settings.json file and stores it to data.json file. Typical settings.json file:
{
"IV_LENGTH" : 16,
"ALGORITHM" : "AES-256-CBC",
"PWDLENGTH" : 16
}
GitHub repository of the application is here https://github.com/ahmetcanozturk/JPwdManager
As a sample platform independent desktop application, I created the JPwdManager, a simple platform independent password manager application.
An Electron application is essentially a Node.js application. The starting point is a package.json that is identical to that of a Node.js module.
The script specified by the main field is the startup script of your app, which will run the main process. Our package.json file is as follows:
{
"name": "jpwdmanager",
"version": "1.0.1",
"description": "platform independent simple password manager app",
"main": "main.js",
"scripts": {
"start": "electron .",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ahmetcanozturk/JPwdManager.git"
},
"keywords": [
"password manager",
"javascript",
"nodejs",
"electron"
],
"author": "Ahmetcan Ozturk",
"license": "GPL-3.0",
"bugs": {
"url": "https://github.com/ahmetcanozturk/JPwdManager/issues"
},
"homepage": "https://github.com/ahmetcanozturk/JPwdManager#readme",
"devDependencies": {
"electron": "^3.0.6"
},
"dependencies": {}
}
In order to build the application first install Electron itself.
npm install --save-dev electron
Electron apps are developed using the same principles and methods found in Node.js development. All APIs and features found in Electron are accessible through the electron module.
The lifecycle of the application is managed through electron.app, windows can be created using the electron.BrowserWindow class. The main.js file is the entry file for the application. It might wait for the application to be ready and open a window.
let window = null
app.once('ready', () => {
window = new BrowserWindow({
width: 800,
height: 600,
show: false
})
window.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
}))
window.once('ready-to-show', () => {
window.show();
})
});
The main.js create windows and handle all the system events our application might encounter.
Our main.js is as follows:
const {app, BrowserWindow, Menu, dialog} = require('electron');
const path = require('path');
const url = require('url');
let window = null
app.once('ready', () => {
window = new BrowserWindow({
width: 800,
height: 600,
backgroundColor: "#6a6a6a",
show: false
})
var mtemplate = [
{ label: "JPwdManager", submenu: [
{ type: 'normal', label: 'Quit', accelerator: 'Command+Q', click: () => { app.quit(); } }
]
},
{
label: 'Edit',
submenu: [{role: 'undo'},{role: 'redo'},{type: 'separator'},{role: 'cut'},{role: 'copy'},{role: 'paste'},{role: 'pasteandmatchstyle'},{role: 'delete'},{role: 'selectall'}]
},
{
label: 'View',
submenu: [{role: 'reload'},{role: 'forcereload'},{role: 'toggledevtools'},{type: 'separator'},{role: 'resetzoom'},{role: 'zoomin'},{role: 'zoomout'},{type: 'separator'},{role: 'togglefullscreen'}]
},
{
role: 'window',
submenu: [{role: 'minimize'},{role: 'close'}]
},
{
role: "Help", submenu: [{ label: "About", click: () => {
dialog.showMessageBox(window, {title: "About", message: "written by Ahmetcan Ozturk\nhttps://github.com/ahmetcanozturk/"});
} }]
}
];
const menu = Menu.buildFromTemplate(mtemplate);
Menu.setApplicationMenu(menu);
window.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
}))
window.once('ready-to-show', () => {
window.show();
})
});
app.on('window-all-closed', () => {
app.quit();
});
index.html is the main html page to show.
The template.js file contain the user interface templates.
The templateHelper.js file contain the methods for user interface constructiona and user interface interactions.
exports.templateContent = (element, index) => {
var content = templates.pwdItemTemplate.replace('%KEY%', cryp.decrypt(element.key)).replace('%VALUE%', cryp.decrypt(element.value)).replace('%HDNKEY%', cryp.decrypt(element.key));
content = content.replace('%ITEMID%', "item".concat("_", index.toString()));
content = content.replace('%KEYID%', "key".concat("_", index.toString()));
content = content.replace('%HDNKEYID%', "hdn".concat("_", index.toString()));
content = content.replace('%VALUEID%', "value".concat("_", index.toString()));
content = content.replace('%GENBTNID%', "gbtn".concat("_", index.toString()));
content = content.replace('%SAVEBTNID%', "sbtn".concat("_", index.toString()));
content = content.replace('%DELBTNID%', "dbtn".concat("_", index.toString()));
return content;
}
exports.templateBinder = (wutil) => {
$(".value").bind("focus", function() {
wutil.showPassword(this);
});
$(".value").bind("blur", function() {
wutil.hidePassword(this);
});
$(".generate").bind("click", function() {
wutil.generatePassword(this);
});
$(".save").bind("click", function() {
wutil.updateItem(this);
});
$(".delete" ).bind("click", function() {
wutil.deleteItem(this);
});
}
The windowUtil.js file contain the user interface methods. The window.js file requires windowsUtil and templateHelper contain user interface on window load methods.
JPwdManager encrypts the password to be stored using specified algorithm specified in settings.json file and stores it to data.json file. Typical settings.json file:
{
"IV_LENGTH" : 16,
"ALGORITHM" : "AES-256-CBC",
"PWDLENGTH" : 16
}
The handleData.js file contain the methods for loading, reading and writing data.json file.
The utils.js contain utility methods like random password generation method.
// password length for random generation
const PWDLENGTH = settings.PWDLENGTH;
// creates cryptographically strong random and complex password
exports.generateRandomPassword = () => {
var alphabet = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwYyZz";
var numbers = "0123456789";
var signs = "!-@*";
var random = [];
var array = new Uint32Array(PWDLENGTH);
//the Crypto.getRandomValues() method creates cryptographically strong random values
window.crypto.getRandomValues(array);
array.forEach(element => {
if (element % 3 == 0)
random.push(alphabet[element % 50]);
else if (element % 3 == 1)
random.push(numbers[element % 10]);
else if (element % 3 == 2)
random.push(signs[element % 4]);
});
return random.join("");
}
Comments
Post a Comment