From d4d5ccc4a48cc6dd195173b4acf61a07bbbd06fd Mon Sep 17 00:00:00 2001 From: eliykat <31796059+eliykat@users.noreply.github.com> Date: Mon, 24 Aug 2020 23:10:15 +1000 Subject: [PATCH 1/2] =?UTF-8?q?=E2=80=9CAutofill=E2=80=9D=20and=20?= =?UTF-8?q?=E2=80=9CAutofill=20and=20Save=E2=80=9D=20buttons=20for=20the?= =?UTF-8?q?=20browser=20extension=20Vault=20(#1367)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add autofill button to View cipher screen in Vault * Add Autofill and Save button to View screen in Vault * disable Vault Autofill buttons in popout * tidy up 'autofill from vault' features as per feedback in PR #1367 Includes: - remove duplicate code - better handling of error and success messages --- src/_locales/en/messages.json | 9 ++ src/popup/vault/view.component.html | 18 ++++ src/popup/vault/view.component.ts | 122 +++++++++++++++++++++++++++- 3 files changed, 148 insertions(+), 1 deletion(-) diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index ea0387fc6f..5471171a60 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -1299,5 +1299,14 @@ }, "vaultTimeoutLogOutConfirmationTitle": { "message": "Timeout Action Confirmation" + }, + "autoFillAndSave": { + "message": "Auto-fill and Save" + }, + "savedUri": { + "message": "Auto-filled and Saved Item" + }, + "autoFillSuccess": { + "message": "Auto-filled Item" } } \ No newline at end of file diff --git a/src/popup/vault/view.component.html b/src/popup/vault/view.component.html index 061ca996ac..e3f3da2165 100644 --- a/src/popup/vault/view.component.html +++ b/src/popup/vault/view.component.html @@ -268,6 +268,24 @@
+ +
+ + {{'autoFill' | i18n}} +
+
+ +
+ + {{'autoFillAndSave' | i18n}} +
+
diff --git a/src/popup/vault/view.component.ts b/src/popup/vault/view.component.ts index f74ed34fe0..d4fc9bdcae 100644 --- a/src/popup/vault/view.component.ts +++ b/src/popup/vault/view.component.ts @@ -14,14 +14,22 @@ import { CipherService } from 'jslib/abstractions/cipher.service'; import { CryptoService } from 'jslib/abstractions/crypto.service'; import { EventService } from 'jslib/abstractions/event.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; +import { MessagingService } from 'jslib/abstractions/messaging.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { TokenService } from 'jslib/abstractions/token.service'; import { TotpService } from 'jslib/abstractions/totp.service'; import { UserService } from 'jslib/abstractions/user.service'; +import { Cipher } from 'jslib/models/domain'; +import { LoginUriView } from 'jslib/models/view'; import { BroadcasterService } from 'jslib/angular/services/broadcaster.service'; import { ViewComponent as BaseViewComponent } from 'jslib/angular/components/view.component'; +import { BrowserApi } from '../../browser/browserApi'; +import { AutofillService } from '../../services/abstractions/autofill.service'; +import { PopupUtilsService } from '../services/popup-utils.service'; + +const BroadcasterSubscriptionId = 'ChildViewComponent'; @Component({ selector: 'app-vault-view', @@ -29,6 +37,8 @@ import { ViewComponent as BaseViewComponent } from 'jslib/angular/components/vie }) export class ViewComponent extends BaseViewComponent { showAttachments = true; + pageDetails: any[] = []; + inPopout: boolean = false; constructor(cipherService: CipherService, totpService: TotpService, tokenService: TokenService, i18nService: I18nService, @@ -37,13 +47,15 @@ export class ViewComponent extends BaseViewComponent { private router: Router, private location: Location, broadcasterService: BroadcasterService, ngZone: NgZone, changeDetectorRef: ChangeDetectorRef, userService: UserService, - eventService: EventService) { + eventService: EventService, private autofillService: AutofillService, + private messagingService: MessagingService, private popupUtilsService: PopupUtilsService) { super(cipherService, totpService, tokenService, i18nService, cryptoService, platformUtilsService, auditService, window, broadcasterService, ngZone, changeDetectorRef, userService, eventService); } ngOnInit() { this.showAttachments = !this.platformUtilsService.isEdge(); + this.inPopout = this.popupUtilsService.inPopout(window); const queryParamsSub = this.route.queryParams.subscribe(async (params) => { if (params.cipherId) { this.cipherId = params.cipherId; @@ -57,6 +69,29 @@ export class ViewComponent extends BaseViewComponent { } }); super.ngOnInit(); + + this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { + this.ngZone.run(async () => { + switch (message.command) { + case 'collectPageDetailsResponse': + if (message.sender === BroadcasterSubscriptionId) { + this.pageDetails.push({ + frameId: message.webExtSender.frameId, + tab: message.tab, + details: message.details, + }); + } + break; + default: + break; + } + }); + }); + } + + ngOnDestroy() { + this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); + super.ngOnDestroy(); } edit() { @@ -80,6 +115,91 @@ export class ViewComponent extends BaseViewComponent { }); } + async fillCipher() { + const didAutofill: boolean = await this.doAutofill(); + if (didAutofill) { + this.platformUtilsService.showToast('success', null, + this.i18nService.t('autoFillSuccess')); + } + } + + async fillCipherAndSave() { + const didAutofill: boolean = await this.doAutofill(); + + if (didAutofill) { + const tab = await BrowserApi.getTabFromCurrentWindow(); + if (!tab) { + throw new Error('No tab found.'); + } + + if (this.cipher.login.uris == null) { + this.cipher.login.uris = []; + } else { + if (this.cipher.login.uris.some((uri) => uri.uri === tab.url)) { + this.platformUtilsService.showToast('success', null, + this.i18nService.t('savedURI')); + return; + } + } + + const loginUri: LoginUriView = new LoginUriView(); + loginUri.uri = tab.url; + this.cipher.login.uris.push(loginUri); + + try { + const cipher: Cipher = await this.cipherService.encrypt(this.cipher); + await this.cipherService.saveWithServer(cipher); + this.cipher.id = cipher.id; + + this.platformUtilsService.showToast('success', null, + this.i18nService.t('savedURI')); + this.messagingService.send('editedCipher'); + } catch { + this.platformUtilsService.showToast('error', null, + this.i18nService.t('unexpectedError')); + } + } + } + + async doAutofill() { + if (this.pageDetails == null || this.pageDetails.length === 0) { + this.platformUtilsService.showToast('error', null, + this.i18nService.t('autofillError')); + return false; + } + + try { + this.totpCode = await this.autofillService.doAutoFill({ + cipher: this.cipher, + pageDetails: this.pageDetails, + doc: window.document, + }); + if (this.totpCode != null) { + this.platformUtilsService.copyToClipboard(this.totpCode, { window: window }); + } + } catch { + this.platformUtilsService.showToast('error', null, + this.i18nService.t('autofillError')); + this.changeDetectorRef.detectChanges(); + return false; + } + + return true; + } + + async load() { + await super.load(); + + const tab = await BrowserApi.getTabFromCurrentWindow(); + + this.pageDetails = []; + BrowserApi.tabSendMessage(tab, { + command: 'collectPageDetails', + tab: tab, + sender: BroadcasterSubscriptionId, + }); + } + async restore() { if (!this.cipher.isDeleted) { return false; From b7c2c7623053a94bdb42d4e2567ce86ab5de90dc Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Mon, 24 Aug 2020 10:17:15 -0400 Subject: [PATCH 2/2] finish autofill from view, other misc cleanup (#1368) * finish autofill from view, other misc cleanup * compare hostnames for authResult --- src/_locales/en/messages.json | 6 +- src/background/main.background.ts | 4 +- src/background/runtime.background.ts | 35 ++++---- src/background/tabs.background.ts | 6 ++ src/background/windows.background.ts | 2 + src/browser/browserApi.ts | 8 +- src/content/sso.ts | 10 +-- src/popup/accounts/home.component.ts | 67 +++++++-------- src/popup/accounts/login.component.ts | 4 +- src/popup/accounts/sso.component.ts | 16 ++-- src/popup/vault/view.component.html | 14 +-- src/popup/vault/view.component.ts | 118 ++++++++++++++------------ 12 files changed, 150 insertions(+), 140 deletions(-) diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 5471171a60..12d9466742 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -1303,10 +1303,10 @@ "autoFillAndSave": { "message": "Auto-fill and Save" }, - "savedUri": { - "message": "Auto-filled and Saved Item" + "autoFillSuccessAndSavedUri": { + "message": "Auto-filled Item and Saved URI" }, "autoFillSuccess": { "message": "Auto-filled Item" } -} \ No newline at end of file +} diff --git a/src/background/main.background.ts b/src/background/main.background.ts index bbd455a52c..57e647fb4d 100644 --- a/src/background/main.background.ts +++ b/src/background/main.background.ts @@ -219,8 +219,8 @@ export default class MainBackground { // Background this.runtimeBackground = new RuntimeBackground(this, this.autofillService, this.cipherService, this.platformUtilsService as BrowserPlatformUtilsService, this.storageService, this.i18nService, - this.analytics, this.notificationsService, this.systemService, this.vaultTimeoutService, this.syncService, - this.authService, this.stateService, this.environmentService, this.popupUtilsService); + this.analytics, this.notificationsService, this.systemService, this.vaultTimeoutService, + this.environmentService); this.commandsBackground = new CommandsBackground(this, this.passwordGenerationService, this.platformUtilsService, this.analytics, this.vaultTimeoutService); diff --git a/src/background/runtime.background.ts b/src/background/runtime.background.ts index e74d8b44fa..b028911921 100644 --- a/src/background/runtime.background.ts +++ b/src/background/runtime.background.ts @@ -38,8 +38,7 @@ export default class RuntimeBackground { private storageService: StorageService, private i18nService: I18nService, private analytics: Analytics, private notificationsService: NotificationsService, private systemService: SystemService, private vaultTimeoutService: VaultTimeoutService, - private syncService: SyncService, private authService: AuthService, private stateService: StateService, - private environmentService: EnvironmentService, private popupUtilsService : PopupUtilsService) { + private environmentService: EnvironmentService) { this.isSafari = this.platformUtilsService.isSafari(); this.runtime = this.isSafari ? {} : chrome.runtime; @@ -165,25 +164,21 @@ export default class RuntimeBackground { } break; case 'authResult': - var vaultUrl = this.environmentService.webVaultUrl; - if(!vaultUrl) { - vaultUrl = 'https://vault.bitwarden.com'; - } + let vaultUrl = this.environmentService.webVaultUrl; + if (vaultUrl == null) { + vaultUrl = 'https://vault.bitwarden.com'; + } - if(!msg.referrer) { - return; - } - - if(!vaultUrl.includes(msg.referrer)) { - return; - } - - try { - chrome.tabs.create({ - url: 'popup/index.html?uilocation=popout#/sso?code=' + msg.code + '&state=' + msg.state - }); - } - catch { } + if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) { + return; + } + + try { + chrome.tabs.create({ + url: 'popup/index.html?uilocation=popout#/sso?code=' + msg.code + '&state=' + msg.state + }); + } + catch { } break; default: break; diff --git a/src/background/tabs.background.ts b/src/background/tabs.background.ts index 78f177bad5..8f4d4e12b1 100644 --- a/src/background/tabs.background.ts +++ b/src/background/tabs.background.ts @@ -14,6 +14,8 @@ export default class TabsBackground { this.tabs.onActivated.addListener(async (activeInfo: any) => { await this.main.refreshBadgeAndMenu(); + this.main.messagingService.send('tabActivated'); + this.main.messagingService.send('tabChanged'); }); this.tabs.onReplaced.addListener(async (addedTabId: any, removedTabId: any) => { @@ -23,6 +25,8 @@ export default class TabsBackground { this.main.onReplacedRan = true; await this.main.checkNotificationQueue(); await this.main.refreshBadgeAndMenu(); + this.main.messagingService.send('tabReplaced'); + this.main.messagingService.send('tabChanged'); }); this.tabs.onUpdated.addListener(async (tabId: any, changeInfo: any, tab: any) => { @@ -32,6 +36,8 @@ export default class TabsBackground { this.main.onUpdatedRan = true; await this.main.checkNotificationQueue(); await this.main.refreshBadgeAndMenu(); + this.main.messagingService.send('tabUpdated'); + this.main.messagingService.send('tabChanged'); }); } } diff --git a/src/background/windows.background.ts b/src/background/windows.background.ts index c0fcc0b263..6239ddd782 100644 --- a/src/background/windows.background.ts +++ b/src/background/windows.background.ts @@ -18,6 +18,8 @@ export default class WindowsBackground { } await this.main.refreshBadgeAndMenu(); + this.main.messagingService.send('windowFocused'); + this.main.messagingService.send('windowChanged'); }); } } diff --git a/src/browser/browserApi.ts b/src/browser/browserApi.ts index a034a52bba..d806275416 100644 --- a/src/browser/browserApi.ts +++ b/src/browser/browserApi.ts @@ -215,11 +215,9 @@ export class BrowserApi { } static reloadOpenWindows() { - if(!BrowserApi.isSafariApi) - { - var sidebarName : string = 'sidebar'; - var sidebarWindows = chrome.extension.getViews({ type: sidebarName }); - if(sidebarWindows && sidebarWindows.length > 0) { + if (!BrowserApi.isSafariApi) { + const sidebarWindows = chrome.extension.getViews({ type: 'sidebar' }); + if (sidebarWindows && sidebarWindows.length > 0) { sidebarWindows[0].location.reload(); } } diff --git a/src/content/sso.ts b/src/content/sso.ts index cc936d91ab..326758b90a 100644 --- a/src/content/sso.ts +++ b/src/content/sso.ts @@ -1,13 +1,13 @@ -window.addEventListener("message", function(event) { - if (event.source != window) +window.addEventListener('message', (event) => { + if (event.source !== window) return; - if (event.data.command && (event.data.command == "authResult")) { + if (event.data.command && (event.data.command === 'authResult')) { chrome.runtime.sendMessage({ command: event.data.command, code: event.data.code, state: event.data.state, - referrer: event.source.location.hostname + referrer: event.source.location.hostname, }); } -}, false) \ No newline at end of file +}, false) diff --git a/src/popup/accounts/home.component.ts b/src/popup/accounts/home.component.ts index 49ec71d8a8..838f5b6d69 100644 --- a/src/popup/accounts/home.component.ts +++ b/src/popup/accounts/home.component.ts @@ -13,43 +13,40 @@ import { Utils } from 'jslib/misc/utils'; selector: 'app-home', templateUrl: 'home.component.html', }) -export class HomeComponent { - constructor( - protected platformUtilsService: PlatformUtilsService, - private passwordGenerationService : PasswordGenerationService, - private cryptoFunctionService: CryptoFunctionService, - private environmentService: EnvironmentService, - private storageService : StorageService) { } +export class HomeComponent { + constructor(protected platformUtilsService: PlatformUtilsService, + private passwordGenerationService: PasswordGenerationService, private storageService: StorageService, + private cryptoFunctionService: CryptoFunctionService, private environmentService: EnvironmentService) { } - async launchSsoBrowser() { - // Generate necessary sso params - const passwordOptions: any = { - type: 'password', - length: 64, - uppercase: true, - lowercase: true, - numbers: true, - special: false, - }; + async launchSsoBrowser() { + // Generate necessary sso params + const passwordOptions: any = { + type: 'password', + length: 64, + uppercase: true, + lowercase: true, + numbers: true, + special: false, + }; - const state = (await this.passwordGenerationService.generatePassword(passwordOptions)) + ':clientId=browser'; - let codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); - const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, 'sha256'); - const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); - - await this.storageService.save(ConstantsService.ssoCodeVerifierKey, codeVerifier); - await this.storageService.save(ConstantsService.ssoStateKey, state); + const state = (await this.passwordGenerationService.generatePassword(passwordOptions)) + ':clientId=browser'; + const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); + const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, 'sha256'); + const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); - let url = this.environmentService.getWebVaultUrl(); - if (url == null) { - url = 'https://vault.bitwarden.com'; - } + await this.storageService.save(ConstantsService.ssoCodeVerifierKey, codeVerifier); + await this.storageService.save(ConstantsService.ssoStateKey, state); - const redirectUri = url + '/sso-connector.html'; - - // Launch browser - this.platformUtilsService.launchUri(url + '/#/sso?clientId=browser' + - '&redirectUri=' + encodeURIComponent(redirectUri) + - '&state=' + state + '&codeChallenge=' + codeChallenge); - } + let url = this.environmentService.getWebVaultUrl(); + if (url == null) { + url = 'https://vault.bitwarden.com'; + } + + const redirectUri = url + '/sso-connector.html'; + + // Launch browser + this.platformUtilsService.launchUri(url + '/#/sso?clientId=browser' + + '&redirectUri=' + encodeURIComponent(redirectUri) + + '&state=' + state + '&codeChallenge=' + codeChallenge); + } } diff --git a/src/popup/accounts/login.component.ts b/src/popup/accounts/login.component.ts index 66873e538c..073d182df1 100644 --- a/src/popup/accounts/login.component.ts +++ b/src/popup/accounts/login.component.ts @@ -22,8 +22,8 @@ export class LoginComponent extends BaseLoginComponent { protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService, protected stateService: StateService, protected environmentService: EnvironmentService, protected passwordGenerationService: PasswordGenerationService, - protected cryptoFunctionService: CryptoFunctionService, - storageService: StorageService, syncService : SyncService) { + protected cryptoFunctionService: CryptoFunctionService, storageService: StorageService, + syncService: SyncService) { super(authService, router, platformUtilsService, i18nService, stateService, environmentService, passwordGenerationService, cryptoFunctionService, storageService); super.onSuccessfulLogin = () => { return syncService.fullSync(true); diff --git a/src/popup/accounts/sso.component.ts b/src/popup/accounts/sso.component.ts index e4f9a07c84..c88f3d1e60 100644 --- a/src/popup/accounts/sso.component.ts +++ b/src/popup/accounts/sso.component.ts @@ -29,23 +29,23 @@ export class SsoComponent extends BaseSsoComponent { storageService: StorageService, stateService: StateService, platformUtilsService: PlatformUtilsService, apiService: ApiService, cryptoFunctionService: CryptoFunctionService, passwordGenerationService: PasswordGenerationService, - syncService: SyncService, private environmentService: EnvironmentService ) { + syncService: SyncService, private environmentService: EnvironmentService) { super(authService, router, i18nService, route, storageService, stateService, platformUtilsService, apiService, cryptoFunctionService, passwordGenerationService); - + let url = this.environmentService.getWebVaultUrl(); if (url == null) { url = 'https://vault.bitwarden.com'; } - + this.redirectUri = url + '/sso-connector.html'; this.clientId = 'browser'; - + super.onSuccessfulLogin = () => { - BrowserApi.reloadOpenWindows(); - const thisWindow = window.open('', '_self'); - thisWindow.close(); - return syncService.fullSync(true); + BrowserApi.reloadOpenWindows(); + const thisWindow = window.open('', '_self'); + thisWindow.close(); + return syncService.fullSync(true); }; } } diff --git a/src/popup/vault/view.component.html b/src/popup/vault/view.component.html index e3f3da2165..439b3f2bfa 100644 --- a/src/popup/vault/view.component.html +++ b/src/popup/vault/view.component.html @@ -17,7 +17,7 @@
-
-
-