mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-02 18:17:46 +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/chrome": "0.0.51",
|
||||||
"@types/jquery": "^3.2.16",
|
"@types/jquery": "^3.2.16",
|
||||||
"@types/node-forge": "0.6.10",
|
"@types/node-forge": "0.6.10",
|
||||||
|
"@types/papaparse": "4.1.31",
|
||||||
"@types/tldjs": "1.7.1",
|
"@types/tldjs": "1.7.1",
|
||||||
"@types/webcrypto": "^0.0.28",
|
"@types/webcrypto": "^0.0.28",
|
||||||
"@uirouter/angularjs": "^1.0.10"
|
"@uirouter/angularjs": "^1.0.10"
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
window.Papa = require('papaparse');
|
|
||||||
require('clipboard');
|
require('clipboard');
|
||||||
|
|
||||||
require('angular');
|
require('angular');
|
||||||
|
|
||||||
require('angular-animate');
|
require('angular-animate');
|
||||||
@ -131,7 +129,6 @@ require('./settings/settingsEditFolderController.js');
|
|||||||
require('./settings/settingsPremiumController.js');
|
require('./settings/settingsPremiumController.js');
|
||||||
require('./settings/settingsEnvironmentController.js');
|
require('./settings/settingsEnvironmentController.js');
|
||||||
require('./tools/toolsPasswordGeneratorHistoryController.js');
|
require('./tools/toolsPasswordGeneratorHistoryController.js');
|
||||||
require('./tools/toolsExportController.js');
|
|
||||||
|
|
||||||
// Bootstrap the angular application
|
// Bootstrap the angular application
|
||||||
angular.element(function () {
|
angular.element(function () {
|
||||||
|
@ -180,8 +180,7 @@ angular
|
|||||||
})
|
})
|
||||||
.state('export', {
|
.state('export', {
|
||||||
url: '/export',
|
url: '/export',
|
||||||
template: require('./tools/views/toolsExport.html'),
|
component: 'export',
|
||||||
controller: 'toolsExportController',
|
|
||||||
data: { authorize: true },
|
data: { authorize: true },
|
||||||
params: { animation: null }
|
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="header">
|
||||||
<div class="left">
|
<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>
|
||||||
<div class="right">
|
<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>
|
||||||
<div class="title">{{i18n.exportVault}}</div>
|
<div class="title">{{$ctrl.i18n.exportVault}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="list">
|
<div class="list">
|
||||||
@ -14,14 +14,14 @@
|
|||||||
<div class="list-section-items">
|
<div class="list-section-items">
|
||||||
<div class="list-section-item list-section-item-icon-input">
|
<div class="list-section-item list-section-item-icon-input">
|
||||||
<i class="fa fa-lock fa-lg fa-fw"></i>
|
<i class="fa fa-lock fa-lg fa-fw"></i>
|
||||||
<label for="master-password" class="sr-only">{{i18n.masterPass}}</label>
|
<label for="master-password" class="sr-only">{{$ctrl.i18n.masterPass}}</label>
|
||||||
<input id="master-password" type="password" name="MasterPassword" placeholder="{{i18n.masterPass}}"
|
<input id="master-password" type="password" name="MasterPassword"
|
||||||
ng-model="masterPassword">
|
placeholder="{{$ctrl.i18n.masterPass}}" ng-model="$ctrl.masterPassword">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-section-footer">
|
<div class="list-section-footer">
|
||||||
<p>{{i18n.exportMasterPassword}}</p>
|
<p>{{$ctrl.i18n.exportMasterPassword}}</p>
|
||||||
<b>{{i18n.warning}}</b>: {{i18n.exportWarning}}
|
<b>{{$ctrl.i18n.warning}}</b>: {{$ctrl.i18n.exportWarning}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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 * as angular from 'angular';
|
||||||
|
import { ExportComponent } from './export.component';
|
||||||
import { PasswordGeneratorComponent } from './password-generator.component';
|
import { PasswordGeneratorComponent } from './password-generator.component';
|
||||||
import { ToolsComponent } from './tools.component';
|
import { ToolsComponent } from './tools.component';
|
||||||
|
|
||||||
@ -7,5 +8,6 @@ export default angular
|
|||||||
|
|
||||||
.component('tools', ToolsComponent)
|
.component('tools', ToolsComponent)
|
||||||
.component('passwordGenerator', PasswordGeneratorComponent)
|
.component('passwordGenerator', PasswordGeneratorComponent)
|
||||||
|
.component('export', ExportComponent)
|
||||||
|
|
||||||
.name;
|
.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