1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-30 23:23:52 +00:00

[Account Switching] [Feature] Add the ability to maintain state for up to 5 accounts at once (#1079)

* [refactor] Remove references to deprecated services

* [feature] Implement account switching

* [bug] Fix state handling for authentication dependent system menu items

* [bug] Enable the account switcher to fucntion properly when switching to a locked accounts

* [feature] Enable locking any account from the menu

* [bug] Ensure the avatar instance used in the account switcher updates on account change

* [style] Fix lint complaints

* [bug] Ensure the logout command callback can handle any user in state

* [style] Fix lint complaints

* rollup

* [style] Fix lint complaints

* [bug] Don't clean up state until everything else is done on logout

* [bug] Navigate to vault on a succesful account switch

* [bug] Init the state service on start

* [feature] Limit account switching to 5 account maximum

* [bug] Resolve app lock state with 5 logged out accounts

* [chore] Update account refrences to match recent jslib restructuring

* [bug] Add missing awaits

* [bug] Update app menu on logout

* [bug] Hide the switcher if there are no authed accounts

* [bug] Move authenticationStatus display information out of jslib

* [bug] Remove unused active style from scss

* [refactor] Rewrite the menu bar

* [style] Fix lint complaints

* [bug] Clean state of loggout out user after redirect

* [bug] Redirect on logout if not explicity provided a userId that isn't active

* [bug] Relocated several settings items to persistant storage

* [bug] Correct account switcher styles on all themes

* [chore] Include state migration service in services

* [bug] Swap to next account on logout

* [bug] Correct DI service

* [bug] fix loginGuard deps in services.module

* [chore] update jslib

* [bug] Remove badly merged scss

* [chore] update jslib

* [review] Code review cleanup

* [review] Code review cleanup

Co-authored-by: Hinton <oscar@oscarhinton.com>
This commit is contained in:
Addison Beck
2021-12-15 17:32:00 -05:00
committed by GitHub
parent 5865f08b37
commit 0b306ca1a7
56 changed files with 2106 additions and 837 deletions

View File

@@ -19,14 +19,10 @@ import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { LockComponent as BaseLockComponent } from 'jslib-angular/components/lock.component';
import { ConstantsService } from 'jslib-common/services/constants.service';
const BroadcasterSubscriptionId = 'LockComponent';
@Component({
@@ -38,20 +34,19 @@ export class LockComponent extends BaseLockComponent implements OnDestroy {
constructor(router: Router, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, messagingService: MessagingService,
userService: UserService, cryptoService: CryptoService,
storageService: StorageService, vaultTimeoutService: VaultTimeoutService,
cryptoService: CryptoService, vaultTimeoutService: VaultTimeoutService,
environmentService: EnvironmentService, stateService: StateService,
apiService: ApiService, private route: ActivatedRoute,
private broadcasterService: BroadcasterService, ngZone: NgZone,
logService: LogService, keyConnectorService: KeyConnectorService) {
super(router, i18nService, platformUtilsService, messagingService, userService, cryptoService,
storageService, vaultTimeoutService, environmentService, stateService, apiService, logService,
super(router, i18nService, platformUtilsService, messagingService, cryptoService,
vaultTimeoutService, environmentService, stateService, apiService, logService,
keyConnectorService, ngZone);
}
async ngOnInit() {
await super.ngOnInit();
const autoPromptBiometric = !await this.storageService.get<boolean>(ConstantsService.disableAutoBiometricsPromptKey);
const autoPromptBiometric = !await this.stateService.getNoAutoPromptBiometrics();
this.route.queryParams.subscribe(params => {
if (this.supportsBiometric && params.promptBiometric && autoPromptBiometric) {

View File

@@ -1,3 +1,9 @@
<div class="login-header">
<a href="#" appStopClick (click)="settings()" class="environment-urls-settings-icon" attr.aria-label="{{'settings' | i18n}}">
{{'settings' | i18n}}
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
</a>
</div>
<form id="login-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise" attr.aria-hidden="{{showingModal}}">
<div id="content" class="content">
<img class="logo-image" alt="Bitwarden">
@@ -47,10 +53,6 @@
<div class="sub-options">
<a routerLink="/hint">{{'getMasterPasswordHint' | i18n}}</a>
</div>
<a href="#" appStopClick (click)="settings()" class="settings-icon" attr.aria-label="{{'settings' | i18n}}">
<i class="fa fa-cog fa-lg" aria-hidden="true"></i><span
aria-hidden="true">&nbsp;{{'settings' | i18n}}</span>
</a>
</div>
</form>
<ng-template #environment></ng-template>

View File

@@ -20,7 +20,6 @@ import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ModalService } from 'jslib-angular/services/modal.service';
@@ -44,11 +43,11 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
syncService: SyncService, private modalService: ModalService,
platformUtilsService: PlatformUtilsService, stateService: StateService,
environmentService: EnvironmentService, passwordGenerationService: PasswordGenerationService,
cryptoFunctionService: CryptoFunctionService, storageService: StorageService,
private broadcasterService: BroadcasterService, ngZone: NgZone,
private messagingService: MessagingService, logService: LogService) {
cryptoFunctionService: CryptoFunctionService, private broadcasterService: BroadcasterService,
ngZone: NgZone, private messagingService: MessagingService,
logService: LogService) {
super(authService, router, platformUtilsService, i18nService, stateService, environmentService,
passwordGenerationService, cryptoFunctionService, storageService, logService, ngZone);
passwordGenerationService, cryptoFunctionService, logService, ngZone);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};

View File

@@ -4,7 +4,7 @@ import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { PremiumComponent as BasePremiumComponent } from 'jslib-angular/components/premium.component';
@@ -14,8 +14,8 @@ import { PremiumComponent as BasePremiumComponent } from 'jslib-angular/componen
})
export class PremiumComponent extends BasePremiumComponent {
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
apiService: ApiService, userService: UserService,
logService: LogService) {
super(i18nService, platformUtilsService, apiService, userService, logService);
apiService: ApiService, logService: LogService,
stateService: StateService) {
super(i18nService, platformUtilsService, apiService, logService, stateService);
}
}

View File

@@ -17,8 +17,8 @@ import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { UserService } from 'jslib-common/abstractions/user.service';
const BroadcasterSubscriptionId = 'SetPasswordComponent';
@@ -33,12 +33,14 @@ import {
export class SetPasswordComponent extends BaseSetPasswordComponent implements OnDestroy {
constructor(apiService: ApiService, i18nService: I18nService,
cryptoService: CryptoService, messagingService: MessagingService,
userService: UserService, passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService, policyService: PolicyService, router: Router,
passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService,
policyService: PolicyService, router: Router,
syncService: SyncService, route: ActivatedRoute,
private broadcasterService: BroadcasterService, private ngZone: NgZone) {
super(i18nService, cryptoService, messagingService, userService, passwordGenerationService,
platformUtilsService, policyService, router, apiService, syncService, route);
private broadcasterService: BroadcasterService, private ngZone: NgZone,
stateService: StateService) {
super(i18nService, cryptoService, messagingService, passwordGenerationService,
platformUtilsService, policyService, router, apiService, syncService, route,
stateService);
}
get masterPasswordScoreWidth() {

View File

@@ -13,19 +13,16 @@ import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { ModalService } from 'jslib-angular/services/modal.service';
import { ElectronConstants } from 'jslib-electron/electronConstants';
import { SetPinComponent } from '../components/set-pin.component';
import { Utils } from 'jslib-common/misc/utils';
import { isWindowsStore } from 'jslib-electron/utils';
import { SetPinComponent } from '../components/set-pin.component';
import { StorageLocation } from 'jslib-common/enums/storageLocation';
@Component({
selector: 'app-settings',
@@ -72,9 +69,9 @@ export class SettingsComponent implements OnInit {
vaultTimeout: FormControl = new FormControl(null);
constructor(private i18nService: I18nService, private platformUtilsService: PlatformUtilsService,
private storageService: StorageService, private vaultTimeoutService: VaultTimeoutService,
private stateService: StateService, private messagingService: MessagingService,
private cryptoService: CryptoService, private modalService: ModalService) {
private vaultTimeoutService: VaultTimeoutService, private stateService: StateService,
private messagingService: MessagingService, private cryptoService: CryptoService,
private modalService: ModalService) {
const isMac = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
// Workaround to avoid ghosting trays https://github.com/electron/electron/issues/17622
@@ -153,31 +150,29 @@ export class SettingsComponent implements OnInit {
async ngOnInit() {
this.showMinToTray = this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop;
this.vaultTimeout.setValue(await this.vaultTimeoutService.getVaultTimeout());
this.vaultTimeoutAction = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey);
this.vaultTimeout.setValue(await this.stateService.getVaultTimeout());
this.vaultTimeoutAction = await this.stateService.getVaultTimeoutAction();
const pinSet = await this.vaultTimeoutService.isPinLockSet();
this.pin = pinSet[0] || pinSet[1];
this.disableFavicons = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey);
this.enableBrowserIntegration = await this.storageService.get<boolean>(
ElectronConstants.enableBrowserIntegration);
this.enableBrowserIntegrationFingerprint = await this.storageService.get<boolean>(ElectronConstants.enableBrowserIntegrationFingerprint);
this.enableMinToTray = await this.storageService.get<boolean>(ElectronConstants.enableMinimizeToTrayKey);
this.enableCloseToTray = await this.storageService.get<boolean>(ElectronConstants.enableCloseToTrayKey);
this.enableTray = await this.storageService.get<boolean>(ElectronConstants.enableTrayKey);
this.startToTray = await this.storageService.get<boolean>(ElectronConstants.enableStartToTrayKey);
this.locale = await this.storageService.get<string>(ConstantsService.localeKey);
this.theme = await this.storageService.get<string>(ConstantsService.themeKey);
this.clearClipboard = await this.storageService.get<number>(ConstantsService.clearClipboardKey);
this.minimizeOnCopyToClipboard = await this.storageService.get<boolean>(
ElectronConstants.minimizeOnCopyToClipboardKey);
this.disableFavicons = await this.stateService.getDisableFavicon();
this.enableBrowserIntegration = await this.stateService.getEnableBrowserIntegration();
this.enableBrowserIntegrationFingerprint = await this.stateService.getEnableBrowserIntegrationFingerprint();
this.enableMinToTray = await this.stateService.getEnableMinimizeToTray();
this.enableCloseToTray = await this.stateService.getEnableCloseToTray();
this.enableTray = await this.stateService.getEnableTray();
this.startToTray = await this.stateService.getEnableStartToTray();
this.locale = await this.stateService.getLocale();
this.theme = await this.stateService.getTheme();
this.clearClipboard = await this.stateService.getClearClipboard();
this.minimizeOnCopyToClipboard = await this.stateService.getMinimizeOnCopyToClipboard();
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
this.biometric = await this.vaultTimeoutService.isBiometricLockSet();
this.biometricText = await this.storageService.get<string>(ConstantsService.biometricText);
this.noAutoPromptBiometrics = await this.storageService.get<boolean>(ConstantsService.disableAutoBiometricsPromptKey);
this.noAutoPromptBiometricsText = await this.storageService.get<string>(ElectronConstants.noAutoPromptBiometricsText);
this.alwaysShowDock = await this.storageService.get<boolean>(ElectronConstants.alwaysShowDock);
this.biometricText = await this.stateService.getBiometricText();
this.noAutoPromptBiometrics = await this.stateService.getNoAutoPromptBiometrics();
this.noAutoPromptBiometricsText = await this.stateService.getNoAutoPromptBiometricsText();
this.alwaysShowDock = await this.stateService.getAlwaysShowDock();
this.showAlwaysShowDock = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
this.openAtLogin = await this.storageService.get<boolean>(ElectronConstants.openAtLogin);
this.openAtLogin = await this.stateService.getOpenAtLogin();
}
async saveVaultTimeoutOptions() {
@@ -233,13 +228,13 @@ export class SettingsComponent implements OnInit {
return;
}
if (this.biometric) {
await this.storageService.save(ConstantsService.biometricUnlockKey, true);
await this.stateService.setBiometricUnlock(true);
} else {
await this.storageService.remove(ConstantsService.biometricUnlockKey);
await this.storageService.remove(ConstantsService.disableAutoBiometricsPromptKey);
await this.stateService.setBiometricUnlock(null);
await this.stateService.setNoAutoPromptBiometrics(null);
this.noAutoPromptBiometrics = false;
}
this.vaultTimeoutService.biometricLocked = false;
await this.stateService.setBiometricLocked(false);
await this.cryptoService.toggleKey();
}
@@ -249,29 +244,29 @@ export class SettingsComponent implements OnInit {
}
if (this.noAutoPromptBiometrics) {
await this.storageService.save(ConstantsService.disableAutoBiometricsPromptKey, true);
await this.stateService.setNoAutoPromptBiometrics(true);
} else {
await this.storageService.remove(ConstantsService.disableAutoBiometricsPromptKey);
await this.stateService.setNoAutoPromptBiometrics(null);
}
}
async saveFavicons() {
await this.storageService.save(ConstantsService.disableFaviconKey, this.disableFavicons);
await this.stateService.save(ConstantsService.disableFaviconKey, this.disableFavicons);
await this.stateService.setDisableFavicon(this.disableFavicons);
await this.stateService.setDisableFavicon(this.disableFavicons, { storageLocation: StorageLocation.Disk });
this.messagingService.send('refreshCiphers');
}
async saveMinToTray() {
await this.storageService.save(ElectronConstants.enableMinimizeToTrayKey, this.enableMinToTray);
await this.stateService.setEnableMinimizeToTray(this.enableMinToTray);
}
async saveCloseToTray() {
if (this.requireEnableTray) {
this.enableTray = true;
await this.storageService.save(ElectronConstants.enableTrayKey, this.enableTray);
await this.stateService.setEnableTray(this.enableTray);
}
await this.storageService.save(ElectronConstants.enableCloseToTrayKey, this.enableCloseToTray);
await this.stateService.setEnableCloseToTray(this.enableCloseToTray);
}
async saveTray() {
@@ -282,9 +277,9 @@ export class SettingsComponent implements OnInit {
if (confirm) {
this.startToTray = false;
await this.storageService.save(ElectronConstants.enableStartToTrayKey, this.startToTray);
await this.stateService.setEnableStartToTray(this.startToTray);
this.enableCloseToTray = false;
await this.storageService.save(ElectronConstants.enableCloseToTrayKey, this.enableCloseToTray);
await this.stateService.setEnableCloseToTray(this.enableCloseToTray);
} else {
this.enableTray = true;
}
@@ -292,42 +287,42 @@ export class SettingsComponent implements OnInit {
return;
}
await this.storageService.save(ElectronConstants.enableTrayKey, this.enableTray);
await this.stateService.setEnableTray(this.enableTray);
this.messagingService.send(this.enableTray ? 'showTray' : 'removeTray');
}
async saveStartToTray() {
if (this.requireEnableTray) {
this.enableTray = true;
await this.storageService.save(ElectronConstants.enableTrayKey, this.enableTray);
await this.stateService.setEnableTray(this.enableTray);
}
await this.storageService.save(ElectronConstants.enableStartToTrayKey, this.startToTray);
await this.stateService.setEnableStartToTray(this.startToTray);
}
async saveLocale() {
await this.storageService.save(ConstantsService.localeKey, this.locale);
await this.stateService.setLocale(this.locale);
}
async saveTheme() {
await this.storageService.save(ConstantsService.themeKey, this.theme);
await this.stateService.setTheme(this.theme);
window.setTimeout(() => window.location.reload(), 200);
}
async saveMinOnCopyToClipboard() {
await this.storageService.save(ElectronConstants.minimizeOnCopyToClipboardKey, this.minimizeOnCopyToClipboard);
await this.stateService.setMinimizeOnCopyToClipboard(this.minimizeOnCopyToClipboard);
}
async saveClearClipboard() {
await this.storageService.save(ConstantsService.clearClipboardKey, this.clearClipboard);
await this.stateService.setClearClipboard(this.clearClipboard);
}
async saveAlwaysShowDock() {
await this.storageService.save(ElectronConstants.alwaysShowDock, this.alwaysShowDock);
await this.stateService.setAlwaysShowDock(this.alwaysShowDock);
}
async saveOpenAtLogin() {
this.storageService.save(ElectronConstants.openAtLogin, this.openAtLogin);
this.stateService.setOpenAtLogin(this.openAtLogin);
this.messagingService.send(this.openAtLogin ? 'addOpenAtLogin' : 'removeOpenAtLogin');
}
@@ -350,7 +345,7 @@ export class SettingsComponent implements OnInit {
return;
}
await this.storageService.save(ElectronConstants.enableBrowserIntegration, this.enableBrowserIntegration);
await this.stateService.setEnableBrowserIntegration(this.enableBrowserIntegration);
this.messagingService.send(this.enableBrowserIntegration ? 'enableBrowserIntegration' : 'disableBrowserIntegration');
if (!this.enableBrowserIntegration) {
@@ -360,6 +355,6 @@ export class SettingsComponent implements OnInit {
}
async saveBrowserIntegrationFingerprint() {
await this.storageService.save(ElectronConstants.enableBrowserIntegrationFingerprint, this.enableBrowserIntegrationFingerprint);
await this.stateService.setEnableBrowserIntegrationFingerprint(this.enableBrowserIntegrationFingerprint);
}
}

View File

@@ -14,7 +14,6 @@ import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { SsoComponent as BaseSsoComponent } from 'jslib-angular/components/sso.component';
@@ -26,11 +25,11 @@ import { SsoComponent as BaseSsoComponent } from 'jslib-angular/components/sso.c
export class SsoComponent extends BaseSsoComponent {
constructor(authService: AuthService, router: Router,
i18nService: I18nService, syncService: SyncService, route: ActivatedRoute,
storageService: StorageService, stateService: StateService,
platformUtilsService: PlatformUtilsService, apiService: ApiService,
cryptoFunctionService: CryptoFunctionService, environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService, logService: LogService) {
super(authService, router, i18nService, route, storageService, stateService, platformUtilsService,
stateService: StateService, platformUtilsService: PlatformUtilsService,
apiService: ApiService, cryptoFunctionService: CryptoFunctionService,
environmentService: EnvironmentService, passwordGenerationService: PasswordGenerationService,
logService: LogService) {
super(authService, router, i18nService, route, stateService, platformUtilsService,
apiService, cryptoFunctionService, environmentService, passwordGenerationService, logService);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);

View File

@@ -20,7 +20,6 @@ import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ModalService } from 'jslib-angular/services/modal.service';
@@ -40,10 +39,10 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
i18nService: I18nService, apiService: ApiService,
platformUtilsService: PlatformUtilsService, syncService: SyncService,
environmentService: EnvironmentService, private modalService: ModalService,
stateService: StateService, storageService: StorageService, route: ActivatedRoute,
stateService: StateService, route: ActivatedRoute,
logService: LogService) {
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService,
stateService, storageService, route, logService);
stateService, route, logService);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};

View File

@@ -8,8 +8,8 @@ import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from 'jslib-angular/components/update-temp-password.component';
@@ -56,10 +56,10 @@ export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent
}
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService, policyService: PolicyService,
cryptoService: CryptoService, userService: UserService,
messagingService: MessagingService, apiService: ApiService,
syncService: SyncService, logService: LogService) {
cryptoService: CryptoService, messagingService: MessagingService,
apiService: ApiService, syncService: SyncService,
logService: LogService, stateService: StateService) {
super(i18nService, platformUtilsService, passwordGenerationService, policyService, cryptoService,
userService, messagingService, apiService, syncService, logService);
messagingService, apiService, stateService, syncService, logService);
}
}

View File

@@ -6,7 +6,7 @@ import {
import { AuthGuardService } from 'jslib-angular/services/auth-guard.service';
import { LockGuardService } from 'jslib-angular/services/lock-guard.service';
import { UnauthGuardService } from 'jslib-angular/services/unauth-guard.service';
import { LoginGuardService } from '../services/loginGuard.service';
import { HintComponent } from './accounts/hint.component';
import { LockComponent } from './accounts/lock.component';
@@ -32,8 +32,7 @@ const routes: Routes = [
{
path: 'login',
component: LoginComponent,
canActivate: [UnauthGuardService],
canActivate: [LoginGuardService],
},
{ path: '2fa', component: TwoFactorComponent },
{ path: 'register', component: RegisterComponent },

View File

@@ -36,23 +36,22 @@ import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { SettingsService } from 'jslib-common/abstractions/settings.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { SystemService } from 'jslib-common/abstractions/system.service';
import { TokenService } from 'jslib-common/abstractions/token.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { CipherType } from 'jslib-common/enums/cipherType';
import { ModalRef } from 'jslib-angular/components/modal/modal.ref';
import { ModalService } from 'jslib-angular/services/modal.service';
import { ExportComponent } from './vault/export.component';
import { FolderAddEditComponent } from './vault/folder-add-edit.component';
import { PasswordGeneratorComponent } from './vault/password-generator.component';
import { ModalRef } from 'jslib-angular/components/modal/modal.ref';
import { ModalService } from 'jslib-angular/services/modal.service';
import { MenuUpdateRequest } from 'src/main/menu.updater';
const BroadcasterSubscriptionId = 'AppComponent';
const IdleTimeout = 60000 * 10; // 10 minutes
const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
@@ -67,7 +66,8 @@ const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
<ng-template #appFolderAddEdit></ng-template>
<ng-template #exportVault></ng-template>
<ng-template #appPasswordGenerator></ng-template>
<router-outlet></router-outlet>`,
<app-header></app-header>
<div id="container"><router-outlet></router-outlet></div>`,
})
export class AppComponent implements OnInit {
@ViewChild('settings', { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef;
@@ -84,14 +84,14 @@ export class AppComponent implements OnInit {
private idleTimer: number = null;
private isIdle = false;
constructor(private broadcasterService: BroadcasterService, private userService: UserService,
constructor(private broadcasterService: BroadcasterService,
private tokenService: TokenService, private folderService: FolderService,
private settingsService: SettingsService, private syncService: SyncService,
private passwordGenerationService: PasswordGenerationService, private cipherService: CipherService,
private authService: AuthService, private router: Router,
private toastrService: ToastrService, private i18nService: I18nService,
private sanitizer: DomSanitizer, private ngZone: NgZone,
private vaultTimeoutService: VaultTimeoutService, private storageService: StorageService,
private vaultTimeoutService: VaultTimeoutService,
private cryptoService: CryptoService, private logService: LogService,
private messagingService: MessagingService, private collectionService: CollectionService,
private searchService: SearchService, private notificationsService: NotificationsService,
@@ -103,7 +103,7 @@ export class AppComponent implements OnInit {
ngOnInit() {
this.ngZone.runOutsideAngular(() => {
setTimeout(async () => {
await this.updateAppMenu('auth');
await this.updateAppMenu();
}, 1000);
window.onmousemove = () => this.recordActivity();
@@ -120,7 +120,7 @@ export class AppComponent implements OnInit {
case 'loggedIn':
case 'unlocked':
this.notificationsService.updateConnection();
this.updateAppMenu('auth');
this.updateAppMenu();
this.systemService.cancelProcessReload();
break;
case 'loggedOut':
@@ -128,7 +128,7 @@ export class AppComponent implements OnInit {
this.modal.close();
}
this.notificationsService.updateConnection();
this.updateAppMenu('auth');
this.updateAppMenu();
this.systemService.startProcessReload();
await this.systemService.clearPendingClipboard();
break;
@@ -136,21 +136,29 @@ export class AppComponent implements OnInit {
this.router.navigate(['login']);
break;
case 'logout':
this.logOut(!!message.expired);
await this.logOut(!!message.expired, message.userId);
break;
case 'lockVault':
await this.vaultTimeoutService.lock(true);
await this.vaultTimeoutService.lock(true, message.userId);
break;
case 'lockAllVaults':
for (const userId in this.stateService.accounts.getValue()) {
if (userId != null) {
await this.vaultTimeoutService.lock(true, userId);
}
}
break;
case 'locked':
if (this.modal != null) {
this.modal.close();
}
this.stateService.purge();
this.router.navigate(['lock']);
this.updateAppMenu();
if (message.userId == null || message.userId === await this.stateService.getUserId()) {
this.router.navigate(['lock']);
}
this.notificationsService.updateConnection();
this.updateAppMenu('auth');
this.systemService.startProcessReload();
await this.systemService.clearPendingClipboard();
this.systemService.startProcessReload();
break;
case 'reloadProcess':
window.location.reload(true);
@@ -158,7 +166,7 @@ export class AppComponent implements OnInit {
case 'syncStarted':
break;
case 'syncCompleted':
await this.updateAppMenu('sync');
await this.updateAppMenu();
break;
case 'openSettings':
await this.openModal<SettingsComponent>(SettingsComponent, this.settingsRef);
@@ -168,7 +176,7 @@ export class AppComponent implements OnInit {
break;
case 'showFingerprintPhrase':
const fingerprint = await this.cryptoService.getFingerprint(
await this.userService.getUserId());
await this.stateService.getUserId());
const result = await this.platformUtilsService.showDialog(
this.i18nService.t('yourAccountsFingerprint') + ':\n' + fingerprint.join('-'),
this.i18nService.t('fingerprintPhrase'), this.i18nService.t('learnMore'),
@@ -322,52 +330,84 @@ export class AppComponent implements OnInit {
});
}
private async updateAppMenu(type: 'auth' | 'sync') {
let data: any;
if (type === 'sync') {
data = {
hideChangeMasterPass: await this.keyConnectorService.getUsesKeyConnector(),
private async updateAppMenu() {
let updateRequest: MenuUpdateRequest;
const stateAccounts = this.stateService.accounts?.getValue();
if (stateAccounts == null || Object.keys(stateAccounts).length < 1) {
updateRequest = {
accounts: null,
activeUserId: null,
hideChangeMasterPassword: true,
};
} else {
data = {
isAuthenticated: await this.userService.isAuthenticated(),
isLocked: await this.vaultTimeoutService.isLocked(),
const accounts: { [userId: string]: any } = {};
for (const i in stateAccounts) {
if (i != null && stateAccounts[i]?.profile?.userId != null) {
const userId = stateAccounts[i].profile.userId;
accounts[userId] = {
isAuthenticated: await this.stateService.getIsAuthenticated({
userId: userId,
}),
isLocked: await this.vaultTimeoutService.isLocked(userId),
email: stateAccounts[i].profile.email,
userId: stateAccounts[i].profile.userId,
};
}
}
updateRequest = {
accounts: accounts,
activeUserId: await this.stateService.getUserId(),
hideChangeMasterPassword: await this.keyConnectorService.getUsesKeyConnector(),
};
}
this.messagingService.send('updateAppMenu', data);
this.messagingService.send('updateAppMenu', { updateRequest: updateRequest });
}
private async logOut(expired: boolean) {
await this.eventService.uploadEvents();
const userId = await this.userService.getUserId();
private async logOut(expired: boolean, userId?: string) {
await Promise.all([
this.eventService.clearEvents(),
this.syncService.setLastSync(new Date(0)),
this.tokenService.clearToken(),
this.cryptoService.clearKeys(),
this.userService.clear(),
this.eventService.uploadEvents(userId),
this.syncService.setLastSync(new Date(0), userId),
this.tokenService.clearToken(userId),
this.cryptoService.clearKeys(userId),
this.settingsService.clear(userId),
this.cipherService.clear(userId),
this.folderService.clear(userId),
this.collectionService.clear(userId),
this.passwordGenerationService.clear(),
this.vaultTimeoutService.clear(),
this.stateService.purge(),
this.passwordGenerationService.clear(userId),
this.vaultTimeoutService.clear(userId),
this.policyService.clear(userId),
this.keyConnectorService.clear(),
]);
this.vaultTimeoutService.biometricLocked = true;
this.searchService.clearIndex();
this.authService.logOut(async () => {
if (expired) {
this.platformUtilsService.showToast('warning', this.i18nService.t('loggedOut'),
this.i18nService.t('loginExpired'));
}
await this.stateService.setBiometricLocked(true, { userId: userId });
if (userId == null || userId === await this.stateService.getUserId()) {
this.searchService.clearIndex();
this.authService.logOut(async () => {
if (expired) {
this.platformUtilsService.showToast('warning', this.i18nService.t('loggedOut'),
this.i18nService.t('loginExpired'));
}
});
}
await this.stateService.clean({ userId: userId });
if (this.stateService.activeAccount.getValue() == null) {
this.router.navigate(['login']);
});
} else {
const locked = await this.vaultTimeoutService.isLocked();
if (locked) {
this.messagingService.send('locked');
} else {
this.messagingService.send('unlocked');
this.messagingService.send('syncVault');
this.router.navigate(['vault']);
}
}
await this.updateAppMenu();
}
private async recordActivity() {
@@ -377,7 +417,7 @@ export class AppComponent implements OnInit {
}
this.lastActivity = now;
this.storageService.save(ConstantsService.lastActiveKey, now);
await this.stateService.setLastActive(now);
// Idle states
if (this.isIdle) {

View File

@@ -1,16 +1,18 @@
import 'zone.js/dist/zone';
import { AppRoutingModule } from './app-routing.module';
import { ServicesModule } from './services.module';
import { A11yModule } from '@angular/cdk/a11y';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { OverlayModule } from '@angular/cdk/overlay';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { DatePipe } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import 'zone.js/dist/zone';
import { AppRoutingModule } from './app-routing.module';
import { ServicesModule } from './services.module';
import { AppComponent } from './app.component';
@@ -29,6 +31,7 @@ import { TwoFactorComponent } from './accounts/two-factor.component';
import { UpdateTempPasswordComponent } from './accounts/update-temp-password.component';
import { VaultTimeoutInputComponent } from './accounts/vault-timeout-input.component';
import { AvatarComponent } from 'jslib-angular/components/avatar.component';
import { CalloutComponent } from 'jslib-angular/components/callout.component';
import { IconComponent } from 'jslib-angular/components/icon.component';
import { BitwardenToastModule } from 'jslib-angular/components/toastr.component';
@@ -70,7 +73,10 @@ import { AddEditComponent as SendAddEditComponent } from './send/add-edit.compon
import { EffluxDatesComponent as SendEffluxDatesComponent } from './send/efflux-dates.component';
import { SendComponent } from './send/send.component';
import { AccountSwitcherComponent } from './layout/account-switcher.component';
import { HeaderComponent } from './layout/header.component';
import { NavComponent } from './layout/nav.component';
import { SearchComponent } from './layout/search/search.component';
import { PasswordRepromptComponent } from './components/password-reprompt.component';
import { SetPinComponent } from './components/set-pin.component';
@@ -183,6 +189,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
}),
ScrollingModule,
A11yModule,
OverlayModule,
],
declarations: [
A11yTitleDirective,
@@ -239,6 +246,10 @@ registerLocaleData(localeZhTw, 'zh-TW');
VerifyMasterPasswordComponent,
ViewComponent,
ViewCustomFieldsComponent,
HeaderComponent,
AccountSwitcherComponent,
AvatarComponent,
SearchComponent,
],
providers: [DatePipe],
bootstrap: [AppComponent],

View File

@@ -0,0 +1,22 @@
<a class="account-switcher" (click)="toggle()" cdkOverlayOrigin #trigger="cdkOverlayOrigin" [hidden]="!showSwitcher">
<app-avatar [data]="activeAccountEmail" size="25" [circle]="true" [fontSize]="14" [dynamic]="true" *ngIf="activeAccountEmail != null"></app-avatar>
<span>{{activeAccountEmail}}</span>
<i class="fa" aria-hidden="true" [ngClass]="{'fa-chevron-down': !isOpen, 'fa-chevron-up': isOpen}"></i>
</a>
<ng-template cdkConnectedOverlay [cdkConnectedOverlayOrigin]="trigger" [cdkConnectedOverlayOpen]="isOpen" cdkConnectedOverlayMinWidth="250px">
<div class="account-switcher-dropdown" [@transformPanel]="'open'">
<div class="accounts">
<a *ngFor="let a of accounts | keyvalue" class="account" [ngClass]="{'active': a.value.profile.authenticationStatus == 'active'}"
href="#" appStopClick (click)="switch(a.key)">
<span class="email">{{a.value.profile.email}}</span>
<span class="server">{{a.value.settings.environmentUrls.server}}</span>
<span class="status">{{a.value.profile.authenticationStatus}}</span>
</a>
</div>
<div class="border"></div>
<a class="add" routerLink="/login" (click)="toggle()">
<i class="fa fa-plus" aria-hidden="true"></i> {{'addAccount' | i18n}}
</a>
</div>
</ng-template>

View File

@@ -0,0 +1,78 @@
import {
animate,
state,
style,
transition,
trigger,
} from '@angular/animations';
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { AuthenticationStatus } from 'jslib-common/enums/authenticationStatus';
import { Account } from 'jslib-common/models/domain/account';
@Component({
selector: 'app-account-switcher',
templateUrl: 'account-switcher.component.html',
animations: [
trigger('transformPanel', [
state('void', style({
opacity: 0,
})),
transition('void => open', animate('100ms linear', style({
opacity: 1,
}))),
transition('* => void', animate('100ms linear', style({opacity: 0}))),
]),
],
})
export class AccountSwitcherComponent implements OnInit {
isOpen: boolean = false;
accounts: { [userId: string]: Account } = {};
activeAccountEmail: string;
get showSwitcher() {
return this.accounts != null && Object.keys(this.accounts).length > 0;
}
constructor(private stateService: StateService, private vaultTimeoutService: VaultTimeoutService,
private messagingService: MessagingService, private router: Router) {}
async ngOnInit(): Promise<void> {
this.stateService.accounts.subscribe(async accounts => {
for (const userId in accounts) {
if (userId === await this.stateService.getUserId()) {
accounts[userId].profile.authenticationStatus = AuthenticationStatus.Active;
} else {
accounts[userId].profile.authenticationStatus = await this.vaultTimeoutService.isLocked(userId) ?
AuthenticationStatus.Locked :
AuthenticationStatus.Unlocked;
}
}
this.accounts = accounts;
this.activeAccountEmail = await this.stateService.getEmail();
});
}
toggle() {
this.isOpen = !this.isOpen;
}
async switch(userId: string) {
await this.stateService.setActiveUser(userId);
const locked = await this.vaultTimeoutService.isLocked(userId);
if (locked) {
this.messagingService.send('locked', { userId: userId });
} else {
this.messagingService.send('unlocked');
this.messagingService.send('syncVault');
this.router.navigate(['vault']);
}
}
}

View File

@@ -0,0 +1,4 @@
<div class="header">
<app-search></app-search>
<app-account-switcher></app-account-switcher>
</div>

View File

@@ -0,0 +1,8 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-header',
templateUrl: 'header.component.html',
})
export class HeaderComponent {
}

View File

@@ -0,0 +1,39 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
export type SearchBarState = {
enabled: boolean;
placeholderText: string;
};
@Injectable()
export class SearchBarService {
searchText = new BehaviorSubject<string>(null);
private _state = {
enabled: false,
placeholderText: '',
};
// tslint:disable-next-line:member-ordering
state = new BehaviorSubject<SearchBarState>(this._state);
setEnabled(enabled: boolean) {
this._state.enabled = enabled;
this.updateState();
}
setPlaceholderText(placeholderText: string) {
this._state.placeholderText = placeholderText;
this.updateState();
}
setSearchText(value: string) {
this.searchText.next(value);
}
private updateState() {
this.state.next(this._state);
}
}

View File

@@ -0,0 +1,4 @@
<div class="search" *ngIf="state.enabled">
<input type="search" [placeholder]="state.placeholderText" id="search" autocomplete="off" [formControl]="searchText" appAutofocus>
<i class="fa fa-search" aria-hidden="true"></i>
</div>

View File

@@ -0,0 +1,24 @@
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
import { SearchBarService, SearchBarState } from './search-bar.service';
@Component({
selector: 'app-search',
templateUrl: 'search.component.html',
})
export class SearchComponent {
state: SearchBarState;
searchText: FormControl = new FormControl(null);
constructor(private searchBarService: SearchBarService) {
this.searchBarService.state.subscribe(state => {
this.state = state;
});
this.searchText.valueChanges.subscribe(value => {
this.searchBarService.setSearchText(value);
});
}
}

View File

@@ -9,7 +9,7 @@ import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SendService } from 'jslib-common/abstractions/send.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { AddEditComponent as BaseAddEditComponent } from 'jslib-angular/components/send/add-edit.component';
@@ -20,12 +20,12 @@ import { AddEditComponent as BaseAddEditComponent } from 'jslib-angular/componen
export class AddEditComponent extends BaseAddEditComponent {
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService, datePipe: DatePipe,
sendService: SendService, userService: UserService,
sendService: SendService, stateService: StateService,
messagingService: MessagingService, policyService: PolicyService,
logService: LogService) {
super(i18nService, platformUtilsService, environmentService,
datePipe, sendService, userService, messagingService, policyService,
logService);
datePipe, sendService, messagingService, policyService,
logService, stateService);
}
async refresh() {

View File

@@ -3,7 +3,6 @@ import { DatePipe } from '@angular/common';
import {
Component,
OnChanges,
SimpleChanges,
} from '@angular/core';
import { ControlContainer, NgForm } from '@angular/forms';

View File

@@ -31,13 +31,6 @@
</div>
</div>
<div id="items" class="items">
<div class="header header-search">
<div class="search">
<input type="search" placeholder="{{'searchSends' | i18n}}" id="search"
[(ngModel)]="searchText" (input)="searchTextChanged()" autocomplete="off" appAutofocus>
<i class="fa fa-search" aria-hidden="true"></i>
</div>
</div>
<div class="content">
<div class="list" *ngIf="filteredSends.length">
<a *ngFor="let s of filteredSends" appStopClick (click)="selectSend(s.id)"

View File

@@ -14,7 +14,6 @@ import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.se
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { SendService } from 'jslib-common/abstractions/send.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { SendComponent as BaseSendComponent } from 'jslib-angular/components/send/send.component';
@@ -22,6 +21,7 @@ import { invokeMenu, RendererMenuItem } from 'jslib-electron/utils';
import { SendView } from 'jslib-common/models/view/sendView';
import { SearchBarService } from '../layout/search/search-bar.service';
import { AddEditComponent } from './add-edit.component';
enum Action {
@@ -46,13 +46,20 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
private broadcasterService: BroadcasterService, ngZone: NgZone,
searchService: SearchService, policyService: PolicyService,
userService: UserService, logService: LogService) {
private searchBarService: SearchBarService, logService: LogService) {
super(sendService, i18nService, platformUtilsService,
environmentService, ngZone, searchService,
policyService, userService, logService);
policyService, logService);
this.searchBarService.searchText.subscribe(searchText => {
this.searchText = searchText;
this.searchTextChanged();
});
}
async ngOnInit() {
this.searchBarService.setEnabled(true);
this.searchBarService.setPlaceholderText(this.i18nService.t('searchSends'));
super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
@@ -68,6 +75,7 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
this.searchBarService.setEnabled(false);
}
addSend() {

View File

@@ -10,13 +10,14 @@ import { ElectronRendererSecureStorageService } from 'jslib-electron/services/el
import { ElectronRendererStorageService } from 'jslib-electron/services/electronRendererStorage.service';
import { I18nService } from '../services/i18n.service';
import { LoginGuardService } from '../services/loginGuard.service';
import { NativeMessagingService } from '../services/nativeMessaging.service';
import { PasswordRepromptService } from '../services/passwordReprompt.service';
import { SearchBarService } from './layout/search/search-bar.service';
import { JslibServicesModule } from 'jslib-angular/services/jslib-services.module';
import { AuthService } from 'jslib-common/services/auth.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { ContainerService } from 'jslib-common/services/container.service';
import { EventService } from 'jslib-common/services/event.service';
import { SystemService } from 'jslib-common/services/system.service';
@@ -46,16 +47,17 @@ import { ThemeType } from 'jslib-common/enums/themeType';
export function initFactory(window: Window, environmentService: EnvironmentServiceAbstraction,
syncService: SyncServiceAbstraction, vaultTimeoutService: VaultTimeoutService,
storageService: StorageServiceAbstraction, i18nService: I18nService, eventService: EventService,
i18nService: I18nService, eventService: EventService,
authService: AuthService, notificationsService: NotificationsServiceAbstraction,
platformUtilsService: PlatformUtilsServiceAbstraction, stateService: StateServiceAbstraction,
cryptoService: CryptoServiceAbstraction): Function {
return async () => {
await stateService.init();
await environmentService.setUrlsFromStorage();
syncService.fullSync(true);
vaultTimeoutService.init(true);
const locale = await storageService.get<string>(ConstantsService.localeKey);
await vaultTimeoutService.init(true);
const locale = await stateService.getLocale();
await i18nService.init(locale);
eventService.init(true);
authService.init();
@@ -63,22 +65,18 @@ export function initFactory(window: Window, environmentService: EnvironmentServi
const htmlEl = window.document.documentElement;
htmlEl.classList.add('os_' + platformUtilsService.getDeviceString());
htmlEl.classList.add('locale_' + i18nService.translationLocale);
const theme = await platformUtilsService.getEffectiveTheme();
htmlEl.classList.add('theme_' + theme);
platformUtilsService.onDefaultSystemThemeChange(async sysTheme => {
const bwTheme = await storageService.get<ThemeType>(ConstantsService.themeKey);
const bwTheme = await stateService.getTheme();
if (bwTheme == null || bwTheme === ThemeType.System) {
htmlEl.classList.remove('theme_' + ThemeType.Light, 'theme_' + ThemeType.Dark);
htmlEl.classList.add('theme_' + sysTheme);
}
});
stateService.save(ConstantsService.disableFaviconKey,
await storageService.get<boolean>(ConstantsService.disableFaviconKey));
let installAction = null;
const installedVersion = await storageService.get<string>(ConstantsService.installedVersionKey);
const installedVersion = await stateService.getInstalledVersion();
const currentVersion = await platformUtilsService.getApplicationVersion();
if (installedVersion == null) {
installAction = 'install';
@@ -87,7 +85,7 @@ export function initFactory(window: Window, environmentService: EnvironmentServi
}
if (installAction != null) {
await storageService.save(ConstantsService.installedVersionKey, currentVersion);
await stateService.setInstalledVersion(currentVersion);
}
const containerService = new ContainerService(cryptoService);
@@ -109,7 +107,6 @@ export function initFactory(window: Window, environmentService: EnvironmentServi
EnvironmentServiceAbstraction,
SyncServiceAbstraction,
VaultTimeoutServiceAbstraction,
StorageServiceAbstraction,
I18nServiceAbstraction,
EventServiceAbstraction,
AuthServiceAbstraction,
@@ -124,12 +121,12 @@ export function initFactory(window: Window, environmentService: EnvironmentServi
{
provide: PlatformUtilsServiceAbstraction,
useFactory: (i18nService: I18nServiceAbstraction, messagingService: MessagingServiceAbstraction,
storageService: StorageServiceAbstraction) => new ElectronPlatformUtilsService(i18nService,
messagingService, true, storageService),
stateService: StateServiceAbstraction) => new ElectronPlatformUtilsService(i18nService,
messagingService, true, stateService),
deps: [
I18nServiceAbstraction,
MessagingServiceAbstraction,
StorageServiceAbstraction,
StateServiceAbstraction,
],
},
{
@@ -148,25 +145,34 @@ export function initFactory(window: Window, environmentService: EnvironmentServi
provide: CryptoServiceAbstraction,
useClass: ElectronCryptoService,
deps: [
StorageServiceAbstraction,
'SECURE_STORAGE',
CryptoFunctionServiceAbstraction,
PlatformUtilsServiceAbstraction,
LogServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: SystemServiceAbstraction,
useClass: SystemService,
deps: [
StorageServiceAbstraction,
VaultTimeoutServiceAbstraction,
MessagingServiceAbstraction,
PlatformUtilsServiceAbstraction,
StateServiceAbstraction,
],
},
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
NativeMessagingService,
SearchBarService,
{
provide: LoginGuardService,
useClass: LoginGuardService,
deps: [
StateServiceAbstraction,
PlatformUtilsServiceAbstraction,
I18nServiceAbstraction,
],
},
],
})
export class ServicesModule {

View File

@@ -16,15 +16,14 @@ import { FolderService } from 'jslib-common/abstractions/folder.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { AddEditComponent as BaseAddEditComponent } from 'jslib-angular/components/add-edit.component';
const BroadcasterSubscriptionId = 'AddEditComponent';
@Component({
@@ -37,13 +36,14 @@ export class AddEditComponent extends BaseAddEditComponent implements OnChanges,
constructor(cipherService: CipherService, folderService: FolderService,
i18nService: I18nService, platformUtilsService: PlatformUtilsService,
auditService: AuditService, stateService: StateService,
userService: UserService, collectionService: CollectionService,
messagingService: MessagingService, eventService: EventService,
policyService: PolicyService, passwordRepromptService: PasswordRepromptService,
private broadcasterService: BroadcasterService, private ngZone: NgZone, logService: LogService) {
collectionService: CollectionService, messagingService: MessagingService,
eventService: EventService, policyService: PolicyService,
passwordRepromptService: PasswordRepromptService, private broadcasterService: BroadcasterService,
private ngZone: NgZone, logService: LogService,
organizationService: OrganizationService) {
super(cipherService, folderService, i18nService, platformUtilsService, auditService, stateService,
userService, collectionService, messagingService, eventService, policyService, passwordRepromptService,
logService);
collectionService, messagingService, eventService, policyService, logService,
passwordRepromptService, organizationService);
}
async ngOnInit() {

View File

@@ -6,7 +6,7 @@ import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { AttachmentsComponent as BaseAttachmentsComponent } from 'jslib-angular/components/attachments.component';
@@ -16,10 +16,10 @@ import { AttachmentsComponent as BaseAttachmentsComponent } from 'jslib-angular/
})
export class AttachmentsComponent extends BaseAttachmentsComponent {
constructor(cipherService: CipherService, i18nService: I18nService,
cryptoService: CryptoService, userService: UserService,
platformUtilsService: PlatformUtilsService, apiService: ApiService,
logService: LogService) {
super(cipherService, i18nService, cryptoService, userService, platformUtilsService,
apiService, window, logService);
cryptoService: CryptoService, platformUtilsService: PlatformUtilsService,
apiService: ApiService, logService: LogService,
stateService: StateService) {
super(cipherService, i18nService, cryptoService, platformUtilsService,
apiService, window, logService, stateService);
}
}

View File

@@ -1,10 +1,3 @@
<div class="header header-search">
<div class="search">
<input type="search" placeholder="{{searchPlaceholder || ('searchVault' | i18n)}}" id="search"
[(ngModel)]="searchText" (input)="search(200)" autocomplete="off" appAutofocus>
<i class="fa fa-search" aria-hidden="true"></i>
</div>
</div>
<div class="content">
<cdk-virtual-scroll-viewport itemSize="42" minBufferPx="400" maxBufferPx="600" *ngIf="ciphers.length">
<div class="list">

View File

@@ -1,14 +1,27 @@
import { Component } from '@angular/core';
import { CiphersComponent as BaseCiphersComponent } from 'jslib-angular/components/ciphers.component';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { CipherView } from 'jslib-common/models/view/cipherView';
import { SearchBarService } from '../layout/search/search-bar.service';
@Component({
selector: 'app-vault-ciphers',
templateUrl: 'ciphers.component.html',
})
export class CiphersComponent extends BaseCiphersComponent {
constructor(searchService: SearchService, searchBarService: SearchBarService) {
super(searchService);
searchBarService.searchText.subscribe(searchText => {
this.searchText = searchText;
this.search(200);
});
}
trackByFn(index: number, c: CipherView) {
return c.id;
}

View File

@@ -2,8 +2,7 @@ import { Component } from '@angular/core';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { FolderService } from 'jslib-common/abstractions/folder.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { GroupingsComponent as BaseGroupingsComponent } from 'jslib-angular/components/groupings.component';
@@ -13,7 +12,7 @@ import { GroupingsComponent as BaseGroupingsComponent } from 'jslib-angular/comp
})
export class GroupingsComponent extends BaseGroupingsComponent {
constructor(collectionService: CollectionService, folderService: FolderService,
storageService: StorageService, userService: UserService) {
super(collectionService, folderService, storageService, userService);
stateService: StateService) {
super(collectionService, folderService, stateService);
}
}

View File

@@ -4,8 +4,8 @@ import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { ShareComponent as BaseShareComponent } from 'jslib-angular/components/share.component';
@@ -15,9 +15,9 @@ import { ShareComponent as BaseShareComponent } from 'jslib-angular/components/s
})
export class ShareComponent extends BaseShareComponent {
constructor(cipherService: CipherService, i18nService: I18nService,
collectionService: CollectionService, userService: UserService,
platformUtilsService: PlatformUtilsService, logService: LogService) {
super(collectionService, platformUtilsService, i18nService, userService, cipherService,
logService);
collectionService: CollectionService, platformUtilsService: PlatformUtilsService,
logService: LogService, organizationService: OrganizationService) {
super(collectionService, platformUtilsService, i18nService, cipherService,
logService, organizationService);
}
}

View File

@@ -14,6 +14,7 @@ import {
import { first } from 'rxjs/operators';
import { SearchBarService } from '../layout/search/search-bar.service';
import { AddEditComponent } from './add-edit.component';
import { AttachmentsComponent } from './attachments.component';
import { CiphersComponent } from './ciphers.component';
@@ -35,15 +36,16 @@ import { FolderView } from 'jslib-common/models/view/folderView';
import { ModalRef } from 'jslib-angular/components/modal/modal.ref';
import { ModalService } from 'jslib-angular/services/modal.service';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { EventService } from 'jslib-common/abstractions/event.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { TotpService } from 'jslib-common/abstractions/totp.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { invokeMenu, RendererMenuItem } from 'jslib-electron/utils';
@@ -86,11 +88,11 @@ export class VaultComponent implements OnInit, OnDestroy {
private ngZone: NgZone, private syncService: SyncService,
private messagingService: MessagingService,
private platformUtilsService: PlatformUtilsService, private eventService: EventService,
private totpService: TotpService, private userService: UserService,
private passwordRepromptService: PasswordRepromptService) { }
private totpService: TotpService, private passwordRepromptService: PasswordRepromptService,
private stateService: StateService, private searchBarService: SearchBarService) { }
async ngOnInit() {
this.userHasPremiumAccess = await this.userService.canAccessPremium();
this.userHasPremiumAccess = await this.stateService.getCanAccessPremium();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
let detectChanges = true;
@@ -166,9 +168,13 @@ export class VaultComponent implements OnInit, OnDestroy {
await this.load();
}
document.body.classList.remove('layout_frontend');
this.searchBarService.setEnabled(true);
this.searchBarService.setPlaceholderText(this.i18nService.t('searchVault'));
}
ngOnDestroy() {
this.searchBarService.setEnabled(false);
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
document.body.classList.add('layout_frontend');
}
@@ -494,14 +500,14 @@ export class VaultComponent implements OnInit, OnDestroy {
}
async clearGroupingFilters() {
this.ciphersComponent.searchPlaceholder = this.i18nService.t('searchVault');
this.searchBarService.setPlaceholderText(this.i18nService.t('searchVault'));
await this.ciphersComponent.reload();
this.clearFilters();
this.go();
}
async filterFavorites() {
this.ciphersComponent.searchPlaceholder = this.i18nService.t('searchFavorites');
this.searchBarService.setPlaceholderText(this.i18nService.t('searchFavorites'));
await this.ciphersComponent.reload(c => c.favorite);
this.clearFilters();
this.favorites = true;
@@ -509,7 +515,7 @@ export class VaultComponent implements OnInit, OnDestroy {
}
async filterDeleted() {
this.ciphersComponent.searchPlaceholder = this.i18nService.t('searchTrash');
this.searchBarService.setPlaceholderText(this.i18nService.t('searchTrash'));
this.ciphersComponent.deleted = true;
await this.ciphersComponent.reload(null, true);
this.clearFilters();
@@ -518,7 +524,7 @@ export class VaultComponent implements OnInit, OnDestroy {
}
async filterCipherType(type: CipherType) {
this.ciphersComponent.searchPlaceholder = this.i18nService.t('searchType');
this.searchBarService.setPlaceholderText(this.i18nService.t('searchType'));
await this.ciphersComponent.reload(c => c.type === type);
this.clearFilters();
this.type = type;
@@ -527,7 +533,7 @@ export class VaultComponent implements OnInit, OnDestroy {
async filterFolder(folderId: string) {
folderId = folderId === 'none' ? null : folderId;
this.ciphersComponent.searchPlaceholder = this.i18nService.t('searchFolder');
this.searchBarService.setPlaceholderText(this.i18nService.t('searchFolder'));
await this.ciphersComponent.reload(c => c.folderId === folderId);
this.clearFilters();
this.folderId = folderId == null ? 'none' : folderId;
@@ -535,7 +541,7 @@ export class VaultComponent implements OnInit, OnDestroy {
}
async filterCollection(collectionId: string) {
this.ciphersComponent.searchPlaceholder = this.i18nService.t('searchCollection');
this.searchBarService.setPlaceholderText(this.i18nService.t('searchCollection'));
await this.ciphersComponent.reload(c => c.collectionIds != null &&
c.collectionIds.indexOf(collectionId) > -1);
this.clearFilters();

View File

@@ -18,9 +18,9 @@ import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { TokenService } from 'jslib-common/abstractions/token.service';
import { TotpService } from 'jslib-common/abstractions/totp.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { ViewComponent as BaseViewComponent } from 'jslib-angular/components/view.component';
@@ -40,12 +40,12 @@ export class ViewComponent extends BaseViewComponent implements OnChanges {
cryptoService: CryptoService, platformUtilsService: PlatformUtilsService,
auditService: AuditService, broadcasterService: BroadcasterService,
ngZone: NgZone, changeDetectorRef: ChangeDetectorRef,
userService: UserService, eventService: EventService, apiService: ApiService,
eventService: EventService, apiService: ApiService,
private messagingService: MessagingService, passwordRepromptService: PasswordRepromptService,
logService: LogService) {
logService: LogService, stateService: StateService) {
super(cipherService, totpService, tokenService, i18nService, cryptoService, platformUtilsService,
auditService, window, broadcasterService, ngZone, changeDetectorRef, userService, eventService,
apiService, passwordRepromptService, logService);
auditService, window, broadcasterService, ngZone, changeDetectorRef, eventService,
apiService, passwordRepromptService, logService, stateService);
}
ngOnInit() {
super.ngOnInit();