From 2c91a2004c4ce86972daa5c9c5d294924d4884d7 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 13 Feb 2019 10:06:58 -0500 Subject: [PATCH] pin locking --- gulpfile.js | 13 +++++- jslib | 2 +- package-lock.json | 30 ++++++++++---- package.json | 3 +- src/app/accounts/lock.component.html | 9 ++++- src/app/accounts/lock.component.ts | 6 ++- src/app/accounts/settings.component.html | 8 ++++ src/app/accounts/settings.component.ts | 35 ++++++++++++++++- src/locales/en/messages.json | 19 +++++++++ src/scss/plugins.scss | 50 ++++++++++++++++++++++++ 10 files changed, 158 insertions(+), 17 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index fade66741c..61307017b5 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,9 +1,11 @@ const gulp = require('gulp'); const googleWebFonts = require('gulp-google-webfonts'); const del = require('del'); +const fs = require('fs'); const paths = { cssDir: './src/css/', + node_modules: './node_modules/', }; function clean() { @@ -25,7 +27,16 @@ function cleanupAotIssue() { return del(['./node_modules/@types/uglify-js/node_modules/source-map/source-map.d.ts']); } +// ref: https://github.com/t4t5/sweetalert/issues/890 +function fixSweetAlert(cb) { + fs.writeFileSync(paths.node_modules + 'sweetalert/typings/sweetalert.d.ts', + 'import swal, { SweetAlert } from "./core";export default swal;export as namespace swal;'); + cb(); +} + exports.clean = clean; exports.cleanupAotIssue = cleanupAotIssue; exports.webfonts = gulp.series(clean, webfonts); -exports['prebuild:renderer'] = gulp.parallel(webfonts, cleanupAotIssue);; +exports['prebuild:renderer'] = gulp.parallel(webfonts, cleanupAotIssue); +exports.fixSweetAlert = fixSweetAlert; +exports.postinstall = fixSweetAlert; diff --git a/jslib b/jslib index 647b254a71..f67fac3eeb 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 647b254a71d0af105e1f6f1a1febeb15cd4181fb +Subproject commit f67fac3eebc21b8935a54a28b7a21152c8513322 diff --git a/package-lock.json b/package-lock.json index c0cb84addf..bbeb6dc06f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4279,6 +4279,11 @@ "es6-symbol": "^3.1.1" } }, + "es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=" + }, "es6-symbol": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", @@ -5114,14 +5119,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5141,8 +5144,7 @@ "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", @@ -5264,8 +5266,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -5291,7 +5292,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -9427,6 +9427,11 @@ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", "dev": true }, + "promise-polyfill": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-6.1.0.tgz", + "integrity": "sha1-36lpQ+qcEh/KTem1hoyznTRy4Fc=" + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -10880,6 +10885,15 @@ "es6-symbol": "^3.1.1" } }, + "sweetalert": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/sweetalert/-/sweetalert-2.1.2.tgz", + "integrity": "sha512-iWx7X4anRBNDa/a+AdTmvAzQtkN1+s4j/JJRWlHpYE8Qimkohs8/XnFcWeYHH2lMA8LRCa5tj2d244If3S/hzA==", + "requires": { + "es6-object-assign": "^1.1.0", + "promise-polyfill": "^6.0.2" + } + }, "symbol-observable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", diff --git a/package.json b/package.json index 1f61a87921..8f4037f230 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "sub:update": "git submodule update --remote", "sub:pull": "git submodule foreach git pull origin master", "sub:commit": "npm run sub:pull && git commit -am \"update submodule\"", - "postinstall": "./node_modules/.bin/electron-rebuild && npm run sub:init", + "postinstall": "./node_modules/.bin/electron-rebuild && npm run sub:init && gulp postinstall", "lint": "tslint src/**/*.ts || true", "lint:fix": "tslint src/**/*.ts --fix", "build": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\"", @@ -258,6 +258,7 @@ "nord": "0.2.1", "papaparse": "4.6.0", "rxjs": "6.3.3", + "sweetalert": "2.1.2", "zone.js": "0.8.28", "zxcvbn": "4.4.2" } diff --git a/src/app/accounts/lock.component.html b/src/app/accounts/lock.component.html index 208a4953c6..6e2b2aea7d 100644 --- a/src/app/accounts/lock.component.html +++ b/src/app/accounts/lock.component.html @@ -1,11 +1,16 @@

-

{{'yourVaultIsLocked' | i18n}}

+

{{(pinLock ? 'yourVaultIsLockedPinCode' : 'yourVaultIsLocked') | i18n}}

-
+
+ + +
+
diff --git a/src/app/accounts/lock.component.ts b/src/app/accounts/lock.component.ts index 3127e643f5..79f590fbfb 100644 --- a/src/app/accounts/lock.component.ts +++ b/src/app/accounts/lock.component.ts @@ -10,6 +10,7 @@ import { import { CryptoService } from 'jslib/abstractions/crypto.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; +import { LockService } from 'jslib/abstractions/lock.service'; import { MessagingService } from 'jslib/abstractions/messaging.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { StorageService } from 'jslib/abstractions/storage.service'; @@ -30,8 +31,9 @@ export class LockComponent extends BaseLockComponent implements OnDestroy { platformUtilsService: PlatformUtilsService, messagingService: MessagingService, userService: UserService, cryptoService: CryptoService, private ngZone: NgZone, private route: ActivatedRoute, - private storageService: StorageService) { - super(router, i18nService, platformUtilsService, messagingService, userService, cryptoService); + storageService: StorageService, lockService: LockService) { + super(router, i18nService, platformUtilsService, messagingService, userService, cryptoService, + storageService, lockService); } async ngOnInit() { diff --git a/src/app/accounts/settings.component.html b/src/app/accounts/settings.component.html index 085bf4aad2..89bfd86bc9 100644 --- a/src/app/accounts/settings.component.html +++ b/src/app/accounts/settings.component.html @@ -15,6 +15,14 @@ {{'lockOptionsDesc' | i18n}}
+
+
+ +
+
diff --git a/src/app/accounts/settings.component.ts b/src/app/accounts/settings.component.ts index f8ef339164..0d232617cd 100644 --- a/src/app/accounts/settings.component.ts +++ b/src/app/accounts/settings.component.ts @@ -5,15 +5,18 @@ import { import { ToasterService } from 'angular2-toaster'; import { Angulartics2 } from 'angulartics2'; +import swal from 'sweetalert'; import { DeviceType } from 'jslib/enums/deviceType'; +import { CryptoService } from 'jslib/abstractions/crypto.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; import { LockService } from 'jslib/abstractions/lock.service'; import { MessagingService } from 'jslib/abstractions/messaging.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { StateService } from 'jslib/abstractions/state.service'; import { StorageService } from 'jslib/abstractions/storage.service'; +import { UserService } from 'jslib/abstractions/user.service'; import { ConstantsService } from 'jslib/services/constants.service'; @@ -27,6 +30,7 @@ import { Utils } from 'jslib/misc/utils'; }) export class SettingsComponent implements OnInit { lockOption: number = null; + pin: boolean = null; disableFavicons: boolean = false; enableMinToTray: boolean = false; enableCloseToTray: boolean = false; @@ -40,9 +44,10 @@ export class SettingsComponent implements OnInit { themeOptions: any[]; constructor(private analytics: Angulartics2, private toasterService: ToasterService, - i18nService: I18nService, private platformUtilsService: PlatformUtilsService, + private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private storageService: StorageService, private lockService: LockService, - private stateService: StateService, private messagingService: MessagingService) { + private stateService: StateService, private messagingService: MessagingService, + private userService: UserService, private cryptoService: CryptoService) { this.lockOptions = [ // { name: i18nService.t('immediately'), value: 0 }, { name: i18nService.t('oneMinute'), value: 1 }, @@ -77,6 +82,7 @@ export class SettingsComponent implements OnInit { async ngOnInit() { this.showMinToTray = this.platformUtilsService.getDevice() === DeviceType.WindowsDesktop; this.lockOption = await this.storageService.get(ConstantsService.lockOptionKey); + this.pin = await this.lockService.isPinLockSet(); this.disableFavicons = await this.storageService.get(ConstantsService.disableFaviconKey); this.enableMinToTray = await this.storageService.get(ElectronConstants.enableMinimizeToTrayKey); this.enableCloseToTray = await this.storageService.get(ElectronConstants.enableCloseToTrayKey); @@ -90,6 +96,31 @@ export class SettingsComponent implements OnInit { await this.lockService.setLockOption(this.lockOption != null ? this.lockOption : null); } + async updatePin() { + if (this.pin) { + const pin = await swal({ + text: this.i18nService.t('setYourPinCode'), + content: { element: 'input' }, + buttons: [this.i18nService.t('cancel'), this.i18nService.t('submit')], + }); + + if (pin != null && pin.trim() !== '') { + const kdf = await this.userService.getKdf(); + const kdfIterations = await this.userService.getKdfIterations(); + const email = await this.userService.getEmail(); + const pinKey = await this.cryptoService.makePinKey(pin, email, kdf, kdfIterations); + const key = await this.cryptoService.getKey(); + const pinProtectedKey = await this.cryptoService.encrypt(key.key, pinKey); + await this.storageService.save(ConstantsService.pinProtectedKey, pinProtectedKey.encryptedString); + } else { + this.pin = false; + } + } + if (!this.pin) { + await this.storageService.remove(ConstantsService.pinProtectedKey); + } + } + async saveFavicons() { await this.storageService.save(ConstantsService.disableFaviconKey, this.disableFavicons); await this.stateService.save(ConstantsService.disableFaviconKey, this.disableFavicons); diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index b70005d8a5..68bfb597c8 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -1169,5 +1169,24 @@ }, "weakMasterPasswordDesc": { "message": "The master password you have chosen is weak. You should use a strong master password (or a passphrase) to properly protect your Bitwarden account. Are you sure you want to use this master password?" + }, + "pin": { + "message": "PIN", + "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." + }, + "unlockWithPin": { + "message": "Unlock with PIN" + }, + "setYourPinCode": { + "message": "Set your PIN code for unlocking Bitwarden. Your PIN settings will be reset if you ever fully log out of the application." + }, + "pinRequired": { + "message": "PIN code is required." + }, + "invalidPin": { + "message": "Invalid PIN code." + }, + "yourVaultIsLockedPinCode": { + "message": "Your vault is locked. Verify your PIN code to continue." } } diff --git a/src/scss/plugins.scss b/src/scss/plugins.scss index c032a25058..0af7ee4a2a 100644 --- a/src/scss/plugins.scss +++ b/src/scss/plugins.scss @@ -95,3 +95,53 @@ $fa-font-path: "~font-awesome/fonts"; } } } + +// SweetAlert + +.swal-modal { + border-radius: $border-radius; + + @include themify($themes) { + background-color: themed('backgroundColorAlt'); + color: themed('textColor'); + } + + .swal-text { + font-size: $font-size-base; + + @include themify($themes) { + color: themed('textColor'); + } + } + + > .swal-text:first-child { + margin-top: 20px; + } + + .swal-content__input, .swal-content__textarea { + border: 1px solid #000000; + border-radius: $border-radius; + @include themify($themes) { + border-color: themed('inputBorderColor'); + color: themed('textColor'); + background-color: themed('inputBackgroundColor'); + } + } + + .swal-footer { + padding: 15px 10px 10px 10px; + margin: 0; + + .swal-button { + @extend .btn; + + &:focus { + box-shadow: none; + } + } + + .swal-button--confirm { + @extend .btn.primary; + } + } +}