mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-21 16:18:28 +01:00
convert export to ts component
This commit is contained in:
parent
b9139eec33
commit
00b8a3e7be
@ -58,6 +58,7 @@
|
||||
"@types/chrome": "0.0.51",
|
||||
"@types/jquery": "^3.2.16",
|
||||
"@types/node-forge": "0.6.10",
|
||||
"@types/papaparse": "4.1.31",
|
||||
"@types/tldjs": "1.7.1",
|
||||
"@types/webcrypto": "^0.0.28",
|
||||
"@uirouter/angularjs": "^1.0.10"
|
||||
|
@ -1,6 +1,4 @@
|
||||
window.Papa = require('papaparse');
|
||||
require('clipboard');
|
||||
|
||||
require('angular');
|
||||
|
||||
require('angular-animate');
|
||||
@ -131,7 +129,6 @@ require('./settings/settingsEditFolderController.js');
|
||||
require('./settings/settingsPremiumController.js');
|
||||
require('./settings/settingsEnvironmentController.js');
|
||||
require('./tools/toolsPasswordGeneratorHistoryController.js');
|
||||
require('./tools/toolsExportController.js');
|
||||
|
||||
// Bootstrap the angular application
|
||||
angular.element(function () {
|
||||
|
@ -180,8 +180,7 @@ angular
|
||||
})
|
||||
.state('export', {
|
||||
url: '/export',
|
||||
template: require('./tools/views/toolsExport.html'),
|
||||
controller: 'toolsExportController',
|
||||
component: 'export',
|
||||
data: { authorize: true },
|
||||
params: { animation: null }
|
||||
})
|
||||
|
@ -1,12 +1,12 @@
|
||||
<form name="theForm" ng-submit="submit()" bit-form="submitPromise">
|
||||
<form name="theForm" ng-submit="$ctrl.submit()" bit-form="submitPromise">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="tabs.tools({animation: 'out-slide-down'})">{{i18n.close}}</a>
|
||||
<a ui-sref="tabs.tools({animation: 'out-slide-down'})">{{$ctrl.i18n.close}}</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button type="submit" class="btn btn-link">{{i18n.submit}}</button>
|
||||
<button type="submit" class="btn btn-link">{{$ctrl.i18n.submit}}</button>
|
||||
</div>
|
||||
<div class="title">{{i18n.exportVault}}</div>
|
||||
<div class="title">{{$ctrl.i18n.exportVault}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list">
|
||||
@ -14,14 +14,14 @@
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-icon-input">
|
||||
<i class="fa fa-lock fa-lg fa-fw"></i>
|
||||
<label for="master-password" class="sr-only">{{i18n.masterPass}}</label>
|
||||
<input id="master-password" type="password" name="MasterPassword" placeholder="{{i18n.masterPass}}"
|
||||
ng-model="masterPassword">
|
||||
<label for="master-password" class="sr-only">{{$ctrl.i18n.masterPass}}</label>
|
||||
<input id="master-password" type="password" name="MasterPassword"
|
||||
placeholder="{{$ctrl.i18n.masterPass}}" ng-model="$ctrl.masterPassword">
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
<p>{{i18n.exportMasterPassword}}</p>
|
||||
<b>{{i18n.warning}}</b>: {{i18n.exportWarning}}
|
||||
<p>{{$ctrl.i18n.exportMasterPassword}}</p>
|
||||
<b>{{$ctrl.i18n.warning}}</b>: {{$ctrl.i18n.exportWarning}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
163
src/popup/app/tools/export.component.ts
Normal file
163
src/popup/app/tools/export.component.ts
Normal file
@ -0,0 +1,163 @@
|
||||
import * as angular from 'angular';
|
||||
import * as papa from 'papaparse';
|
||||
import * as template from './export.component.html';
|
||||
|
||||
import { CipherType } from '../../../enums/cipherType.enum';
|
||||
|
||||
import { CryptoService } from '../../../services/abstractions/crypto.service';
|
||||
import { UtilsService } from '../../../services/abstractions/utils.service';
|
||||
|
||||
export class ExportController {
|
||||
i18n: any;
|
||||
masterPassword: string;
|
||||
|
||||
constructor(private $scope: any, private $state: any, private cryptoService: CryptoService,
|
||||
private toastr: any, private utilsService: UtilsService, private $analytics: any,
|
||||
private i18nService: any, private folderService: any, private cipherService: any,
|
||||
private $window: any, private userService: any) {
|
||||
this.i18n = i18nService;
|
||||
this.$scope.submitPromise = null;
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
document.getElementById('master-password').focus();
|
||||
}
|
||||
|
||||
submit() {
|
||||
const self = this;
|
||||
this.$scope.submitPromise = this.checkPassword().then(() => {
|
||||
return self.getCsv();
|
||||
}).then((csv) => {
|
||||
self.$analytics.eventTrack('Exported Data');
|
||||
self.downloadFile(csv);
|
||||
}, () => {
|
||||
this.toastr.error(self.i18n.invalidMasterPassword, self.i18n.errorsOccurred);
|
||||
});
|
||||
}
|
||||
|
||||
private async checkPassword() {
|
||||
const email = await this.userService.getEmail();
|
||||
const key = this.cryptoService.makeKey(this.masterPassword, email);
|
||||
const keyHash = await this.cryptoService.hashPassword(this.masterPassword, key);
|
||||
const storedKeyHash = await this.cryptoService.getKeyHash();
|
||||
if (storedKeyHash == null || keyHash == null || storedKeyHash !== keyHash) {
|
||||
throw new Error('Invalid password.');
|
||||
}
|
||||
}
|
||||
|
||||
private async getCsv(): Promise<string> {
|
||||
let decFolders: any[] = [];
|
||||
let decCiphers: any[] = [];
|
||||
const promises = [];
|
||||
|
||||
promises.push(this.folderService.getAllDecrypted().then((folders: any[]) => {
|
||||
decFolders = folders;
|
||||
}));
|
||||
|
||||
promises.push(this.cipherService.getAllDecrypted().then((ciphers: any[]) => {
|
||||
decCiphers = ciphers;
|
||||
}));
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
const foldersMap = new Map<string, any>();
|
||||
for (const f of decFolders) {
|
||||
foldersMap.set(f.id, f);
|
||||
}
|
||||
|
||||
const exportCiphers = [];
|
||||
for (const c of decCiphers) {
|
||||
// only export logins and secure notes
|
||||
if (c.type !== CipherType.Login && c.type !== CipherType.SecureNote) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const cipher: any = {
|
||||
folder: c.folderId && foldersMap.has(c.folderId) ? foldersMap.get(c.folderId).name : null,
|
||||
favorite: c.favorite ? 1 : null,
|
||||
type: null,
|
||||
name: c.name,
|
||||
notes: c.notes,
|
||||
fields: null,
|
||||
// Login props
|
||||
login_uri: null,
|
||||
login_username: null,
|
||||
login_password: null,
|
||||
login_totp: null,
|
||||
};
|
||||
|
||||
if (c.fields) {
|
||||
for (const f of c.fields) {
|
||||
if (!cipher.fields) {
|
||||
cipher.fields = '';
|
||||
} else {
|
||||
cipher.fields += '\n';
|
||||
}
|
||||
|
||||
cipher.fields += ((f.name || '') + ': ' + f.value);
|
||||
}
|
||||
}
|
||||
|
||||
switch (c.type) {
|
||||
case CipherType.Login:
|
||||
cipher.type = 'login';
|
||||
cipher.login_uri = c.login.uri;
|
||||
cipher.login_username = c.login.username;
|
||||
cipher.login_password = c.login.password;
|
||||
cipher.login_totp = c.login.totp;
|
||||
break;
|
||||
case CipherType.SecureNote:
|
||||
cipher.type = 'note';
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
exportCiphers.push(cipher);
|
||||
}
|
||||
|
||||
const csv = papa.unparse(exportCiphers);
|
||||
return csv;
|
||||
}
|
||||
|
||||
private downloadFile(csv: string): void {
|
||||
const csvBlob = new Blob([csv]);
|
||||
const fileName = this.makeFileName();
|
||||
|
||||
if (this.$window.navigator.msSaveOrOpenBlob) {
|
||||
// Currently bugged in Edge. See
|
||||
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8178877/
|
||||
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8477778/
|
||||
this.$window.navigator.msSaveBlob(csvBlob, fileName);
|
||||
} else {
|
||||
const a = this.$window.document.createElement('a');
|
||||
a.href = this.$window.URL.createObjectURL(csvBlob, { type: 'text/plain' });
|
||||
a.download = fileName;
|
||||
this.$window.document.body.appendChild(a);
|
||||
a.click();
|
||||
this.$window.document.body.removeChild(a);
|
||||
}
|
||||
}
|
||||
|
||||
private makeFileName(): string {
|
||||
const now = new Date();
|
||||
const dateString =
|
||||
now.getFullYear() + '' + this.padNumber(now.getMonth() + 1, 2) + '' + this.padNumber(now.getDate(), 2) +
|
||||
this.padNumber(now.getHours(), 2) + '' + this.padNumber(now.getMinutes(), 2) +
|
||||
this.padNumber(now.getSeconds(), 2);
|
||||
|
||||
return 'bitwarden_export_' + dateString + '.csv';
|
||||
}
|
||||
|
||||
private padNumber(num: number, width: number, padCharacter: string = '0'): string {
|
||||
const numString = num.toString();
|
||||
return numString.length >= width ? numString :
|
||||
new Array(width - numString.length + 1).join(padCharacter) + numString;
|
||||
}
|
||||
}
|
||||
|
||||
export const ExportComponent = {
|
||||
bindings: {},
|
||||
controller: ExportController,
|
||||
template,
|
||||
};
|
@ -1,4 +1,5 @@
|
||||
import * as angular from 'angular';
|
||||
import { ExportComponent } from './export.component';
|
||||
import { PasswordGeneratorComponent } from './password-generator.component';
|
||||
import { ToolsComponent } from './tools.component';
|
||||
|
||||
@ -7,5 +8,6 @@ export default angular
|
||||
|
||||
.component('tools', ToolsComponent)
|
||||
.component('passwordGenerator', PasswordGeneratorComponent)
|
||||
.component('export', ExportComponent)
|
||||
|
||||
.name;
|
||||
|
@ -1,158 +0,0 @@
|
||||
angular
|
||||
.module('bit.tools')
|
||||
|
||||
.controller('toolsExportController', function ($scope, $state, toastr, $q, $analytics,
|
||||
i18nService, cryptoService, userService, folderService, cipherService, $window, constantsService) {
|
||||
$scope.i18n = i18nService;
|
||||
document.getElementById('master-password').focus();
|
||||
|
||||
$scope.submitPromise = null;
|
||||
$scope.submit = function () {
|
||||
$scope.submitPromise = checkPassword().then(function () {
|
||||
return getCsv();
|
||||
}).then(function (csv) {
|
||||
$analytics.eventTrack('Exported Data');
|
||||
downloadFile(csv);
|
||||
}, function () {
|
||||
toastr.error(i18nService.invalidMasterPassword, i18nService.errorsOccurred);
|
||||
});
|
||||
};
|
||||
|
||||
function checkPassword() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
userService.getEmail().then(function (email) {
|
||||
var key = cryptoService.makeKey($scope.masterPassword, email);
|
||||
var keyHash;
|
||||
cryptoService.hashPassword($scope.masterPassword, key).then(function (theKeyHash) {
|
||||
keyHash = theKeyHash;
|
||||
return cryptoService.getKeyHash();
|
||||
}).then(function (storedKeyHash) {
|
||||
if (storedKeyHash && keyHash && storedKeyHash === keyHash) {
|
||||
deferred.resolve();
|
||||
}
|
||||
else {
|
||||
deferred.reject();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function getCsv() {
|
||||
var decFolders = [];
|
||||
var decCiphers = [];
|
||||
var promises = [];
|
||||
|
||||
var folderPromise = folderService.getAllDecrypted().then(function (folders) {
|
||||
decFolders = folders;
|
||||
});
|
||||
promises.push(folderPromise);
|
||||
|
||||
var ciphersPromise = cipherService.getAllDecrypted().then(function (ciphers) {
|
||||
decCiphers = ciphers;
|
||||
});
|
||||
promises.push(ciphersPromise);
|
||||
|
||||
return $q.all(promises).then(function () {
|
||||
var foldersDict = {};
|
||||
for (var i = 0; i < decFolders.length; i++) {
|
||||
foldersDict[decFolders[i].id] = decFolders[i];
|
||||
}
|
||||
|
||||
var exportCiphers = [];
|
||||
for (i = 0; i < decCiphers.length; i++) {
|
||||
// only export logins and secure notes
|
||||
if (decCiphers[i].type !== constantsService.cipherType.login &&
|
||||
decCiphers[i].type !== constantsService.cipherType.secureNote) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var cipher = {
|
||||
folder: decCiphers[i].folderId && (decCiphers[i].folderId in foldersDict) ?
|
||||
foldersDict[decCiphers[i].folderId].name : null,
|
||||
favorite: decCiphers[i].favorite ? 1 : null,
|
||||
type: null,
|
||||
name: decCiphers[i].name,
|
||||
notes: decCiphers[i].notes,
|
||||
fields: null,
|
||||
// Login props
|
||||
login_uri: null,
|
||||
login_username: null,
|
||||
login_password: null,
|
||||
login_totp: null
|
||||
};
|
||||
|
||||
if (decCiphers[i].fields) {
|
||||
for (var j = 0; j < decCiphers[i].fields.length; j++) {
|
||||
if (!cipher.fields) {
|
||||
cipher.fields = '';
|
||||
}
|
||||
else {
|
||||
cipher.fields += '\n';
|
||||
}
|
||||
|
||||
cipher.fields += ((decCiphers[i].fields[j].name || '') + ': ' + decCiphers[i].fields[j].value);
|
||||
}
|
||||
}
|
||||
|
||||
switch (decCiphers[i].type) {
|
||||
case constantsService.cipherType.login:
|
||||
cipher.type = 'login';
|
||||
cipher.login_uri = decCiphers[i].login.uri;
|
||||
cipher.login_username = decCiphers[i].login.username;
|
||||
cipher.login_password = decCiphers[i].login.password;
|
||||
cipher.login_totp = decCiphers[i].login.totp;
|
||||
break;
|
||||
case constantsService.cipherType.secureNote:
|
||||
cipher.type = 'note';
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
exportCiphers.push(cipher);
|
||||
}
|
||||
|
||||
var csv = Papa.unparse(exportCiphers);
|
||||
return csv;
|
||||
});
|
||||
}
|
||||
|
||||
function downloadFile(csvString) {
|
||||
var csvBlob = new Blob([csvString]);
|
||||
var fileName = makeFileName();
|
||||
|
||||
if ($window.navigator.msSaveOrOpenBlob) {
|
||||
// Currently bugged in Edge. See
|
||||
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8178877/
|
||||
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8477778/
|
||||
$window.navigator.msSaveBlob(csvBlob, fileName);
|
||||
}
|
||||
else {
|
||||
var a = $window.document.createElement('a');
|
||||
a.href = $window.URL.createObjectURL(csvBlob, { type: 'text/plain' });
|
||||
a.download = fileName;
|
||||
$window.document.body.appendChild(a);
|
||||
a.click();
|
||||
$window.document.body.removeChild(a);
|
||||
}
|
||||
}
|
||||
|
||||
function makeFileName() {
|
||||
var now = new Date();
|
||||
var dateString =
|
||||
now.getFullYear() + '' + padNumber(now.getMonth() + 1, 2) + '' + padNumber(now.getDate(), 2) +
|
||||
padNumber(now.getHours(), 2) + '' + padNumber(now.getMinutes(), 2) +
|
||||
padNumber(now.getSeconds(), 2);
|
||||
|
||||
return 'bitwarden_export_' + dateString + '.csv';
|
||||
}
|
||||
|
||||
function padNumber(number, width, paddingCharacter) {
|
||||
paddingCharacter = paddingCharacter || '0';
|
||||
number = number + '';
|
||||
return number.length >= width ? number : new Array(width - number.length + 1).join(paddingCharacter) + number;
|
||||
}
|
||||
});
|
Loading…
Reference in New Issue
Block a user