1
0
mirror of https://github.com/bitwarden/desktop synced 2026-02-26 09:23:13 +00:00

[feature] Implement account switching

This commit is contained in:
Hinton
2021-09-14 17:07:57 +02:00
committed by addison
parent f2f3c97867
commit 26659e528b
42 changed files with 602 additions and 328 deletions

View File

@@ -9,7 +9,6 @@ import {
} from '@angular/router';
import { ipcRenderer } from 'electron';
import { ActiveAccountService } from 'jslib-common/abstractions/activeAccount.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
@@ -24,8 +23,6 @@ import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
import { LockComponent as BaseLockComponent } from 'jslib-angular/components/lock.component';
import { StorageKey } from 'jslib-common/enums/storageKey';
const BroadcasterSubscriptionId = 'LockComponent';
@Component({
@@ -41,15 +38,14 @@ export class LockComponent extends BaseLockComponent implements OnDestroy {
environmentService: EnvironmentService, stateService: StateService,
apiService: ApiService, private route: ActivatedRoute,
private broadcasterService: BroadcasterService, private ngZone: NgZone,
logService: LogService, activeAccount: ActiveAccountService) {
logService: LogService) {
super(router, i18nService, platformUtilsService, messagingService, cryptoService,
vaultTimeoutService, environmentService, stateService, apiService, logService,
activeAccount);
vaultTimeoutService, environmentService, stateService, apiService, logService);
}
async ngOnInit() {
await super.ngOnInit();
const autoPromptBiometric = !await this.activeAccount.getInformation<boolean>(StorageKey.NoAutoPromptBiometrics);
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

@@ -10,7 +10,6 @@ import { Router } from '@angular/router';
import { EnvironmentComponent } from './environment.component';
import { ActiveAccountService } from 'jslib-common/abstractions/activeAccount.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
@@ -20,7 +19,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 { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
@@ -28,8 +26,6 @@ import { ModalService } from 'jslib-angular/services/modal.service';
import { LoginComponent as BaseLoginComponent } from 'jslib-angular/components/login.component';
import { StorageKey } from 'jslib-common/enums/storageKey';
const BroadcasterSubscriptionId = 'LoginComponent';
@Component({
@@ -47,12 +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, private ngZone: NgZone,
private messagingService: MessagingService, logService: LogService,
activeAccount: ActiveAccountService) {
cryptoFunctionService: CryptoFunctionService, private broadcasterService: BroadcasterService,
private ngZone: NgZone, private messagingService: MessagingService,
logService: LogService) {
super(authService, router, platformUtilsService, i18nService, stateService, environmentService,
passwordGenerationService, cryptoFunctionService, storageService, logService, activeAccount);
passwordGenerationService, cryptoFunctionService, logService);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};

View File

@@ -1,10 +1,10 @@
import { Component } from '@angular/core';
import { ActiveAccountService } from 'jslib-common/abstractions/activeAccount.service';
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 { StateService } from 'jslib-common/abstractions/state.service';
import { PremiumComponent as BasePremiumComponent } from 'jslib-angular/components/premium.component';
@@ -15,7 +15,7 @@ import { PremiumComponent as BasePremiumComponent } from 'jslib-angular/componen
export class PremiumComponent extends BasePremiumComponent {
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
apiService: ApiService, logService: LogService,
activeAccount: ActiveAccountService) {
super(i18nService, platformUtilsService, apiService, logService, activeAccount);
stateService: StateService) {
super(i18nService, platformUtilsService, apiService, logService, stateService);
}
}

View File

@@ -9,7 +9,6 @@ import {
Router,
} from '@angular/router';
import { ActiveAccountService } from 'jslib-common/abstractions/activeAccount.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
@@ -17,10 +16,9 @@ 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 { StorageKey } from 'jslib-common/enums/storageKey';
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
const BroadcasterSubscriptionId = 'SetPasswordComponent';
@@ -40,10 +38,10 @@ export class SetPasswordComponent extends BaseSetPasswordComponent implements On
policyService: PolicyService, router: Router,
syncService: SyncService, route: ActivatedRoute,
private broadcasterService: BroadcasterService, private ngZone: NgZone,
activeAccount: ActiveAccountService) {
stateService: StateService) {
super(i18nService, cryptoService, messagingService, passwordGenerationService,
platformUtilsService, policyService, router, apiService, syncService, route,
activeAccount);
stateService);
}
get masterPasswordScoreWidth() {

View File

@@ -8,13 +8,11 @@ import { debounceTime } from 'rxjs/operators';
import { DeviceType } from 'jslib-common/enums/deviceType';
import { ThemeType } from 'jslib-common/enums/themeType';
import { ActiveAccountService } from 'jslib-common/abstractions/activeAccount.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
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 { ModalService } from 'jslib-angular/services/modal.service';
@@ -24,7 +22,7 @@ import { SetPinComponent } from '../components/set-pin.component';
import { Utils } from 'jslib-common/misc/utils';
import { isWindowsStore } from 'jslib-electron/utils';
import { StorageKey } from 'jslib-common/enums/storageKey';
import { StorageLocation } from 'jslib-common/enums/storageLocation';
@Component({
selector: 'app-settings',
@@ -73,8 +71,7 @@ export class SettingsComponent implements OnInit {
constructor(private i18nService: I18nService, private platformUtilsService: PlatformUtilsService,
private vaultTimeoutService: VaultTimeoutService, private stateService: StateService,
private messagingService: MessagingService, private cryptoService: CryptoService,
private modalService: ModalService, private activeAccount: ActiveAccountService,
private storageService: StorageService) {
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.activeAccount.getInformation<number>(StorageKey.VaultTimeout) ?? 1);
this.vaultTimeoutAction = await this.activeAccount.getInformation<string>(StorageKey.VaultTimeoutAction) ?? 'lock';
this.vaultTimeout.setValue(await this.stateService.getVaultTimeout() ?? 1);
this.vaultTimeoutAction = await this.stateService.getVaultTimeoutAction() ?? 'lock';
const pinSet = await this.vaultTimeoutService.isPinLockSet();
this.pin = pinSet[0] || pinSet[1];
this.disableFavicons = await this.storageService.get(StorageKey.DisableFavicon) ?? false;
this.enableBrowserIntegration = await this.activeAccount.getInformation<boolean>(
StorageKey.EnableBrowserIntegration);
this.enableBrowserIntegrationFingerprint = await this.activeAccount.getInformation<boolean>(StorageKey.EnableBrowserIntegrationFingerprint);
this.enableMinToTray = await this.activeAccount.getInformation<boolean>(StorageKey.EnableMinimizeToTrayKey);
this.enableCloseToTray = await this.activeAccount.getInformation<boolean>(StorageKey.EnableCloseToTrayKey);
this.enableTray = await this.activeAccount.getInformation<boolean>(StorageKey.EnableTrayKey);
this.startToTray = await this.activeAccount.getInformation<boolean>(StorageKey.EnableStartToTrayKey);
this.locale = await this.activeAccount.getInformation<string>(StorageKey.Locale);
this.theme = await this.activeAccount.getInformation<string>(StorageKey.Theme);
this.clearClipboard = await this.activeAccount.getInformation<number>(StorageKey.ClearClipboard);
this.minimizeOnCopyToClipboard = await this.activeAccount.getInformation<boolean>(
StorageKey.MinimizeOnCopyToClipboardKey);
this.disableFavicons = await this.stateService.getDisableFavicon() ?? false;
this.enableBrowserIntegration = await this.stateService.getEnableBrowserIntegration() ?? false;
this.enableBrowserIntegrationFingerprint = await this.stateService.getEnableBrowserIntegrationFingerprint() ?? false;
this.enableMinToTray = await this.stateService.getEnableMinimizeToTray() ?? false;
this.enableCloseToTray = await this.stateService.getEnableCloseToTray() ?? false;
this.enableTray = await this.stateService.getEnableTray() ?? false;
this.startToTray = await this.stateService.getEnableStartToTray() ?? false;
this.locale = await this.stateService.getLocale() ?? 'en';
this.theme = await this.stateService.getTheme() ?? null;
this.clearClipboard = await this.stateService.getClearClipboard() ?? 0;
this.minimizeOnCopyToClipboard = await this.stateService.getMinimizeOnCopyToClipboard() ?? false;
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
this.biometric = await this.vaultTimeoutService.isBiometricLockSet();
this.biometricText = await this.activeAccount.getInformation<string>(StorageKey.BiometricText);
this.noAutoPromptBiometrics = await this.activeAccount.getInformation<boolean>(StorageKey.NoAutoPromptBiometrics);
this.noAutoPromptBiometricsText = await this.activeAccount.getInformation<string>(StorageKey.NoAutoPromptBiometricsText);
this.alwaysShowDock = await this.activeAccount.getInformation<boolean>(StorageKey.AlwaysShowDock);
this.biometric = await this.vaultTimeoutService.isBiometricLockSet() ?? false;
this.biometricText = await this.stateService.getBiometricText();
this.noAutoPromptBiometrics = await this.stateService.getNoAutoPromptBiometrics() ?? false;
this.noAutoPromptBiometricsText = await this.stateService.getNoAutoPromptBiometricsText();
this.alwaysShowDock = await this.stateService.getAlwaysShowDock() ?? false;
this.showAlwaysShowDock = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
this.openAtLogin = await this.activeAccount.getInformation<boolean>(StorageKey.OpenAtLogin);
this.openAtLogin = await this.stateService.getOpenAtLogin() ?? false;
}
async saveVaultTimeoutOptions() {
@@ -233,13 +228,13 @@ export class SettingsComponent implements OnInit {
return;
}
if (this.biometric) {
await this.activeAccount.saveInformation(StorageKey.BiometricUnlock, true);
await this.stateService.setBiometricUnlock(true);
} else {
await this.activeAccount.removeInformation(StorageKey.BiometricUnlock);
await this.activeAccount.removeInformation(StorageKey.NoAutoPromptBiometrics);
await this.stateService.setBiometricUnlock(null);
await this.stateService.setNoAutoPromptBiometrics(null);
this.noAutoPromptBiometrics = false;
}
this.vaultTimeoutService.biometricLocked = false;
this.stateService.setBiometricLocked(false);
await this.cryptoService.toggleKey();
}
@@ -249,29 +244,29 @@ export class SettingsComponent implements OnInit {
}
if (this.noAutoPromptBiometrics) {
await this.activeAccount.saveInformation(StorageKey.NoAutoPromptBiometrics, true);
await this.stateService.setNoAutoPromptBiometrics(true);
} else {
await this.activeAccount.removeInformation(StorageKey.NoAutoPromptBiometrics);
await this.stateService.setNoAutoPromptBiometrics(null);
}
}
async saveFavicons() {
await this.activeAccount.saveInformation(StorageKey.DisableFavicon, this.disableFavicons);
await this.stateService.save(StorageKey.DisableFavicon, 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.activeAccount.saveInformation(StorageKey.EnableMinimizeToTrayKey, this.enableMinToTray);
await this.stateService.setEnableMinimizeToTray(this.enableMinToTray);
}
async saveCloseToTray() {
if (this.requireEnableTray) {
this.enableTray = true;
await this.activeAccount.saveInformation(StorageKey.EnableTrayKey, this.enableTray);
await this.stateService.setEnableTray(this.enableTray);
}
await this.activeAccount.saveInformation(StorageKey.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.activeAccount.saveInformation(StorageKey.EnableStartToTrayKey, this.startToTray);
await this.stateService.setEnableStartToTray(this.startToTray);
this.enableCloseToTray = false;
await this.activeAccount.saveInformation(StorageKey.EnableCloseToTrayKey, this.enableCloseToTray);
await this.stateService.setEnableCloseToTray(this.enableCloseToTray);
} else {
this.enableTray = true;
}
@@ -292,43 +287,42 @@ export class SettingsComponent implements OnInit {
return;
}
await this.activeAccount.saveInformation(StorageKey.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.activeAccount.saveInformation(StorageKey.EnableTrayKey, this.enableTray);
await this.stateService.setEnableTray(this.enableTray);
}
await this.activeAccount.saveInformation(StorageKey.EnableStartToTrayKey, this.startToTray);
await this.stateService.setEnableStartToTray(this.startToTray);
}
async saveLocale() {
await this.activeAccount.saveInformation(StorageKey.Locale, this.locale);
await this.stateService.setLocale(this.locale);
}
async saveTheme() {
await this.activeAccount.saveInformation(StorageKey.Theme, this.theme);
await this.storageService.save(`global.${StorageKey.Theme}`, this.theme);
await this.stateService.setTheme(this.theme);
window.setTimeout(() => window.location.reload(), 200);
}
async saveMinOnCopyToClipboard() {
await this.activeAccount.saveInformation(StorageKey.MinimizeOnCopyToClipboardKey, this.minimizeOnCopyToClipboard);
await this.stateService.setMinimizeOnCopyToClipboard(this.minimizeOnCopyToClipboard);
}
async saveClearClipboard() {
await this.activeAccount.saveInformation(StorageKey.ClearClipboard, this.clearClipboard);
await this.stateService.setClearClipboard(this.clearClipboard);
}
async saveAlwaysShowDock() {
await this.activeAccount.saveInformation(StorageKey.AlwaysShowDock, this.alwaysShowDock);
await this.stateService.setAlwaysShowDock(this.alwaysShowDock);
}
async saveOpenAtLogin() {
this.activeAccount.saveInformation(StorageKey.OpenAtLogin, this.openAtLogin);
this.stateService.setOpenAtLogin(this.openAtLogin);
this.messagingService.send(this.openAtLogin ? 'addOpenAtLogin' : 'removeOpenAtLogin');
}
@@ -351,7 +345,7 @@ export class SettingsComponent implements OnInit {
return;
}
await this.activeAccount.saveInformation(StorageKey.EnableBrowserIntegration, this.enableBrowserIntegration);
await this.stateService.setEnableBrowserIntegration(this.enableBrowserIntegration);
this.messagingService.send(this.enableBrowserIntegration ? 'enableBrowserIntegration' : 'disableBrowserIntegration');
if (!this.enableBrowserIntegration) {
@@ -361,6 +355,6 @@ export class SettingsComponent implements OnInit {
}
async saveBrowserIntegrationFingerprint() {
await this.activeAccount.saveInformation(StorageKey.EnableBrowserIntegrationFingerprint, this.enableBrowserIntegrationFingerprint);
await this.stateService.setEnableBrowserIntegrationFingerprint(this.enableBrowserIntegrationFingerprint);
}
}

View File

@@ -5,7 +5,6 @@ import {
Router,
} from '@angular/router';
import { ActiveAccountService } from 'jslib-common/abstractions/activeAccount.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
@@ -15,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';
@@ -27,14 +25,12 @@ 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,
activeAccount: ActiveAccountService) {
super(authService, router, i18nService, route, storageService, stateService, platformUtilsService,
apiService, cryptoFunctionService, environmentService, passwordGenerationService, logService,
activeAccount);
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

@@ -13,7 +13,6 @@ import { TwoFactorOptionsComponent } from './two-factor-options.component';
import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType';
import { ActiveAccountService } from 'jslib-common/abstractions/activeAccount.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
@@ -21,15 +20,12 @@ 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';
import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib-angular/components/two-factor.component';
import { StorageKey } from 'jslib-common/enums/storageKey';
@Component({
selector: 'app-two-factor',
templateUrl: 'two-factor.component.html',
@@ -43,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

@@ -1,6 +1,5 @@
import { Component } from '@angular/core';
import { ActiveAccountService } from 'jslib-common/abstractions/activeAccount.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
@@ -9,6 +8,7 @@ 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 { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from 'jslib-angular/components/update-temp-password.component';
@@ -58,8 +58,8 @@ export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent
passwordGenerationService: PasswordGenerationService, policyService: PolicyService,
cryptoService: CryptoService, messagingService: MessagingService,
apiService: ApiService, syncService: SyncService,
logService: LogService, activeAccount: ActiveAccountService) {
logService: LogService, stateService: StateService) {
super(i18nService, platformUtilsService, passwordGenerationService, policyService, cryptoService,
messagingService, apiService, activeAccount, syncService, logService);
messagingService, apiService, stateService, syncService, logService);
}
}

View File

@@ -6,7 +6,6 @@ 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 { HintComponent } from './accounts/hint.component';
import { LockComponent } from './accounts/lock.component';
@@ -31,8 +30,6 @@ const routes: Routes = [
{
path: 'login',
component: LoginComponent,
canActivate: [UnauthGuardService],
},
{ path: '2fa', component: TwoFactorComponent },
{ path: 'register', component: RegisterComponent },

View File

@@ -23,8 +23,6 @@ import { PasswordGeneratorHistoryComponent } from './vault/password-generator-hi
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
import { AccountsManagementService } from 'jslib-common/abstractions/accountsManagement.service';
import { ActiveAccountService } from 'jslib-common/abstractions/activeAccount.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
@@ -47,14 +45,15 @@ import { TokenService } from 'jslib-common/abstractions/token.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { CipherType } from 'jslib-common/enums/cipherType';
import { StorageKey } from 'jslib-common/enums/storageKey';
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';
const BroadcasterSubscriptionId = 'AppComponent';
const IdleTimeout = 60000 * 10; // 10 minutes
const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
@@ -70,7 +69,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;
@@ -94,20 +94,19 @@ export class AppComponent implements OnInit {
private idleTimer: number = null;
private isIdle = false;
constructor(private broadcasterService: BroadcasterService, private activeAccount: ActiveAccountService,
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 toasterService: ToasterService, private i18nService: I18nService,
private sanitizer: DomSanitizer, private ngZone: NgZone,
private vaultTimeoutService: VaultTimeoutService, private cryptoService: CryptoService,
private logService: LogService, private messagingService: MessagingService,
private collectionService: CollectionService, private searchService: SearchService,
private notificationsService: NotificationsService, private platformUtilsService: PlatformUtilsService,
private systemService: SystemService, private stateService: StateService,
private eventService: EventService, private policyService: PolicyService,
private modalService: ModalService, private accountsManagementService: AccountsManagementService) { }
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 toasterService: ToasterService,
private i18nService: I18nService, private sanitizer: DomSanitizer,
private ngZone: NgZone, private vaultTimeoutService: VaultTimeoutService,
private cryptoService: CryptoService, private logService: LogService,
private messagingService: MessagingService, private collectionService: CollectionService,
private searchService: SearchService, private notificationsService: NotificationsService,
private platformUtilsService: PlatformUtilsService, private systemService: SystemService,
private stateService: StateService, private eventService: EventService,
private policyService: PolicyService, private modalService: ModalService) { }
ngOnInit() {
this.ngZone.runOutsideAngular(() => {
@@ -154,7 +153,7 @@ export class AppComponent implements OnInit {
if (this.modal != null) {
this.modal.close();
}
this.stateService.purge();
await this.stateService.purge();
this.router.navigate(['lock']);
this.notificationsService.updateConnection();
this.updateAppMenu();
@@ -176,7 +175,7 @@ export class AppComponent implements OnInit {
break;
case 'showFingerprintPhrase':
const fingerprint = await this.cryptoService.getFingerprint(
this.activeAccount.userId);
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'),
@@ -328,14 +327,14 @@ export class AppComponent implements OnInit {
private async updateAppMenu() {
this.messagingService.send('updateAppMenu', {
isAuthenticated: this.activeAccount.isAuthenticated,
isAuthenticated: await this.stateService.getIsAuthenticated(),
isLocked: await this.vaultTimeoutService.isLocked(),
});
}
private async logOut(expired: boolean) {
await this.eventService.uploadEvents();
const userId = this.activeAccount.userId;
const userId = await this.stateService.getUserId();
await Promise.all([
this.eventService.clearEvents(),
@@ -348,12 +347,11 @@ export class AppComponent implements OnInit {
this.collectionService.clear(userId),
this.passwordGenerationService.clear(),
this.vaultTimeoutService.clear(),
this.stateService.purge(),
this.policyService.clear(userId),
this.accountsManagementService.remove(userId),
await this.stateService.purge(),
]);
this.vaultTimeoutService.biometricLocked = true;
await this.stateService.setBiometricLocked(true);
this.searchService.clearIndex();
this.authService.logOut(async () => {
if (expired) {
@@ -371,7 +369,7 @@ export class AppComponent implements OnInit {
}
this.lastActivity = now;
this.activeAccount.saveInformation(StorageKey.LastActive, now);
await this.stateService.setLastActive(now);
// Idle states
if (this.isIdle) {

View File

@@ -1,18 +1,17 @@
import 'zone.js/dist/zone';
import { ToasterModule } from 'angular2-toaster';
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 { ToasterModule } from 'angular2-toaster';
import 'zone.js/dist/zone';
import { AppRoutingModule } from './app-routing.module';
import { ServicesModule } from './services.module';
import { AppComponent } from './app.component';
@@ -30,6 +29,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';
@@ -69,7 +69,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';
@@ -177,6 +180,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
ToasterModule.forRoot(),
ScrollingModule,
A11yModule,
OverlayModule,
],
declarations: [
A11yTitleDirective,
@@ -230,6 +234,10 @@ registerLocaleData(localeZhTw, 'zh-TW');
VaultTimeoutInputComponent,
AddEditCustomFieldsComponent,
ViewCustomFieldsComponent,
HeaderComponent,
AccountSwitcherComponent,
AvatarComponent,
SearchComponent,
],
providers: [DatePipe],
bootstrap: [AppComponent],

View File

@@ -0,0 +1,22 @@
<a class="account-switcher" (click)="toggle()" cdkOverlayOrigin #trigger="cdkOverlayOrigin">
<app-avatar [data]="activeAccountEmail" size="25" [circle]="true" [fontSize]="14" *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.status == 'active'}"
href="#" appStopClick (click)="switch(a.value.userId)">
<span class="email">{{a.value.email}}</span>
<span class="server">{{a.value.serverUrl}}</span>
<span class="status">{{a.value.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,52 @@
import {
animate,
state,
style,
transition,
trigger,
} from '@angular/animations';
import { Component, OnInit } from '@angular/core';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
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;
activeAccountEmail: string;
get accounts(): Record<string, Account> {
return this.stateService.accounts.getValue();
}
constructor(private stateService: StateService, private syncService: SyncService) {}
async ngOnInit(): Promise<void> {
this.activeAccountEmail = await this.stateService.getEmail();
}
toggle() {
this.isOpen = !this.isOpen;
}
async switch(userId: string) {
await this.stateService.setActiveUser(userId);
await this.syncService.fullSync(true);
}
}

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,11 @@
import { Component } from '@angular/core';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
@Component({
selector: 'app-header',
templateUrl: 'header.component.html',
})
export class HeaderComponent {
constructor(private i18nService: I18nService) {}
}

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

@@ -2,7 +2,6 @@ import { DatePipe } from '@angular/common';
import { Component } from '@angular/core';
import { ActiveAccountService } from 'jslib-common/abstractions/activeAccount.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
@@ -10,6 +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 { 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, activeAccount: ActiveAccountService,
sendService: SendService, stateService: StateService,
messagingService: MessagingService, policyService: PolicyService,
logService: LogService) {
super(i18nService, platformUtilsService, environmentService,
datePipe, sendService, messagingService, policyService,
logService, activeAccount);
logService, stateService);
}
async refresh() {

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

@@ -21,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 {
@@ -45,13 +46,20 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
private broadcasterService: BroadcasterService, ngZone: NgZone,
searchService: SearchService, policyService: PolicyService,
logService: LogService) {
private searchBarService: SearchBarService, logService: LogService) {
super(sendService, i18nService, platformUtilsService,
environmentService, ngZone, searchService,
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 () => {
@@ -67,6 +75,7 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
this.searchBarService.setEnabled(false);
}
addSend() {

View File

@@ -15,6 +15,7 @@ import { ElectronRendererStorageService } from 'jslib-electron/services/electron
import { I18nService } from '../services/i18n.service';
import { NativeMessagingService } from '../services/nativeMessaging.service';
import { PasswordRepromptService } from '../services/passwordReprompt.service';
import { SearchBarService } from './layout/search/search-bar.service';
import { AuthGuardService } from 'jslib-angular/services/auth-guard.service';
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
@@ -23,8 +24,6 @@ import { ModalService } from 'jslib-angular/services/modal.service';
import { UnauthGuardService } from 'jslib-angular/services/unauth-guard.service';
import { ValidationService } from 'jslib-angular/services/validation.service';
import { AccountsManagementService } from 'jslib-common/services/accountsManagement.service';
import { ActiveAccountService } from 'jslib-common/services/activeAccount.service';
import { ApiService } from 'jslib-common/services/api.service';
import { AppIdService } from 'jslib-common/services/appId.service';
import { AuditService } from 'jslib-common/services/audit.service';
@@ -46,7 +45,6 @@ import { SearchService } from 'jslib-common/services/search.service';
import { SendService } from 'jslib-common/services/send.service';
import { SettingsService } from 'jslib-common/services/settings.service';
import { StateService } from 'jslib-common/services/state.service';
import { StoreService } from 'jslib-common/services/store.service';
import { SyncService } from 'jslib-common/services/sync.service';
import { SystemService } from 'jslib-common/services/system.service';
import { TokenService } from 'jslib-common/services/token.service';
@@ -56,8 +54,6 @@ import { WebCryptoFunctionService } from 'jslib-common/services/webCryptoFunctio
import { ElectronCryptoService } from 'jslib-electron/services/electronCrypto.service';
import { AccountsManagementService as AccountsManagementServiceAbstraction } from 'jslib-common/abstractions/accountsManagement.service';
import { ActiveAccountService as ActiveAccountServiceAbstraction } from 'jslib-common/abstractions/activeAccount.service';
import { ApiService as ApiServiceAbstraction } from 'jslib-common/abstractions/api.service';
import { AuditService as AuditServiceAbstraction } from 'jslib-common/abstractions/audit.service';
import { AuthService as AuthServiceAbstraction } from 'jslib-common/abstractions/auth.service';
@@ -92,64 +88,61 @@ import { SystemService as SystemServiceAbstraction } from 'jslib-common/abstract
import { TokenService as TokenServiceAbstraction } from 'jslib-common/abstractions/token.service';
import { TotpService as TotpServiceAbstraction } from 'jslib-common/abstractions/totp.service';
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib-common/abstractions/vaultTimeout.service';
import { StorageKey } from 'jslib-common/enums/storageKey';
import { ThemeType } from 'jslib-common/enums/themeType';
const logService = new ElectronLogService();
const i18nService = new I18nService(window.navigator.language, './locales');
const stateService = new StateService();
const broadcasterService = new BroadcasterService();
const messagingService = new ElectronRendererMessagingService(broadcasterService);
const storageService: StorageServiceAbstraction = new ElectronRendererStorageService();
const secureStorageService: StorageServiceAbstraction = new ElectronRendererSecureStorageService();
const storeService = new StoreService(storageService, secureStorageService);
const accountsManagementService: AccountsManagementServiceAbstraction = new AccountsManagementService(storageService, secureStorageService);
const activeAccount: ActiveAccountServiceAbstraction = new ActiveAccountService(accountsManagementService, storeService);
const platformUtilsService = new ElectronPlatformUtilsService(i18nService, messagingService, true, storageService, activeAccount);
const stateService = new StateService(storageService, secureStorageService,
logService);
const platformUtilsService = new ElectronPlatformUtilsService(i18nService, messagingService, true, stateService);
const cryptoFunctionService: CryptoFunctionServiceAbstraction = new WebCryptoFunctionService(window,
platformUtilsService);
const cryptoService = new ElectronCryptoService(cryptoFunctionService, platformUtilsService,
logService, activeAccount);
const tokenService = new TokenService(activeAccount);
logService, stateService);
const tokenService = new TokenService(stateService);
const appIdService = new AppIdService(storageService);
const environmentService = new EnvironmentService(activeAccount);
const environmentService = new EnvironmentService(stateService);
const apiService = new ApiService(tokenService, platformUtilsService, environmentService,
async (expired: boolean) => messagingService.send('logout', { expired: expired }));
const settingsService = new SettingsService(activeAccount);
const settingsService = new SettingsService(stateService);
export let searchService: SearchService = null;
const fileUploadService = new FileUploadService(logService, apiService);
const cipherService = new CipherService(cryptoService, settingsService, apiService,
fileUploadService, i18nService, () => searchService, logService, activeAccount);
fileUploadService, i18nService, () => searchService, logService, stateService);
const folderService = new FolderService(cryptoService, apiService, i18nService,
cipherService, activeAccount);
const collectionService = new CollectionService(cryptoService, i18nService, activeAccount);
cipherService, stateService);
const collectionService = new CollectionService(cryptoService, i18nService, stateService);
searchService = new SearchService(cipherService, logService, i18nService);
const sendService = new SendService(cryptoService, apiService, fileUploadService, i18nService,
cryptoFunctionService, activeAccount);
const organizationService = new OrganizationService(activeAccount);
const providerService: ProviderServiceAbstraction = new ProviderService(activeAccount);
const policyService = new PolicyService(activeAccount, organizationService, apiService);
cryptoFunctionService, stateService);
const organizationService = new OrganizationService(stateService);
const providerService: ProviderServiceAbstraction = new ProviderService(stateService);
const policyService = new PolicyService(stateService, organizationService, apiService);
const vaultTimeoutService = new VaultTimeoutService(cipherService, folderService, collectionService,
cryptoService, platformUtilsService, messagingService, searchService, tokenService, policyService, activeAccount, null,
cryptoService, platformUtilsService, messagingService, searchService, tokenService, policyService, stateService, null,
async () => messagingService.send('logout', { expired: false }));
const syncService = new SyncService(apiService, settingsService,
folderService, cipherService, cryptoService, collectionService, messagingService, policyService, sendService, logService,
async (expired: boolean) => messagingService.send('logout', { expired: expired }), activeAccount, organizationService, providerService);
const passwordGenerationService = new PasswordGenerationService(cryptoService, policyService, activeAccount);
const totpService = new TotpService(cryptoFunctionService, logService, activeAccount);
async (expired: boolean) => messagingService.send('logout', { expired: expired }), stateService, organizationService, providerService);
const passwordGenerationService = new PasswordGenerationService(cryptoService, policyService, stateService);
const totpService = new TotpService(cryptoFunctionService, logService, stateService);
const containerService = new ContainerService(cryptoService);
const authService = new AuthService(cryptoService, apiService, tokenService, appIdService, i18nService,
platformUtilsService, messagingService, vaultTimeoutService, logService, activeAccount, accountsManagementService);
platformUtilsService, messagingService, vaultTimeoutService, logService, cryptoFunctionService, stateService);
const exportService = new ExportService(folderService, cipherService, apiService, cryptoService);
const auditService = new AuditService(cryptoFunctionService, apiService);
const notificationsService = new NotificationsService(syncService, appIdService,
apiService, vaultTimeoutService, environmentService, async () => messagingService.send('logout', { expired: true }), logService, activeAccount);
const eventService = new EventService(apiService, cipherService, activeAccount, logService, organizationService);
const systemService = new SystemService(vaultTimeoutService, messagingService, platformUtilsService, null,
activeAccount);
apiService, vaultTimeoutService, environmentService, async () => messagingService.send('logout', { expired: true }), logService, stateService);
const eventService = new EventService(apiService, cipherService, stateService, logService, organizationService);
const systemService = new SystemService(messagingService, platformUtilsService,
null, stateService);
const nativeMessagingService = new NativeMessagingService(cryptoFunctionService, cryptoService, platformUtilsService,
logService, i18nService, messagingService, vaultTimeoutService, activeAccount);
logService, i18nService, messagingService, vaultTimeoutService, stateService);
containerService.attachToGlobal(window);
@@ -158,7 +151,7 @@ export function initFactory(): Function {
await environmentService.setUrlsFromStorage();
syncService.fullSync(true);
vaultTimeoutService.init(true);
const locale = await activeAccount.getInformation<string>(StorageKey.Locale);
const locale = await stateService.getLocale();
await i18nService.init(locale);
eventService.init(true);
authService.init();
@@ -169,18 +162,15 @@ export function initFactory(): Function {
const theme = await platformUtilsService.getEffectiveTheme();
htmlEl.classList.add('theme_' + theme);
platformUtilsService.onDefaultSystemThemeChange(async sysTheme => {
const bwTheme = await activeAccount.getInformation<ThemeType>(StorageKey.Theme);
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(StorageKey.DisableFavicon,
await storageService.get<boolean>(StorageKey.DisableFavicon));
let installAction = null;
const installedVersion = await activeAccount.getInformation<string>(StorageKey.InstalledVersion);
const installedVersion = await stateService.getInstalledVersion();
const currentVersion = await platformUtilsService.getApplicationVersion();
if (installedVersion == null) {
installAction = 'install';
@@ -189,7 +179,7 @@ export function initFactory(): Function {
}
if (installAction != null) {
await activeAccount.saveInformation(StorageKey.InstalledVersion, currentVersion);
await stateService.setInstalledVersion(currentVersion);
}
};
}
@@ -205,8 +195,7 @@ export function initFactory(): Function {
UnauthGuardService,
LockGuardService,
ModalService,
{ provide: AccountsManagementServiceAbstraction, useValue: accountsManagementService },
{ provide: ActiveAccountServiceAbstraction, useValue: activeAccount },
SearchBarService,
{ provide: ApiServiceAbstraction, useValue: apiService },
{ provide: AuditServiceAbstraction, useValue: auditService },
{ provide: AuthServiceAbstraction, useValue: authService },

View File

@@ -7,7 +7,6 @@ import {
} from '@angular/core';
import { NgForm } from '@angular/forms';
import { ActiveAccountService } from 'jslib-common/abstractions/activeAccount.service';
import { AuditService } from 'jslib-common/abstractions/audit.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
@@ -40,11 +39,9 @@ export class AddEditComponent extends BaseAddEditComponent implements OnChanges,
collectionService: CollectionService, messagingService: MessagingService,
eventService: EventService, policyService: PolicyService,
private broadcasterService: BroadcasterService, private ngZone: NgZone,
logService: LogService, activeAccountService: ActiveAccountService,
organizationService: OrganizationService) {
logService: LogService, organizationService: OrganizationService) {
super(cipherService, folderService, i18nService, platformUtilsService, auditService, stateService,
collectionService, messagingService, eventService, policyService, logService, activeAccountService,
organizationService);
collectionService, messagingService, eventService, policyService, logService, organizationService);
}
async ngOnInit() {

View File

@@ -1,12 +1,12 @@
import { Component } from '@angular/core';
import { ActiveAccountService } from 'jslib-common/abstractions/activeAccount.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
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 { StateService } from 'jslib-common/abstractions/state.service';
import { AttachmentsComponent as BaseAttachmentsComponent } from 'jslib-angular/components/attachments.component';
@@ -18,8 +18,8 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
constructor(cipherService: CipherService, i18nService: I18nService,
cryptoService: CryptoService, platformUtilsService: PlatformUtilsService,
apiService: ApiService, logService: LogService,
activeAccountService: ActiveAccountService) {
stateService: StateService) {
super(cipherService, i18nService, cryptoService, platformUtilsService,
apiService, window, logService, activeAccountService);
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

@@ -1,8 +1,8 @@
import { Component } from '@angular/core';
import { ActiveAccountService } from 'jslib-common/abstractions/activeAccount.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { FolderService } from 'jslib-common/abstractions/folder.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { GroupingsComponent as BaseGroupingsComponent } from 'jslib-angular/components/groupings.component';
@@ -12,7 +12,7 @@ import { GroupingsComponent as BaseGroupingsComponent } from 'jslib-angular/comp
})
export class GroupingsComponent extends BaseGroupingsComponent {
constructor(collectionService: CollectionService, folderService: FolderService,
activeAccountService: ActiveAccountService) {
super(collectionService, folderService, activeAccountService);
stateService: StateService) {
super(collectionService, folderService, stateService);
}
}

View File

@@ -1,6 +1,5 @@
import { Component } from '@angular/core';
import { ActiveAccountService } from 'jslib-common/abstractions/activeAccount.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
@@ -17,9 +16,8 @@ import { ShareComponent as BaseShareComponent } from 'jslib-angular/components/s
export class ShareComponent extends BaseShareComponent {
constructor(cipherService: CipherService, i18nService: I18nService,
collectionService: CollectionService, platformUtilsService: PlatformUtilsService,
logService: LogService, activeAccountService: ActiveAccountService,
organizationService: OrganizationService) {
logService: LogService, organizationService: OrganizationService) {
super(collectionService, platformUtilsService, i18nService, cipherService,
logService, activeAccountService, organizationService);
logService, organizationService);
}
}

View File

@@ -16,8 +16,7 @@ import { first } from 'rxjs/operators';
import { ToasterService } from 'angular2-toaster';
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
import { SearchBarService } from '../layout/search/search-bar.service';
import { AddEditComponent } from './add-edit.component';
import { AttachmentsComponent } from './attachments.component';
import { CiphersComponent } from './ciphers.component';
@@ -38,16 +37,18 @@ import { FolderView } from 'jslib-common/models/view/folderView';
import { ModalRef } from 'jslib-angular/components/modal/modal.ref';
import { ActiveAccountService } from 'jslib-common/abstractions/activeAccount.service';
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
import { ModalService } from 'jslib-angular/services/modal.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 { ModalService } from 'jslib-angular/services/modal.service';
import { invokeMenu, RendererMenuItem } from 'jslib-electron/utils';
@@ -91,10 +92,10 @@ export class VaultComponent implements OnInit, OnDestroy {
private toasterService: ToasterService, private messagingService: MessagingService,
private platformUtilsService: PlatformUtilsService, private eventService: EventService,
private totpService: TotpService, private passwordRepromptService: PasswordRepromptService,
private activeAccount: ActiveAccountService) { }
private stateService: StateService, private searchBarService: SearchBarService) { }
async ngOnInit() {
this.userHasPremiumAccess = this.activeAccount.canAccessPremium;
this.userHasPremiumAccess = await this.stateService.getCanAccessPremium();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
let detectChanges = true;
@@ -170,9 +171,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');
}
@@ -498,14 +503,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;
@@ -513,7 +518,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();
@@ -522,7 +527,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;
@@ -531,7 +536,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;
@@ -539,7 +544,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

@@ -7,7 +7,6 @@ import {
Output,
} from '@angular/core';
import { ActiveAccountService } from 'jslib-common/abstractions/activeAccount.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuditService } from 'jslib-common/abstractions/audit.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
@@ -18,6 +17,7 @@ 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';
@@ -43,10 +43,10 @@ export class ViewComponent extends BaseViewComponent implements OnChanges {
ngZone: NgZone, changeDetectorRef: ChangeDetectorRef,
eventService: EventService, apiService: ApiService,
private messagingService: MessagingService, passwordRepromptService: PasswordRepromptService,
logService: LogService, activeAccountService: ActiveAccountService) {
logService: LogService, stateService: StateService) {
super(cipherService, totpService, tokenService, i18nService, cryptoService, platformUtilsService,
auditService, window, broadcasterService, ngZone, changeDetectorRef, eventService,
apiService, passwordRepromptService, logService, activeAccountService);
apiService, passwordRepromptService, logService, stateService);
}
ngOnInit() {
super.ngOnInit();

View File

@@ -1736,5 +1736,8 @@
},
"personalVaultExportPolicyInEffect": {
"message": "One or more organization policies prevents you from exporting your personal vault."
},
"addAccount": {
"message": "Add Account"
}
}

View File

@@ -20,20 +20,14 @@ import { UpdaterMain } from 'jslib-electron/updater.main';
import { WindowMain } from 'jslib-electron/window.main';
import { NativeMessagingMain } from './main/nativeMessaging.main';
import { StorageKey } from 'jslib-common/enums/storageKey';
import { AccountsManagementService } from 'jslib-common/services/accountsManagement.service';
import { ActiveAccountService } from 'jslib-common/services/activeAccount.service';
import { StoreService } from 'jslib-common/services/store.service';
import { StateService } from 'jslib-common/services/state.service';
export class Main {
logService: ElectronLogService;
i18nService: I18nService;
storageService: ElectronStorageService;
messagingService: ElectronMainMessagingService;
accountsManagementService: AccountsManagementService;
activeAccount: ActiveAccountService;
storeService: StoreService;
stateService: StateService;
keytarStorageListener: KeytarStorageListener;
windowMain: WindowMain;
@@ -78,16 +72,14 @@ export class Main {
const storageDefaults: any = {};
// Default vault timeout to "on restart", and action to "lock"
storageDefaults[StorageKey.VaultTimeout] = -1;
storageDefaults[StorageKey.VaultTimeoutAction] = 'lock';
storageDefaults['global.vaultTimeout'] = -1;
storageDefaults['global.vaultTimeoutAction'] = 'lock';
this.storageService = new ElectronStorageService(app.getPath('userData'), storageDefaults);
this.accountsManagementService = new AccountsManagementService(this.storageService, null);
this.storeService = new StoreService(this.storageService, null);
this.activeAccount = new ActiveAccountService(this.accountsManagementService, this.storeService);
this.stateService = new StateService(this.storageService, null, this.logService);
this.windowMain = new WindowMain(this.storageService, this.logService, true, undefined, undefined,
this.windowMain = new WindowMain(this.stateService, this.logService, true, undefined, undefined,
arg => this.processDeepLink(arg), win => this.trayMain.setupWindowListeners(win));
this.messagingMain = new MessagingMain(this, this.storageService);
this.messagingMain = new MessagingMain(this, this.stateService);
this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain, 'desktop', () => {
this.menuMain.updateMenuItem.enabled = false;
}, () => {
@@ -97,7 +89,7 @@ export class Main {
}, 'bitwarden');
this.menuMain = new MenuMain(this);
this.powerMonitorMain = new PowerMonitorMain(this);
this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.storageService);
this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.stateService);
this.messagingService = new ElectronMainMessagingService(this.windowMain, message => {
this.messagingMain.onMessage(message);
@@ -109,7 +101,7 @@ export class Main {
this.biometricMain = new BiometricWindowsMain(this.i18nService, this.windowMain, this.storageService);
} else if (process.platform === 'darwin') {
const BiometricDarwinMain = require('jslib-electron/biometric.darwin.main').default;
this.biometricMain = new BiometricDarwinMain(this.i18nService, this.storageService);
this.biometricMain = new BiometricDarwinMain(this.i18nService, this.stateService);
}
this.keytarStorageListener = new KeytarStorageListener('Bitwarden', this.biometricMain);
@@ -120,7 +112,7 @@ export class Main {
bootstrap() {
this.keytarStorageListener.init();
this.windowMain.init().then(async () => {
const locale = await this.activeAccount.getInformation<string>(StorageKey.Locale);
const locale = await this.stateService.getLocale();
await this.i18nService.init(locale != null ? locale : app.getLocale());
this.messagingMain.init();
this.menuMain.init();
@@ -130,7 +122,7 @@ export class Main {
id: 'lockNow',
click: () => this.messagingService.send('lockVault'),
}]);
if (await this.activeAccount.getInformation<boolean>(StorageKey.EnableStartToTrayKey)) {
if (await this.stateService.getEnableStartToTray()) {
this.trayMain.hideToTray();
}
this.powerMonitorMain.init();
@@ -139,7 +131,7 @@ export class Main {
await this.biometricMain.init();
}
if (await this.activeAccount.getInformation<boolean>(StorageKey.EnableBrowserIntegration)) {
if (await this.stateService.getEnableBrowserIntegration()) {
this.nativeMessagingMain.listen();
}

View File

@@ -10,8 +10,6 @@ import {
import { Main } from '../main';
import { StorageKey } from 'jslib-common/enums/storageKey';
import { BaseMenu } from 'jslib-electron/baseMenu';
import { isMacAppStore, isSnapStore, isWindowsStore } from 'jslib-electron/utils';
@@ -504,7 +502,7 @@ export class MenuMain extends BaseMenu {
private async openWebVault() {
let webUrl = 'https://vault.bitwarden.com';
const urlsObj: any = await this.main.activeAccount.getInformation(StorageKey.EnvironmentUrls);
const urlsObj: any = await this.main.stateService.getEnvironmentUrls();
if (urlsObj != null) {
if (urlsObj.base != null) {
webUrl = urlsObj.base;

View File

@@ -4,24 +4,22 @@ import * as path from 'path';
import { Main } from '../main';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { StorageKey } from 'jslib-common/enums/storageKey';
import { StateService } from 'jslib-common/abstractions/state.service';
const SyncInterval = 5 * 60 * 1000; // 5 minutes
export class MessagingMain {
private syncTimeout: NodeJS.Timer;
constructor(private main: Main, private storageService: StorageService) { }
constructor(private main: Main, private stateService: StateService) { }
init() {
this.scheduleNextSync();
if (process.platform === 'linux') {
this.storageService.save(StorageKey.OpenAtLogin, fs.existsSync(this.linuxStartupFile()));
this.stateService.setOpenAtLogin(fs.existsSync(this.linuxStartupFile()));
} else {
const loginSettings = app.getLoginItemSettings();
this.storageService.save(StorageKey.OpenAtLogin, loginSettings.openAtLogin);
this.stateService.setOpenAtLogin(loginSettings.openAtLogin);
}
ipcMain.on('messagingService', async (event: any, message: any) => this.onMessage(message));
}
@@ -36,7 +34,7 @@ export class MessagingMain {
this.updateTrayMenu(message.isAuthenticated, message.isLocked);
break;
case 'minimizeOnCopy':
this.storageService.get<boolean>(StorageKey.MinimizeOnCopyToClipboardKey).then(
this.stateService.getMinimizeOnCopyToClipboard().then(
shouldMinimize => {
if (shouldMinimize && this.main.windowMain.win !== null) {
this.main.windowMain.win.minimize();

View File

@@ -1,7 +1,5 @@
import { powerMonitor } from 'electron';
import { StorageKey } from 'jslib-common/enums/storageKey';
import { isSnapStore } from 'jslib-electron/utils';
import { Main } from '../main';
@@ -60,8 +58,8 @@ export class PowerMonitorMain {
}
private async getVaultTimeoutOptions(): Promise<[number, string]> {
const timeout = await this.main.activeAccount.getInformation<number>(StorageKey.VaultTimeout);
const action = await this.main.activeAccount.getInformation<string>(StorageKey.VaultTimeoutAction);
const timeout = await this.main.stateService.getVaultTimeout();
const action = await this.main.stateService.getVaultTimeoutAction();
return [timeout, action];
}
}

175
src/scss/header.scss Normal file
View File

@@ -0,0 +1,175 @@
.header {
-webkit-app-region: drag;
min-height: 44px;
max-height: 44px;
border-bottom: 1px solid #000000;
display: grid;
grid-template-columns: 25% 1fr 25%;
grid-column-gap: 5px;
justify-items: center;
align-items: center;
@include themify($themes) {
background-color: themed('headerBackgroundColor');
border-bottom-color: themed('headerBorderColor');
}
app-search {
grid-column-start: 2;
width: 100%;
}
app-account-switcher {
justify-self: end;
height: 100%;
}
.search {
padding: 0 7px;
width: 100%;
text-align: left;
position: relative;
.fa {
position: absolute;
top: 7px;
left: 15px;
@include themify($themes) {
color: themed('headerInputPlaceholderColor');
}
}
input {
width: 100%;
margin: 0;
border: none;
padding: 5px 10px 5px 30px;
border-radius: $border-radius;
@include themify($themes) {
background-color: themed('headerInputBackgroundColor');
color: themed('headerInputColor');
}
&:focus {
border-radius: $border-radius;
outline: none;
@include themify($themes) {
background-color: themed('headerInputBackgroundFocusColor');
}
}
&::-webkit-input-placeholder {
@include themify($themes) {
color: themed('headerInputPlaceholderColor');
}
}
}
}
}
.account-switcher {
display: grid;
grid-template-columns: auto 1fr auto;
grid-column-gap: 5px;
align-items: center;
justify-items: center;
padding: 0 10px;
height: 100%;
user-select: none;
@include themify($themes) {
color: themed('headerInputColor'); // TODO: Change to headerTextColor?
}
img {
display: block;
}
span {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&:hover {
@include themify($themes) {
background-color: themed('headerBorderColor');
color: themed('headerInputColor'); // TODO: Change to headerTextColor?
}
}
}
.account-switcher-dropdown {
@include themify($themes) {
background-color: themed('headerBorderColor');
}
margin-right: 5px;
margin-top: 1px; // Fix for border-bottom in header
width: 100%;
box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 3px 1px -2px rgba(0,0,0,0.12), 0 1px 5px 0 rgba(0,0,0,0.2);
border-radius: $border-radius;
a {
padding: 5px 10px;
display: block;
@include themify($themes) {
color: themed('textColor');
}
&:hover {
@include themify($themes) {
background-color: themed('backgroundColorAlt2'); // TODO: Check if this color is acceptable design prototype uses #ececec
}
}
}
.accounts {
padding: 4px 0;
.account {
display: grid;
grid-column-gap: 5px;
grid-template:
[row1-start] "email status" [row1-end]
[row2-start] "server server" [row2-end]
/ 1fr auto;
align-items: baseline;
.server {
font-size: $font-size-small;
}
.email {
font-size: $font-size-large
}
.status {
font-style: italic;
grid-area: status;
}
&.active {
font-weight: 600;
}
}
}
.border {
@include themify($themes) {
background: themed('borderColor');
}
left: 10px;
width: calc(100% - 20px);
height: 1px;
position: relative;
}
.add {
margin: 4px 0;
}
}

View File

@@ -431,3 +431,7 @@ app-root > #loading {
margin-bottom: 15px;
}
}
.rounded-circle {
border-radius:50% !important;
}

View File

@@ -124,6 +124,27 @@
}
}
.login-header {
padding: 1em;
font-size: 1.2em;
.environment-urls-settings-icon {
@include themify($themes) {
color: themed('mutedColor');
}
span {
visibility: hidden;
}
&:hover, &:focus {
text-decoration: none;
@include themify($themes) {
color: themed('primaryColor');
}
}
}
}
#sso-page {
.content {
width: 300px;

View File

@@ -1,4 +1,5 @@
@import "../../jslib/angular/src/scss/webfonts.css";
@import '~@angular/cdk/overlay-prebuilt.css';
@import "../css/webfonts.css";
@import "variables.scss";
@import "base.scss";
@import "grid.scss";
@@ -11,3 +12,4 @@
@import "modal.scss";
@import "plugins.scss";
@import "environment.scss";
@import "header.scss";

View File

@@ -1,5 +1,16 @@
@import "variables.scss";
app-root {
display: flex;
flex-flow: column;
height: 100%;
}
#container {
height: 100%;
min-height: 0;
}
.vault {
height: 100%;
display: flex;
@@ -356,67 +367,6 @@
}
}
.header {
min-height: 44px;
max-height: 44px;
flex: 0 0 auto;
border-bottom: 1px solid #000000;
display: flex;
align-items: center;
@include themify($themes) {
background-color: themed('headerBackgroundColor');
border-bottom-color: themed('headerBorderColor');
}
&.header-search {
.search {
padding: 0 7px;
width: 100%;
text-align: left;
position: relative;
.fa {
position: absolute;
top: 7px;
left: 15px;
@include themify($themes) {
color: themed('headerInputPlaceholderColor');
}
}
input {
width: 100%;
margin: 0;
border: none;
padding: 5px 10px 5px 30px;
border-radius: $border-radius;
@include themify($themes) {
background-color: themed('headerInputBackgroundColor');
color: themed('headerInputColor');
}
&:focus {
border-radius: $border-radius;
outline: none;
@include themify($themes) {
background-color: themed('headerInputBackgroundFocusColor');
}
}
&::-webkit-input-placeholder {
@include themify($themes) {
color: themed('headerInputPlaceholderColor');
}
}
}
}
}
}
.content {
flex: 1 1 auto;
position: relative;

View File

@@ -1,20 +1,20 @@
import { ipcRenderer } from 'electron';
import Swal from 'sweetalert2';
import { ActiveAccountService } from 'jslib-common/abstractions/activeAccount.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.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 { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { Utils } from 'jslib-common/misc/utils';
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
import { StorageKey } from 'jslib-common/enums/storageKey';
import { KeySuffixOptions } from 'jslib-common/enums/keySuffixOptions';
const MessageValidTimeout = 10 * 1000;
const EncryptionAlgorithm = 'sha1';
@@ -25,7 +25,7 @@ export class NativeMessagingService {
constructor(private cryptoFunctionService: CryptoFunctionService, private cryptoService: CryptoService,
private platformUtilService: PlatformUtilsService, private logService: LogService,
private i18nService: I18nService, private messagingService: MessagingService,
private vaultTimeoutService: VaultTimeoutService, private activeAccount: ActiveAccountService) {
private vaultTimeoutService: VaultTimeoutService, private stateService: StateService) {
ipcRenderer.on('nativeMessaging', async (_event: any, message: any) => {
this.messageHandler(message);
});
@@ -40,15 +40,15 @@ export class NativeMessagingService {
const remotePublicKey = Utils.fromB64ToArray(rawMessage.publicKey).buffer;
// Valudate the UserId to ensure we are logged into the same account.
if (rawMessage.userId !== this.activeAccount.userId) {
if (rawMessage.userId !== await this.stateService.getUserId()) {
ipcRenderer.send('nativeMessagingReply', {command: 'wrongUserId', appId: appId});
return;
}
if (await this.activeAccount.getInformation<boolean>(StorageKey.EnableBrowserIntegrationFingerprint)) {
if (await this.stateService.getEnableBrowserIntegrationFingerprint()) {
ipcRenderer.send('nativeMessagingReply', {command: 'verifyFingerprint', appId: appId});
const fingerprint = (await this.cryptoService.getFingerprint(this.activeAccount.userId, remotePublicKey)).join(' ');
const fingerprint = (await this.cryptoService.getFingerprint(await this.stateService.getUserId(), remotePublicKey)).join(' ');
this.messagingService.send('setFocus');
@@ -103,7 +103,7 @@ export class NativeMessagingService {
});
}
const keyB64 = (await this.cryptoService.getKeyFromStorage('biometric')).keyB64;
const keyB64 = (await this.cryptoService.getKeyFromStorage(KeySuffixOptions.Biometric)).keyB64;
if (keyB64 != null) {
this.send({ command: 'biometricUnlock', response: 'unlocked', keyB64: keyB64 }, appId);