diff --git a/src/app/send/send.component.ts b/src/app/send/send.component.ts index 577579a51a..68889f9e0e 100644 --- a/src/app/send/send.component.ts +++ b/src/app/send/send.component.ts @@ -53,6 +53,6 @@ export class SendComponent extends BaseSendComponent implements OnInit { } get selectedSendType() { - return this.sends.find((s) => s.id === this.sendId).type; + return this.sends.find(s => s.id === this.sendId).type; } } diff --git a/src/app/vault/vault.component.ts b/src/app/vault/vault.component.ts index f0bb25ccee..34e543f468 100644 --- a/src/app/vault/vault.component.ts +++ b/src/app/vault/vault.component.ts @@ -40,11 +40,14 @@ import { EventType } from 'jslib/enums/eventType'; import { CipherView } from 'jslib/models/view/cipherView'; import { FolderView } from 'jslib/models/view/folderView'; +import { userError } from '@angular/compiler-cli/src/transformers/util'; 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 { SyncService } from 'jslib/abstractions/sync.service'; +import { TotpService } from 'jslib/abstractions/totp.service'; +import { UserService } from 'jslib/abstractions/user.service'; const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours const BroadcasterSubscriptionId = 'VaultComponent'; @@ -77,6 +80,7 @@ export class VaultComponent implements OnInit, OnDestroy { addCollectionIds: string[] = null; showingModal = false; deleted = false; + userHasPremiumAccess = false; private modal: ModalComponent = null; @@ -85,9 +89,11 @@ export class VaultComponent implements OnInit, OnDestroy { private broadcasterService: BroadcasterService, private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone, private syncService: SyncService, private analytics: Angulartics2, private toasterService: ToasterService, private messagingService: MessagingService, - private platformUtilsService: PlatformUtilsService, private eventService: EventService) { } + private platformUtilsService: PlatformUtilsService, private eventService: EventService, + private totpService: TotpService, private userService: UserService) { } async ngOnInit() { + this.userHasPremiumAccess = await this.userService.canAccessPremium(); this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { this.ngZone.run(async () => { let detectChanges = true; @@ -170,6 +176,14 @@ export class VaultComponent implements OnInit, OnDestroy { this.copyValue(pCipher.login.password, 'password'); } break; + case 'copyTotp': + const tComponent = this.addEditComponent == null ? this.viewComponent : this.addEditComponent; + const tCipher = tComponent != null ? tComponent.cipher : null; + if (this.cipherId != null && tCipher != null && tCipher.id === this.cipherId && + tCipher.login != null && tCipher.login.hasTotp && this.userHasPremiumAccess) { + const value = await this.totpService.getCode(tCipher.login.totp); + this.copyValue(value, 'verificationCodeTotp'); + } default: detectChanges = false; break; @@ -308,6 +322,15 @@ export class VaultComponent implements OnInit, OnDestroy { }, })); } + if (cipher.login.hasTotp && (cipher.organizationUseTotp || this.userHasPremiumAccess)) { + menu.append(new remote.MenuItem({ + label: this.i18nService.t('copyVerificationCodeTotp'), + click: async () => { + const value = await this.totpService.getCode(cipher.login.totp); + this.copyValue(value, 'verificationCodeTotp'); + }, + })); + } break; case CipherType.Card: if (cipher.card.number != null || cipher.card.code != null) { diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index 57dcbcc9ae..862ea26f90 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -367,6 +367,9 @@ "copyUri": { "message": "Copy URI" }, + "copyVerificationCodeTotp": { + "message": "Copy Verification Code (TOTP)" + }, "length": { "message": "Length" }, diff --git a/src/main/menu.main.ts b/src/main/menu.main.ts index 9ae2e2d67a..0d7d124e39 100644 --- a/src/main/menu.main.ts +++ b/src/main/menu.main.ts @@ -38,6 +38,7 @@ export class MenuMain extends BaseMenu { searchVault: MenuItem; copyUsername: MenuItem; copyPassword: MenuItem; + copyTotp: MenuItem; unlockedRequiredMenuItems: MenuItem[] = []; constructor(private main: Main) { @@ -67,6 +68,7 @@ export class MenuMain extends BaseMenu { this.searchVault = this.menu.getMenuItemById('searchVault'); this.copyUsername = this.menu.getMenuItemById('copyUsername'); this.copyPassword = this.menu.getMenuItemById('copyPassword'); + this.copyTotp = this.menu.getMenuItemById('copyTotp'); this.unlockedRequiredMenuItems = [ this.addNewLogin, this.addNewItem, this.addNewFolder, @@ -170,6 +172,12 @@ export class MenuMain extends BaseMenu { click: () => this.main.messagingService.send('copyPassword'), accelerator: 'CmdOrCtrl+P', }, + { + label: this.main.i18nService.t('copyVerificationCodeTotp'), + id: 'copyTotp', + click: () => this.main.messagingService.send('copyTotp'), + accelerator: 'CmdOrCtrl+T', + }, ]); if (!isWindowsStore() && !isMacAppStore()) { diff --git a/src/main/nativeMessaging.main.ts b/src/main/nativeMessaging.main.ts index 9ab6c47268..4c6415cdd9 100644 --- a/src/main/nativeMessaging.main.ts +++ b/src/main/nativeMessaging.main.ts @@ -73,8 +73,8 @@ export class NativeMessagingMain { 'allowed_origins': [ 'chrome-extension://nngceckbapebfimnlniiiahkandclblb/', 'chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh/', - 'chrome-extension://ccnckbpmaceehanjmeomladnmlffdjgn/' - ] + 'chrome-extension://ccnckbpmaceehanjmeomladnmlffdjgn/', + ], }}; switch (process.platform) { diff --git a/tslint.json b/tslint.json index 49f3444b9a..56dfb1f867 100644 --- a/tslint.json +++ b/tslint.json @@ -58,6 +58,18 @@ "semicolon": [ true, "always" + ], + "trailing-comma": [ + true, + { + "multiline": { + "objects": "always", + "arrays": "always", + "functions": "ignore", + "typeLiterals": "ignore" + }, + "singleline": "never" + } ] } }