mirror of
https://github.com/bitwarden/web
synced 2025-12-06 00:03:28 +00:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b583bea9b | ||
|
|
3f0ca412c6 | ||
|
|
0050b570b4 | ||
|
|
9405be03b0 | ||
|
|
b5b706fe06 | ||
|
|
8313d9fa90 | ||
|
|
93b96b3be7 | ||
|
|
50e6818f2b | ||
|
|
104fb57bd8 | ||
|
|
7ba6b2f00a | ||
|
|
d8e8939eca | ||
|
|
26c5f4049d | ||
|
|
b6c9dba0fc | ||
|
|
8badc1a354 | ||
|
|
15097eb1f0 | ||
|
|
986306f811 | ||
|
|
c5de290dd4 | ||
|
|
21e9e083f5 | ||
|
|
517ea65bc9 | ||
|
|
e7a9699226 | ||
|
|
52b3dfd0e3 | ||
|
|
4c0bde9d87 | ||
|
|
f29bbe4316 | ||
|
|
3c03ed8636 | ||
|
|
71ca4eb84a | ||
|
|
30d19b4ee1 | ||
|
|
7b88d30aa8 | ||
|
|
0ae11cc40c | ||
|
|
dd5cda867d | ||
|
|
2d11bef262 | ||
|
|
5d5d0bfb66 |
@@ -1,3 +1,5 @@
|
||||
[](https://gitter.im/bitwarden/Lobby)
|
||||
|
||||
# bitwarden Web
|
||||
|
||||
The bitwarden Web project is an AngularJS application that powers the web vault (https://vault.bitwarden.com/).
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.1.1",
|
||||
"version": "1.4.0",
|
||||
"environment": "Development",
|
||||
|
||||
"dependencies": {
|
||||
|
||||
@@ -58,8 +58,9 @@
|
||||
/// <reference path="lib/angular-resource/angular-resource.js" />
|
||||
/// <reference path="lib/angulartics/angulartics.js" />
|
||||
/// <reference path="lib/angulartics/angulartics-ga.js" />
|
||||
/// <reference path="lib/angular-toastr/angular-toastr.js" />
|
||||
/// <reference path="lib/angular-toastr/angular-toastr.min.js" />
|
||||
/// <reference path="lib/angular-toastr/angular-toastr.tpls.js" />
|
||||
/// <reference path="lib/angular-toastr/angular-toastr.tpls.min.js" />
|
||||
/// <reference path="lib/angular-ui-router/angular-ui-router.js" />
|
||||
/// <reference path="lib/bootstrap/js/bootstrap.min.js" />
|
||||
/// <reference path="lib/clipboard/clipboard.js" />
|
||||
@@ -69,3 +70,4 @@
|
||||
/// <reference path="lib/papaparse/papaparse.js" />
|
||||
/// <reference path="lib/sjcl/bitArray.js" />
|
||||
/// <reference path="lib/sjcl/cbc.js" />
|
||||
/// <reference path="lib/sjcl/sjcl.js" />
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
var _service = {};
|
||||
|
||||
_service.import = function (source, file, success, error) {
|
||||
if (!file) {
|
||||
error();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (source) {
|
||||
case 'local':
|
||||
importLocal(file, success, error);
|
||||
@@ -15,19 +20,56 @@
|
||||
case 'safeincloudcsv':
|
||||
importSafeInCloudCsv(file, success, error);
|
||||
break;
|
||||
case 'safeincloudxml':
|
||||
importSafeInCloudXml(file, success, error);
|
||||
break;
|
||||
case 'keypassxml':
|
||||
importKeyPassXml(file, success, error);
|
||||
break;
|
||||
case 'padlockcsv':
|
||||
importPadlockCsv(file, success, error);
|
||||
break;
|
||||
case '1password1pif':
|
||||
import1Password1Pif(file, success, error);
|
||||
break;
|
||||
case 'chromecsv':
|
||||
importChromeCsv(file, success, error);
|
||||
break;
|
||||
case 'firefoxpasswordexportercsvxml':
|
||||
importFirefoxPasswordExporterCsvXml(file, success, error);
|
||||
break;
|
||||
case 'upmcsv':
|
||||
importUpmCsv(file, success, error);
|
||||
break;
|
||||
default:
|
||||
error();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
function trimUri(uri) {
|
||||
if (uri.length > 1000) {
|
||||
return uri.substring(0, 1000);
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
function parseCsvErrors(results) {
|
||||
if (results.errors && results.errors.length) {
|
||||
for (var i = 0; i < results.errors.length; i++) {
|
||||
console.warn('Error parsing row ' + results.errors[i].row + ': ' + results.errors[i].message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function importLocal(file, success, error) {
|
||||
Papa.parse(file, {
|
||||
header: true,
|
||||
encoding: 'UTF-8',
|
||||
complete: function (results) {
|
||||
parseCsvErrors(results);
|
||||
|
||||
var folders = [],
|
||||
sites = [],
|
||||
folderRelationships = [];
|
||||
@@ -50,7 +92,7 @@
|
||||
|
||||
sites.push({
|
||||
favorite: value.favorite !== null ? value.favorite : false,
|
||||
uri: value.uri && value.uri !== '' ? value.uri : null,
|
||||
uri: value.uri && value.uri !== '' ? trimUri(value.uri) : null,
|
||||
username: value.username && value.username !== '' ? value.username : null,
|
||||
password: value.password && value.password !== '' ? value.password : null,
|
||||
notes: value.notes && value.notes !== '' ? value.notes : null,
|
||||
@@ -88,16 +130,22 @@
|
||||
|
||||
if (pre.length === 1) {
|
||||
csv = pre.text().trim();
|
||||
results = Papa.parse(csv, { header: true });
|
||||
results = Papa.parse(csv, {
|
||||
header: true,
|
||||
encoding: 'UTF-8'
|
||||
});
|
||||
parseData(results.data);
|
||||
}
|
||||
else {
|
||||
var foundPre = false;
|
||||
for (var i = 0; i < doc.length; i++) {
|
||||
if (doc[i].tagName === 'PRE') {
|
||||
if (doc[i].tagName.toLowerCase() === 'pre') {
|
||||
foundPre = true;
|
||||
csv = doc[i].outerText.trim();
|
||||
results = Papa.parse(csv, { header: true });
|
||||
results = Papa.parse(csv, {
|
||||
header: true,
|
||||
encoding: 'UTF-8'
|
||||
});
|
||||
parseData(results.data);
|
||||
break;
|
||||
}
|
||||
@@ -116,7 +164,9 @@
|
||||
else {
|
||||
Papa.parse(file, {
|
||||
header: true,
|
||||
encoding: 'UTF-8',
|
||||
complete: function (results) {
|
||||
parseCsvErrors(results);
|
||||
parseData(results.data);
|
||||
}
|
||||
});
|
||||
@@ -125,7 +175,8 @@
|
||||
function parseData(data) {
|
||||
var folders = [],
|
||||
sites = [],
|
||||
siteRelationships = [];
|
||||
siteRelationships = [],
|
||||
badDataSites = 0;
|
||||
|
||||
angular.forEach(data, function (value, key) {
|
||||
var folderIndex = folders.length,
|
||||
@@ -143,9 +194,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
if ((!value.name || value.name === '') && (!value.password || value.password === '')) {
|
||||
badDataSites++;
|
||||
}
|
||||
|
||||
sites.push({
|
||||
favorite: value.fav === '1',
|
||||
uri: value.url && value.url !== '' ? value.url : null,
|
||||
uri: value.url && value.url !== '' ? trimUri(value.url) : null,
|
||||
username: value.username && value.username !== '' ? value.username : null,
|
||||
password: value.password && value.password !== '' ? value.password : null,
|
||||
notes: value.extra && value.extra !== '' ? value.extra : null,
|
||||
@@ -167,14 +222,22 @@
|
||||
}
|
||||
});
|
||||
|
||||
success(folders, sites, siteRelationships);
|
||||
if (badDataSites && badDataSites > (data.length / 2)) {
|
||||
error('CSV data is not formatted correctly from LastPass. Please check your import file and try again.');
|
||||
}
|
||||
else {
|
||||
success(folders, sites, siteRelationships);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function importSafeInCloudCsv(file, success, error) {
|
||||
Papa.parse(file, {
|
||||
header: true,
|
||||
encoding: 'UTF-8',
|
||||
complete: function (results) {
|
||||
parseCsvErrors(results);
|
||||
|
||||
var folders = [],
|
||||
sites = [],
|
||||
siteRelationships = [];
|
||||
@@ -182,7 +245,7 @@
|
||||
angular.forEach(results.data, function (value, key) {
|
||||
sites.push({
|
||||
favorite: false,
|
||||
uri: value.URL && value.URL !== '' ? value.URL : null,
|
||||
uri: value.URL && value.URL !== '' ? trimUri(value.URL) : null,
|
||||
username: value.Login && value.Login !== '' ? value.Login : null,
|
||||
password: value.Password && value.Password !== '' ? value.Password : null,
|
||||
notes: value.Notes && value.Notes !== '' ? value.Notes : null,
|
||||
@@ -195,6 +258,196 @@
|
||||
});
|
||||
}
|
||||
|
||||
function importSafeInCloudXml(file, success, error) {
|
||||
var folders = [],
|
||||
sites = [],
|
||||
siteRelationships = [],
|
||||
foldersIndex = [];
|
||||
|
||||
var i = 0,
|
||||
j = 0;
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.readAsText(file, 'utf-8');
|
||||
reader.onload = function (evt) {
|
||||
var xmlDoc = $.parseXML(evt.target.result),
|
||||
xml = $(xmlDoc);
|
||||
|
||||
var db = xml.find('database');
|
||||
if (db.length) {
|
||||
var labels = db.find('> label');
|
||||
if (labels.length) {
|
||||
for (i = 0; i < labels.length; i++) {
|
||||
var label = $(labels[i]);
|
||||
foldersIndex[label.attr('id')] = folders.length;
|
||||
folders.push({
|
||||
name: label.attr('name')
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var cards = db.find('> card');
|
||||
if (cards.length) {
|
||||
for (i = 0; i < cards.length; i++) {
|
||||
var card = $(cards[i]);
|
||||
if (card.attr('template') === 'true') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var site = {
|
||||
favorite: false,
|
||||
uri: null,
|
||||
username: null,
|
||||
password: null,
|
||||
notes: null,
|
||||
name: card.attr('title'),
|
||||
};
|
||||
|
||||
var fields = card.find('> field');
|
||||
for (j = 0; j < fields.length; j++) {
|
||||
var field = $(fields[j]);
|
||||
|
||||
var text = field.text();
|
||||
var type = field.attr('type');
|
||||
|
||||
if (text && text !== '') {
|
||||
if (type === 'login') {
|
||||
site.username = text;
|
||||
}
|
||||
else if (type === 'password') {
|
||||
site.password = text;
|
||||
}
|
||||
else if (type === 'notes') {
|
||||
site.notes = text;
|
||||
}
|
||||
else if (type === 'website') {
|
||||
site.uri = trimUri(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sites.push(site);
|
||||
|
||||
labels = card.find('> label_id');
|
||||
if (labels.length) {
|
||||
var labelId = $(labels[0]).text();
|
||||
var folderIndex = foldersIndex[labelId];
|
||||
if (labelId !== null && labelId !== '' && folderIndex !== null) {
|
||||
siteRelationships.push({
|
||||
key: sites.length - 1,
|
||||
value: folderIndex
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
success(folders, sites, siteRelationships);
|
||||
}
|
||||
else {
|
||||
error();
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = function (evt) {
|
||||
error();
|
||||
};
|
||||
}
|
||||
|
||||
function importPadlockCsv(file, success, error) {
|
||||
Papa.parse(file, {
|
||||
encoding: 'UTF-8',
|
||||
complete: function (results) {
|
||||
parseCsvErrors(results);
|
||||
|
||||
var folders = [],
|
||||
sites = [],
|
||||
folderRelationships = [];
|
||||
|
||||
var customFieldHeaders = [];
|
||||
|
||||
// CSV index ref: 0 = name, 1 = category, 2 = username, 3 = password, 4+ = custom fields
|
||||
|
||||
var i = 0,
|
||||
j = 0;
|
||||
|
||||
for (i = 0; i < results.data.length; i++) {
|
||||
var value = results.data[i];
|
||||
if (i === 0) {
|
||||
// header row
|
||||
for (j = 4; j < value.length; j++) {
|
||||
customFieldHeaders.push(value[j]);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var folderIndex = folders.length,
|
||||
siteIndex = sites.length,
|
||||
hasFolder = value[1] && value[1] !== '',
|
||||
addFolder = hasFolder;
|
||||
|
||||
if (hasFolder) {
|
||||
for (j = 0; j < folders.length; j++) {
|
||||
if (folders[j].name === value[1]) {
|
||||
addFolder = false;
|
||||
folderIndex = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var site = {
|
||||
favorite: false,
|
||||
uri: null,
|
||||
username: value[2] && value[2] !== '' ? value[2] : null,
|
||||
password: value[3] && value[3] !== '' ? value[3] : null,
|
||||
notes: null,
|
||||
name: value[0] && value[0] !== '' ? value[0] : '--',
|
||||
};
|
||||
|
||||
if (customFieldHeaders.length) {
|
||||
for (j = 4; j < value.length; j++) {
|
||||
var cf = value[j];
|
||||
if (!cf || cf === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var cfHeader = customFieldHeaders[j - 4];
|
||||
if (cfHeader.toLowerCase() === 'url' || cfHeader.toLowerCase() === 'uri') {
|
||||
site.uri = trimUri(cf);
|
||||
}
|
||||
else {
|
||||
if (site.notes === null) {
|
||||
site.notes = '';
|
||||
}
|
||||
|
||||
site.notes += cfHeader + ': ' + cf + '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sites.push(site);
|
||||
|
||||
if (addFolder) {
|
||||
folders.push({
|
||||
name: value[1]
|
||||
});
|
||||
}
|
||||
|
||||
if (hasFolder) {
|
||||
folderRelationships.push({
|
||||
key: siteIndex,
|
||||
value: folderIndex
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
success(folders, sites, folderRelationships);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function importKeyPassXml(file, success, error) {
|
||||
var folders = [],
|
||||
sites = [],
|
||||
@@ -264,7 +517,7 @@
|
||||
|
||||
switch (key) {
|
||||
case 'URL':
|
||||
site.uri = value;
|
||||
site.uri = trimUri(value);
|
||||
break;
|
||||
case 'UserName':
|
||||
site.username = value;
|
||||
@@ -310,5 +563,222 @@
|
||||
}
|
||||
}
|
||||
|
||||
function import1Password1Pif(file, success, error) {
|
||||
var folders = [],
|
||||
sites = [],
|
||||
siteRelationships = [];
|
||||
|
||||
var i = 0,
|
||||
j = 0;
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.readAsText(file, 'utf-8');
|
||||
reader.onload = function (evt) {
|
||||
var fileContent = evt.target.result;
|
||||
var fileLines = fileContent.split(/(?:\r\n|\r|\n)/);
|
||||
|
||||
for (i = 0; i < fileLines.length; i++) {
|
||||
var line = fileLines[i];
|
||||
if (!line.length || line[0] !== '{') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var item = JSON.parse(line);
|
||||
if (item.typeName !== 'webforms.WebForm') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var site = {
|
||||
favorite: item.openContents && item.openContents.faveIndex ? true : false,
|
||||
uri: item.location && item.location !== '' ? trimUri(item.location) : null,
|
||||
username: null,
|
||||
password: null,
|
||||
notes: null,
|
||||
name: item.title && item.title !== '' ? item.title : '--',
|
||||
};
|
||||
|
||||
if (item.secureContents) {
|
||||
if (item.secureContents.notesPlain && item.secureContents.notesPlain !== '') {
|
||||
site.notes = item.secureContents.notesPlain;
|
||||
}
|
||||
|
||||
if (item.secureContents.fields) {
|
||||
for (j = 0; j < item.secureContents.fields.length; j++) {
|
||||
var field = item.secureContents.fields[j];
|
||||
if (field.designation === 'username') {
|
||||
site.username = field.value;
|
||||
}
|
||||
else if (field.designation === 'password') {
|
||||
site.password = field.value;
|
||||
}
|
||||
else {
|
||||
if (site.notes === null) {
|
||||
site.notes = '';
|
||||
}
|
||||
else {
|
||||
site.notes += '\n';
|
||||
}
|
||||
|
||||
site.notes += (field.name + ': ' + field.value + '\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sites.push(site);
|
||||
}
|
||||
|
||||
success(folders, sites, siteRelationships);
|
||||
};
|
||||
|
||||
reader.onerror = function (evt) {
|
||||
error();
|
||||
};
|
||||
}
|
||||
|
||||
function importChromeCsv(file, success, error) {
|
||||
Papa.parse(file, {
|
||||
header: true,
|
||||
encoding: 'UTF-8',
|
||||
complete: function (results) {
|
||||
parseCsvErrors(results);
|
||||
|
||||
var folders = [],
|
||||
sites = [],
|
||||
siteRelationships = [];
|
||||
|
||||
angular.forEach(results.data, function (value, key) {
|
||||
sites.push({
|
||||
favorite: false,
|
||||
uri: value.url && value.url !== '' ? trimUri(value.url) : null,
|
||||
username: value.username && value.username !== '' ? value.username : null,
|
||||
password: value.password && value.password !== '' ? value.password : null,
|
||||
notes: null,
|
||||
name: value.name && value.name !== '' ? value.name : '--',
|
||||
});
|
||||
});
|
||||
|
||||
success(folders, sites, siteRelationships);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function importFirefoxPasswordExporterCsvXml(file, success, error) {
|
||||
var folders = [],
|
||||
sites = [],
|
||||
siteRelationships = [];
|
||||
|
||||
function getNameFromHost(host) {
|
||||
var name = '--';
|
||||
try {
|
||||
if (host && host !== '') {
|
||||
var parser = document.createElement('a');
|
||||
parser.href = host;
|
||||
if (parser.hostname) {
|
||||
name = parser.hostname;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
if (file.type === 'text/xml') {
|
||||
var reader = new FileReader();
|
||||
reader.readAsText(file, 'utf-8');
|
||||
reader.onload = function (evt) {
|
||||
var xmlDoc = $.parseXML(evt.target.result),
|
||||
xml = $(xmlDoc);
|
||||
|
||||
var entries = xml.find('entry');
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
var entry = $(entries[i]);
|
||||
if (!entry) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var host = entry.attr('host'),
|
||||
user = entry.attr('user'),
|
||||
password = entry.attr('password');
|
||||
|
||||
sites.push({
|
||||
favorite: false,
|
||||
uri: host && host !== '' ? trimUri(host) : null,
|
||||
username: user && user !== '' ? user : null,
|
||||
password: password && password !== '' ? password : null,
|
||||
notes: null,
|
||||
name: getNameFromHost(host),
|
||||
});
|
||||
}
|
||||
|
||||
success(folders, sites, siteRelationships);
|
||||
};
|
||||
|
||||
reader.onerror = function (evt) {
|
||||
error();
|
||||
};
|
||||
}
|
||||
else {
|
||||
// currently bugged due to the comment
|
||||
// ref: https://github.com/mholt/PapaParse/issues/351
|
||||
|
||||
error('Only .xml exports are supported.');
|
||||
return;
|
||||
|
||||
//Papa.parse(file, {
|
||||
// comments: '#',
|
||||
// header: true,
|
||||
// encoding: 'UTF-8',
|
||||
// complete: function (results) {
|
||||
// parseCsvErrors(results);
|
||||
|
||||
// angular.forEach(results.data, function (value, key) {
|
||||
// sites.push({
|
||||
// favorite: false,
|
||||
// uri: value.hostname && value.hostname !== '' ? trimUri(value.hostname) : null,
|
||||
// username: value.username && value.username !== '' ? value.username : null,
|
||||
// password: value.password && value.password !== '' ? value.password : null,
|
||||
// notes: null,
|
||||
// name: getNameFromHost(value.hostname),
|
||||
// });
|
||||
// });
|
||||
|
||||
// success(folders, sites, siteRelationships);
|
||||
// }
|
||||
//});
|
||||
}
|
||||
}
|
||||
|
||||
function importUpmCsv(file, success, error) {
|
||||
Papa.parse(file, {
|
||||
encoding: 'UTF-8',
|
||||
complete: function (results) {
|
||||
parseCsvErrors(results);
|
||||
|
||||
var folders = [],
|
||||
sites = [],
|
||||
siteRelationships = [];
|
||||
|
||||
angular.forEach(results.data, function (value, key) {
|
||||
if (value.length === 5) {
|
||||
sites.push({
|
||||
favorite: false,
|
||||
uri: value[3] && value[3] !== '' ? trimUri(value[3]) : null,
|
||||
username: value[1] && value[1] !== '' ? value[1] : null,
|
||||
password: value[2] && value[2] !== '' ? value[2] : null,
|
||||
notes: value[4] && value[4] !== '' ? value[4] : null,
|
||||
name: value[0] && value[0] !== '' ? value[0] : '--',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
success(folders, sites, siteRelationships);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return _service;
|
||||
});
|
||||
|
||||
@@ -97,8 +97,39 @@ angular
|
||||
return password;
|
||||
};
|
||||
|
||||
// EFForg/OpenWireless
|
||||
// ref https://github.com/EFForg/OpenWireless/blob/master/app/js/diceware.js
|
||||
function randomInt(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
var rval = 0;
|
||||
var range = max - min;
|
||||
|
||||
var bits_needed = Math.ceil(Math.log2(range));
|
||||
if (bits_needed > 53) {
|
||||
throw new Exception("We cannot generate numbers larger than 53 bits.");
|
||||
}
|
||||
var bytes_needed = Math.ceil(bits_needed / 8);
|
||||
var mask = Math.pow(2, bits_needed) - 1;
|
||||
// 7776 -> (2^13 = 8192) -1 == 8191 or 0x00001111 11111111
|
||||
|
||||
// Create byte array and fill with N random numbers
|
||||
var byteArray = new Uint8Array(bytes_needed);
|
||||
window.crypto.getRandomValues(byteArray);
|
||||
|
||||
var p = (bytes_needed - 1) * 8;
|
||||
for (var i = 0; i < bytes_needed; i++) {
|
||||
rval += byteArray[i] * Math.pow(2, p);
|
||||
p -= 8;
|
||||
}
|
||||
|
||||
// Use & to apply the mask and reduce the number of recursive lookups
|
||||
rval = rval & mask;
|
||||
|
||||
if (rval >= range) {
|
||||
// Integer out of acceptable range
|
||||
return randomInt(min, max);
|
||||
}
|
||||
// Return an integer that falls within the range
|
||||
return min + rval;
|
||||
}
|
||||
|
||||
return _service;
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
angular.module("bit")
|
||||
.constant("appSettings", {"rememberedEmailCookieName":"bit.rememberedEmail","version":"1.1.1","environment":"Development","apiUri":"http://localhost:4000"});
|
||||
.constant("appSettings", {"rememberedEmailCookieName":"bit.rememberedEmail","version":"1.2.2","environment":"Development","apiUri":"http://localhost:4000"});
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
};
|
||||
|
||||
function importSuccess(folders, sites, folderRelationships) {
|
||||
if (!folders.length && !sites.length) {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
toastr.error('Nothing was imported.');
|
||||
return;
|
||||
}
|
||||
|
||||
apiService.ciphers.import({
|
||||
folders: cipherService.encryptFolders(folders, cryptoService.getKey()),
|
||||
sites: cipherService.encryptSites(sites, cryptoService.getKey()),
|
||||
@@ -25,8 +31,39 @@
|
||||
}, importError);
|
||||
}
|
||||
|
||||
function importError() {
|
||||
function importError(error) {
|
||||
$analytics.eventTrack('Import Data Failed', { label: $scope.model.source });
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
|
||||
if (error) {
|
||||
var data = error.data;
|
||||
if (data && data.ValidationErrors) {
|
||||
var message = '';
|
||||
for (var key in data.ValidationErrors) {
|
||||
if (!data.ValidationErrors.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var i = 0; i < data.ValidationErrors[key].length; i++) {
|
||||
message += (key + ': ' + data.ValidationErrors[key][i] + ' ');
|
||||
}
|
||||
}
|
||||
|
||||
if (message !== '') {
|
||||
toastr.error(message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (data && data.Message) {
|
||||
toastr.error(data.Message);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
toastr.error(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
toastr.error('Something went wrong. Try again.', 'Oh No!');
|
||||
}
|
||||
|
||||
|
||||
@@ -9,13 +9,19 @@
|
||||
<select id="source" name="source" class="form-control" ng-model="model.source">
|
||||
<option value="local">bitwarden (csv)</option>
|
||||
<option value="lastpass">LastPass (csv)</option>
|
||||
<option value="chromecsv">Chrome (csv)</option>
|
||||
<option value="firefoxpasswordexportercsvxml">Firefox Password Exporter (xml)</option>
|
||||
<option value="safeincloudxml">SafeInCloud (xml)</option>
|
||||
<option value="safeincloudcsv">SafeInCloud (csv)</option>
|
||||
<option value="keypassxml">KeyPass (xml)</option>
|
||||
<option value="padlockcsv">Padlock (csv)</option>
|
||||
<option value="1password1pif">1Password (1pif)</option>
|
||||
<option value="upmcsv">Universal Password Manager (csv)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="file">File</label>
|
||||
<input type="file" id="file" name="file" />
|
||||
<input type="file" id="file" name="file" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
alert('Your web browser does not support easy clipboard copying. Copy it manually instead.');
|
||||
};
|
||||
|
||||
$scope.folderSort = function (item) {
|
||||
return item.name.toLowerCase();
|
||||
};
|
||||
|
||||
function selectPassword(e) {
|
||||
var target = $(e.trigger).parent().prev();
|
||||
if (target.attr('type') === 'text') {
|
||||
|
||||
@@ -77,13 +77,25 @@
|
||||
}
|
||||
});
|
||||
|
||||
editModel.result.then(function (editedSite) {
|
||||
var site = $filter('filter')($scope.sites, { id: editedSite.id }, true);
|
||||
if (site && site.length > 0) {
|
||||
site[0].folderId = editedSite.folderId;
|
||||
site[0].name = editedSite.name;
|
||||
site[0].username = editedSite.username;
|
||||
site[0].favorite = editedSite.favorite;
|
||||
editModel.result.then(function (returnVal) {
|
||||
if (returnVal.action === 'edit') {
|
||||
var siteToUpdate = $filter('filter')($scope.sites, { id: returnVal.data.id }, true);
|
||||
|
||||
if (siteToUpdate && siteToUpdate.length > 0) {
|
||||
siteToUpdate[0].folderId = returnVal.data.folderId;
|
||||
siteToUpdate[0].name = returnVal.data.name;
|
||||
siteToUpdate[0].username = returnVal.data.username;
|
||||
siteToUpdate[0].favorite = returnVal.data.favorite;
|
||||
}
|
||||
}
|
||||
else if (returnVal.action === 'delete') {
|
||||
var siteToDelete = $filter('filter')($scope.sites, { id: returnVal.data }, true);
|
||||
if (siteToDelete && siteToDelete.length > 0) {
|
||||
var index = $scope.sites.indexOf(siteToDelete[0]);
|
||||
if (index > -1) {
|
||||
$scope.sites.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -115,7 +127,9 @@
|
||||
|
||||
apiService.sites.del({ id: site.id }, function () {
|
||||
var index = $scope.sites.indexOf(site);
|
||||
$scope.sites.splice(index, 1);
|
||||
if (index > -1) {
|
||||
$scope.sites.splice(index, 1);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -162,7 +176,9 @@
|
||||
|
||||
apiService.folders.del({ id: folder.id }, function () {
|
||||
var index = $scope.folders.indexOf(folder);
|
||||
$scope.folders.splice(index, 1);
|
||||
if (index > -1) {
|
||||
$scope.folders.splice(index, 1);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
$scope.savePromise = apiService.sites.put({ id: siteId }, site, function (siteResponse) {
|
||||
$analytics.eventTrack('Edited Site');
|
||||
var decSite = cipherService.decryptSite(siteResponse);
|
||||
$uibModalInstance.close(decSite);
|
||||
$uibModalInstance.close({
|
||||
action: 'edit',
|
||||
data: decSite
|
||||
});
|
||||
}).$promise;
|
||||
};
|
||||
|
||||
@@ -38,6 +41,10 @@
|
||||
alert('Your web browser does not support easy clipboard copying. Copy it manually instead.');
|
||||
};
|
||||
|
||||
$scope.folderSort = function (item) {
|
||||
return item.name.toLowerCase();
|
||||
};
|
||||
|
||||
function selectPassword(e) {
|
||||
var target = $(e.trigger).parent().prev();
|
||||
if (target.attr('type') === 'text') {
|
||||
@@ -45,6 +52,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
$scope.delete = function () {
|
||||
if (!confirm('Are you sure you want to delete this site (' + $scope.site.name + ')?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
apiService.sites.del({ id: $scope.site.id }, function () {
|
||||
$uibModalInstance.close({
|
||||
action: 'delete',
|
||||
data: $scope.site.id
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.close = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
};
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title"><i class="fa fa-folder-open"></i> {{folder.name}}</h3>
|
||||
<div class="box-tools pull-right">
|
||||
<button type="button" class="btn btn-box-tool" ng-click="addSite(folder)" uib-tooltip="Add Site">
|
||||
<i class="fa fa-plus"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-box-tool" ng-click="deleteFolder(folder)" ng-show="canDeleteFolder(folder)" uib-tooltip="Delete">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
@@ -54,4 +57,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<div class="form-group" show-errors>
|
||||
<label for="folder">Folder</label>
|
||||
<select id="folder" name="FolderId" ng-model="site.folderId" class="form-control" api-field>
|
||||
<option ng-repeat="folder in folders" value="{{folder.id}}">{{folder.name}}</option>
|
||||
<option ng-repeat="folder in folders | orderBy: folderSort" value="{{folder.id}}">{{folder.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -95,4 +95,4 @@
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<div class="form-group" show-errors>
|
||||
<label for="folder">Folder</label>
|
||||
<select id="folder" name="FolderId" ng-model="site.folderId" class="form-control" api-field>
|
||||
<option ng-repeat="folder in folders" value="{{folder.id}}">{{folder.name}}</option>
|
||||
<option ng-repeat="folder in folders | orderBy: folderSort" value="{{folder.id}}">{{folder.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -97,5 +97,8 @@
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="editSiteForm.$loading"></i>Save
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||
<button type="button" class="btn btn-link pull-right" ng-click="delete()" uib-tooltip="Delete">
|
||||
<i class="fa fa-trash fa-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://bitwarden.com/contact/" target="_blank"
|
||||
<a href="https://help.bitwarden.com/" target="_blank"
|
||||
analytics-on="click" analytics-event="Clicked Get Help">
|
||||
<i class="fa fa-info-circle"></i> <span>Get Help</span>
|
||||
</a>
|
||||
|
||||
Reference in New Issue
Block a user