1
0
mirror of https://github.com/bitwarden/desktop.git synced 2024-06-26 10:36:19 +02:00

Compare commits

...

20 Commits

Author SHA1 Message Date
Joseph Flinn
45d359353b manually disable successful choco upload 2022-02-10 11:00:43 -08:00
Joseph Flinn
730955a101 Switching where we are pulling our snap token from (#1307)
(cherry picked from commit 97d367dab8)
2022-02-10 10:58:10 -08:00
Micaiah Martin
276aee98e4 Update release.yml (#1306)
(cherry picked from commit 0a545c88b2)
2022-02-10 10:53:26 -07:00
github-actions[bot]
2a61a87542 Bump version to 1.31.0 (#1304)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit 0f6ee08dd5)
2022-02-10 08:58:07 -08:00
Addison Beck
50aec04b4b Remove unused import (#1298) 2022-02-09 21:34:31 -05:00
Addison Beck
ab4ecff04a [bug] Remove redundant token clean call (#1303)
* [bug] Remove redundant token clean call

* bump jslib
2022-02-09 17:19:57 -05:00
Addison Beck
828fcc1896 [bug] Resolve several regression issues (#1302)
* [bug] Ensure accounts logging out in the background doesn't impact active account ui

The main issue here: inactive accounts with a logout timeout actually log out the active account" is fixed by pulling in jslib.
These changes are for some asthetic issues I noticed, where inactive accounts logging out still fires a switchAccount event, which causes a loading spinner to appear and a sync that redraws the vault.

* Only load if the account being logged out is the active account:
* Replaced any calls to `stateService.activeAccount.getValue` with references to `this.activeUserId`, since we subscribe to that in the component now.
* Only send a "switchAccount" method if the active user before a clean and after a clean don't match

* [bug] Ensure default vault timeout is set to On Restart

We dont override the StateMigrationService instance that is injected in desktop, so it is not aware of desktop defaults.
This results in fresh accounts having a "Never" timeout action insteads of "On Restart"

* Use the correct StateMigrationService instance

* update jslib
2022-02-09 12:43:36 -05:00
Joseph Flinn
89b167a9a1 Fixing safari ref logic (#1299)
(cherry picked from commit bd2ed43498)
2022-02-08 07:42:37 -08:00
Addison Beck
25b00a1500 [chore] Update jslib (#1296) 2022-02-07 12:11:50 -05:00
Thomas Rittson
5d9164bce3 Update jslib to fix password history 2022-02-04 15:31:29 +10:00
Addison Beck
5bd1eeb3b4 Update jslib (#1293)
* Update jslib

* [style] Ran prettier
2022-02-03 14:40:17 -05:00
Daniel James Smith
bf3c8cef24
fix announcement of security header (#1292)
(cherry picked from commit 94b561382d)
2022-02-03 17:59:41 +01:00
Daniel James Smith
58709857db
[AccountSwitching]Make account switcher accessible (#1289)
* Make account switcher keyboard accessible

* ScreenReader: Announce submenu and expansion

* ScreenReader: Announc switch account button with account info

* Fix tab focus on dropdown

* Fix esc not changing state

* Fix linting issues

Co-authored-by: Hinton <oscar@oscarhinton.com>
(cherry picked from commit 2b58861296)
2022-02-03 17:47:36 +01:00
Vincent Salucci
9b7672186f [Icons] FF - requested icon changes (#1291)
* [Icons] Remove FA

* Icon changes // webpack correction
2022-02-03 10:29:20 -06:00
Joseph Flinn
55296b136e Switching the way we are pulling secrets for the EV SSL cert (#1285)
(cherry picked from commit 9151fc0164)
2022-02-02 14:37:23 -08:00
Addison Beck
f44c009837 [bug] Remove scroll from login screen (#1283) 2022-02-02 10:32:34 -05:00
Addison Beck
442c8828f5 [bug] Attempt to resolve windows portable build issues (#1280) 2022-02-02 10:30:01 -05:00
Daniel James Smith
3e6989ea91
Fix icons on the settings headers (#1279)
(cherry picked from commit 270411dff7)
2022-02-02 15:52:06 +01:00
Vincent Salucci
4e08ccb4dd [bug] Add options string (#1281) 2022-02-01 23:07:08 -06:00
Addison Beck
79aeb102cb [bug] Inactive accounts with power-based timeout settings are not timing out (#1278)
* [bug] Move bulk of system lock checks into app.component

* [review] Extract shared system timeout logic

* [review] Correct an improper number

* [review] Opt for a more locally scoped system timeout helper than a dedicated enum
2022-02-01 11:33:01 -05:00
22 changed files with 302 additions and 224 deletions

View File

@ -55,9 +55,9 @@ jobs:
run: |
SAFARI_REF=master
if [ "$GITHUB_REF" = "hotfix" ]; then
if [[ "$GITHUB_REF" == "refs/heads/hotfix" ]]; then
SAFARI_REF=hotfix
elif [ "$GITHUB_REF" = "rc" ]; then
elif [[ "$GITHUB_REF" == "refs/heads/rc" ]]; then
SAFARI_REF=rc
fi
@ -221,6 +221,22 @@ jobs:
npm --version
choco --version
- name: Login to Azure
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
with:
keyvault: "bitwarden-prod-kv"
secrets: "code-signing-vault-url,
code-signing-client-id,
code-signing-tenant-id,
code-signing-client-secret,
code-signing-cert-name"
- name: Install Node dependencies
run: npm ci
@ -230,11 +246,11 @@ jobs:
- name: Build & Sign (dev)
env:
ELECTRON_BUILDER_SIGN: 1
SIGNING_VAULT_URL: ${{ secrets.SIGNING_VAULT_URL }}
SIGNING_CLIENT_ID: ${{ secrets.SIGNING_CLIENT_ID }}
SIGNING_TENANT_ID: ${{ secrets.SIGNING_TENANT_ID }}
SIGNING_CLIENT_SECRET: ${{ secrets.SIGNING_CLIENT_SECRET }}
SIGNING_CERT_NAME: ${{ secrets.SIGNING_CERT_NAME }}
SIGNING_VAULT_URL: ${{ steps.retrieve-secrets.outputs.code-signing-vault-url }}
SIGNING_CLIENT_ID: ${{ steps.retrieve-secrets.outputs.code-signing-client-id }}
SIGNING_TENANT_ID: ${{ steps.retrieve-secrets.outputs.code-signing-tenant-id }}
SIGNING_CLIENT_SECRET: ${{ steps.retrieve-secrets.outputs.code-signing-client-secret }}
SIGNING_CERT_NAME: ${{ steps.retrieve-secrets.outputs.code-signing-cert-name }}
run: |
npm run build
npm run pack:win

View File

@ -68,6 +68,8 @@ jobs:
branch: ${{ steps.branch.outputs.branch-name }}
- name: Rename .pkg to .pkg.archive
env:
PKG_VERSION: ${{ steps.retrieve-version.outputs.package_version }}
run: mv Bitwarden-${{ env.PKG_VERSION }}-universal.pkg Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive
- name: Create release
@ -116,10 +118,22 @@ jobs:
- name: Checkout Repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Login to Azure
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
with:
keyvault: "bitwarden-prod-kv"
secrets: "snapcraft-store-token"
- name: Install Snap
uses: samuelmeuli/action-snapcraft@10d7d0a84d9d86098b19f872257df314b0bd8e2d # v1.2.0
with:
snapcraft_token: ${{ secrets.SNAP_TOKEN }}
snapcraft_token: ${{ steps.retrieve-secrets.outputs.snapcraft-store-token }}
- name: Setup
run: mkdir dist
@ -143,6 +157,7 @@ jobs:
choco:
name: Deploy Choco
if: false
runs-on: windows-2019
needs: setup
env:

2
jslib

@ -1 +1 @@
Subproject commit 92a65b7b368a8dbf55350657674c90169b04c30b
Subproject commit 995ad076cca1795ec78f87b81ed3cd2e727703d1

16
package-lock.json generated
View File

@ -43,7 +43,6 @@
"electron-notarize": "^1.1.1",
"electron-rebuild": "^3.2.5",
"electron-reload": "^1.5.0",
"font-awesome": "4.7.0",
"html-loader": "^3.0.1",
"html-webpack-plugin": "^5.5.0",
"husky": "^7.0.4",
@ -4382,15 +4381,6 @@
"node": ">=8"
}
},
"node_modules/font-awesome": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
"integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=",
"dev": true,
"engines": {
"node": ">=0.10.3"
}
},
"node_modules/forcefocus": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/forcefocus/-/forcefocus-1.1.0.tgz",
@ -13406,12 +13396,6 @@
"path-exists": "^4.0.0"
}
},
"font-awesome": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
"integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=",
"dev": true
},
"forcefocus": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/forcefocus/-/forcefocus-1.1.0.tgz",

View File

@ -265,7 +265,6 @@
"electron-notarize": "^1.1.1",
"electron-rebuild": "^3.2.5",
"electron-reload": "^1.5.0",
"font-awesome": "4.7.0",
"html-loader": "^3.0.1",
"html-webpack-plugin": "^5.5.0",
"husky": "^7.0.4",

View File

@ -1,96 +1,98 @@
<div class="login-header">
<a
href="#"
appStopClick
(click)="settings()"
class="environment-urls-settings-icon"
attr.aria-label="{{ 'settings' | i18n }}"
<div id="login-page">
<div class="login-header">
<a
href="#"
appStopClick
(click)="settings()"
class="environment-urls-settings-icon"
attr.aria-label="{{ 'settings' | i18n }}"
>
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
{{ "settings" | i18n }}
</a>
</div>
<form
id="login-page"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
attr.aria-hidden="{{ showingModal }}"
>
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
{{ "settings" | i18n }}
</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" />
<p class="lead">{{ "loginOrCreateNewAccount" | i18n }}</p>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="email"
type="text"
name="Email"
[(ngModel)]="email"
required
appInputVerbatim="false"
/>
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<div id="content" class="content">
<img class="logo-image" alt="Bitwarden" />
<p class="lead">{{ "loginOrCreateNewAccount" | i18n }}</p>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
id="email"
type="text"
name="Email"
[(ngModel)]="email"
required
appInputVerbatim
appInputVerbatim="false"
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</a>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
<div class="box-content-row" [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
</div>
<div class="box-content-row" [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
<div class="buttons with-rows">
<div class="buttons-row">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading"
><i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ "logIn" | i18n }}</b
>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/register" class="btn block">
<i class="bwi bwi-pencil-square" aria-hidden="true"></i> {{ "createAccount" | i18n }}
</a>
</div>
<div class="buttons-row">
<a (click)="launchSsoBrowser('desktop', 'bitwarden://sso-callback')" class="btn block">
<i class="bwi bwi-bank" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | i18n }}
</a>
</div>
</div>
</div>
<div class="buttons with-rows">
<div class="buttons-row">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading"
><i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ "logIn" | i18n }}</b
>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/register" class="btn block">
<i class="bwi bwi-pencil-square" aria-hidden="true"></i> {{ "createAccount" | i18n }}
</a>
</div>
<div class="buttons-row">
<a (click)="launchSsoBrowser('desktop', 'bitwarden://sso-callback')" class="btn block">
<i class="bwi bwi-bank" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | i18n }}
</a>
<div class="sub-options">
<a routerLink="/hint">{{ "getMasterPasswordHint" | i18n }}</a>
</div>
</div>
<div class="sub-options">
<a routerLink="/hint">{{ "getMasterPasswordHint" | i18n }}</a>
</div>
</div>
</form>
</form>
</div>
<ng-template #environment></ng-template>

View File

@ -16,12 +16,12 @@
{{ "security" | i18n }}
<i
*ngIf="!showSecurity"
class="fa fa-chevron-down fa-sm icon"
class="bwi bwi-angle-down bwi-sm icon"
aria-hidden="true"
></i>
<i
*ngIf="showSecurity"
class="fa fa-chevron-up fa-sm icon"
class="bwi bwi-chevron-up bwi-sm icon"
aria-hidden="true"
></i>
</button>
@ -121,12 +121,12 @@
{{ "accountPreferences" | i18n }}
<i
*ngIf="!showAccountPreferences"
class="fa fa-chevron-down fa-sm icon"
class="bwi bwi-angle-down bwi-sm icon"
aria-hidden="true"
></i>
<i
*ngIf="showAccountPreferences"
class="fa fa-chevron-up fa-sm icon"
class="bwi bwi-chevron-up bwi-sm icon"
aria-hidden="true"
></i>
</button>
@ -192,12 +192,12 @@
{{ "appPreferences" | i18n }}
<i
*ngIf="!showAppPreferences"
class="fa fa-chevron-down fa-sm icon"
class="bwi bwi-angle-down bwi-sm icon"
aria-hidden="true"
></i>
<i
*ngIf="showAppPreferences"
class="fa fa-chevron-up fa-sm icon"
class="bwi bwi-chevron-up bwi-sm icon"
aria-hidden="true"
></i>
</button>

View File

@ -10,7 +10,6 @@
name="VaultTimeout"
formControlName="vaultTimeout"
class="form-control"
appAutofocus
>
<option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{ o.name }}</option>
</select>

View File

@ -53,6 +53,12 @@ const BroadcasterSubscriptionId = "AppComponent";
const IdleTimeout = 60000 * 10; // 10 minutes
const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
const systemTimeoutOptions = {
onLock: -2,
onSuspend: -3,
onIdle: -4,
};
@Component({
selector: "app-root",
styles: [],
@ -90,6 +96,7 @@ export class AppComponent implements OnInit {
private modal: ModalRef = null;
private idleTimer: number = null;
private isIdle = false;
private activeUserId: string = null;
constructor(
private broadcasterService: BroadcasterService,
@ -122,21 +129,18 @@ export class AppComponent implements OnInit {
) {}
ngOnInit() {
let activeUserId: string = null;
this.stateService.activeAccount.subscribe((userId) => {
activeUserId = userId;
this.activeUserId = userId;
});
this.ngZone.runOutsideAngular(() => {
setTimeout(async () => {
await this.updateAppMenu();
}, 1000);
if (activeUserId != null) {
window.ontouchstart = () => this.recordActivity(activeUserId);
window.onmousedown = () => this.recordActivity(activeUserId);
window.onscroll = () => this.recordActivity(activeUserId);
window.onkeypress = () => this.recordActivity(activeUserId);
}
window.ontouchstart = () => this.recordActivity();
window.onmousedown = () => this.recordActivity();
window.onscroll = () => this.recordActivity();
window.onkeypress = () => this.recordActivity();
});
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
@ -161,7 +165,7 @@ export class AppComponent implements OnInit {
this.router.navigate(["login"]);
break;
case "logout":
this.loading = true;
this.loading = message.userId == null || message.userId === this.activeUserId;
await this.logOut(!!message.expired, message.userId);
this.loading = false;
break;
@ -339,6 +343,12 @@ export class AppComponent implements OnInit {
this.router.navigate(["vault"]);
}
break;
case "systemSuspended":
await this.checkForSystemTimeout(systemTimeoutOptions.onSuspend);
case "systemLocked":
await this.checkForSystemTimeout(systemTimeoutOptions.onLock);
case "systemIdle":
await this.checkForSystemTimeout(systemTimeoutOptions.onIdle);
}
});
});
@ -441,28 +451,24 @@ export class AppComponent implements OnInit {
}
private async logOut(expired: boolean, userId?: string) {
if (!userId) {
userId = await this.stateService.getUserId();
}
const userBeingLoggedOut = await this.stateService.getUserId({ userId: userId });
await Promise.all([
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(userId),
this.vaultTimeoutService.clear(userId),
this.policyService.clear(userId),
this.eventService.uploadEvents(userBeingLoggedOut),
this.syncService.setLastSync(new Date(0), userBeingLoggedOut),
this.cryptoService.clearKeys(userBeingLoggedOut),
this.settingsService.clear(userBeingLoggedOut),
this.cipherService.clear(userBeingLoggedOut),
this.folderService.clear(userBeingLoggedOut),
this.collectionService.clear(userBeingLoggedOut),
this.passwordGenerationService.clear(userBeingLoggedOut),
this.vaultTimeoutService.clear(userBeingLoggedOut),
this.policyService.clear(userBeingLoggedOut),
this.keyConnectorService.clear(),
]);
await this.stateService.setBiometricLocked(true, { userId: userId });
await this.stateService.setBiometricLocked(true, { userId: userBeingLoggedOut });
if (userId == null || userId === (await this.stateService.getUserId())) {
if (userBeingLoggedOut === this.activeUserId) {
this.searchService.clearIndex();
this.authService.logOut(async () => {
if (expired) {
@ -475,25 +481,30 @@ export class AppComponent implements OnInit {
});
}
await this.stateService.clean({ userId: userId });
const preLogoutActiveUserId = this.activeUserId;
await this.stateService.clean({ userId: userBeingLoggedOut });
if (this.stateService.activeAccount.getValue() == null) {
if (this.activeUserId == null) {
this.router.navigate(["login"]);
} else {
} else if (preLogoutActiveUserId !== this.activeUserId) {
this.messagingService.send("switchAccount");
}
await this.updateAppMenu();
}
private async recordActivity(userId: string) {
private async recordActivity() {
if (this.activeUserId == null) {
return;
}
const now = new Date().getTime();
if (this.lastActivity != null && now - this.lastActivity < 250) {
return;
}
this.lastActivity = now;
await this.stateService.setLastActive(now, { userId: userId });
await this.stateService.setLastActive(now, { userId: this.activeUserId });
// Idle states
if (this.isIdle) {
@ -583,4 +594,24 @@ export class AppComponent implements OnInit {
}
await this.systemService.startProcessReload();
}
private async checkForSystemTimeout(timeout: number): Promise<void> {
for (const userId in this.stateService.accounts.getValue()) {
if (userId == null) {
continue;
}
const options = await this.getVaultTimeoutOptions(userId);
if (options[0] === timeout) {
options[1] === "logOut"
? this.logOut(false, userId)
: await this.vaultTimeoutService.lock(true, userId);
}
}
}
private async getVaultTimeoutOptions(userId: string): Promise<[number, string]> {
const timeout = await this.stateService.getVaultTimeout({ userId: userId });
const action = await this.stateService.getVaultTimeoutAction({ userId: userId });
return [timeout, action];
}
}

View File

@ -1,9 +1,12 @@
<a
<button
class="account-switcher"
(click)="toggle()"
cdkOverlayOrigin
#trigger="cdkOverlayOrigin"
[hidden]="!showSwitcher"
aria-haspopup="menu"
aria-expanded="isOpen"
aria-controls="cdk-overlay-container"
>
<ng-container *ngIf="activeAccountEmail != null; else noActiveAccount">
<app-avatar
@ -13,6 +16,7 @@
[fontSize]="14"
[dynamic]="true"
*ngIf="activeAccountEmail != null"
aria-hidden="true"
></app-avatar>
<span>{{ activeAccountEmail }}</span>
</ng-container>
@ -24,26 +28,33 @@
aria-hidden="true"
[ngClass]="{ 'bwi-angle-down': !isOpen, 'bwi-chevron-up': isOpen }"
></i>
</a>
</button>
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="trigger"
[cdkConnectedOverlayHasBackdrop]="true"
[cdkConnectedOverlayBackdropClass]="'cdk-overlay-transparent-backdrop'"
(backdropClick)="toggle()"
(backdropClick)="close()"
(detach)="close()"
[cdkConnectedOverlayOpen]="showSwitcher && isOpen"
[cdkConnectedOverlayPositions]="overlayPostition"
cdkConnectedOverlayMinWidth="250px"
>
<div class="account-switcher-dropdown" [@transformPanel]="'open'">
<div
class="account-switcher-dropdown"
[@transformPanel]="'open'"
cdkTrapFocus
cdkTrapFocusAutoCapture
>
<div class="accounts" *ngIf="numberOfAccounts > 0">
<a
<button
*ngFor="let a of accounts | keyvalue"
class="account"
[ngClass]="{ active: a.value.profile.authenticationStatus == 'active' }"
href="#"
(mousedown)="switch(a.key)"
(click)="switch(a.key)"
appA11yTitle="{{ 'loggedInAsOn' | i18n: a.value.profile.email:a.value.serverUrl }}"
attr.aria-label="{{ 'switchAccount' | i18n }}"
>
<app-avatar
[data]="a.value.profile.email"
@ -52,30 +63,33 @@
[fontSize]="14"
[dynamic]="true"
*ngIf="a.value.profile.email != null"
aria-hidden="true"
></app-avatar>
<div class="accountInfo">
<span class="email">{{ a.value.profile.email }}</span>
<span class="server" *ngIf="a.value.serverUrl != 'bitwarden.com'">{{
<span class="email" aria-hidden="true">{{ a.value.profile.email }}</span>
<span class="server" aria-hidden="true" *ngIf="a.value.serverUrl != 'bitwarden.com'">{{
a.value.serverUrl
}}</span>
<span class="status">{{ a.value.profile.authenticationStatus }}</span>
<span class="status" aria-hidden="true">{{ a.value.profile.authenticationStatus }}</span>
</div>
<i
class="bwi bwi-unlock bwi-2x text-muted"
aria-hidden="true"
*ngIf="a.value.profile.authenticationStatus == 'unlocked'"
></i>
<i
class="bwi bwi-lock bwi-2x text-muted"
aria-hidden="true"
*ngIf="a.value.profile.authenticationStatus == 'locked'"
></i>
</a>
</button>
</div>
<ng-container *ngIf="activeAccountEmail != null">
<div class="border" *ngIf="numberOfAccounts > 0"></div>
<ng-container *ngIf="numberOfAccounts < 4">
<a class="add" routerLink="/login" (click)="addAccount()">
<button class="add" routerLink="/login" (click)="addAccount()">
<i class="bwi bwi-plus" aria-hidden="true"></i> {{ "addAccount" | i18n }}
</a>
</button>
</ng-container>
<ng-container *ngIf="numberOfAccounts == 4">
<span class="accountLimitReached">{{ "accountSwitcherLimitReached" | i18n }} </span>

View File

@ -107,14 +107,18 @@ export class AccountSwitcherComponent implements OnInit {
this.isOpen = !this.isOpen;
}
close() {
this.isOpen = false;
}
async switch(userId: string) {
this.toggle();
this.close();
this.messagingService.send("switchAccount", { userId: userId });
}
async addAccount() {
this.toggle();
this.close();
await this.stateService.setActiveUser(null);
}

View File

@ -50,6 +50,7 @@ import { Account } from "../models/account";
import { GlobalState } from "jslib-common/models/domain/globalState";
import { StateFactory } from "jslib-common/factories/stateFactory";
import { StateMigrationService } from "jslib-common/services/stateMigration.service";
export function initFactory(
window: Window,
@ -201,6 +202,19 @@ export function initFactory(
StateMigrationServiceAbstraction,
],
},
{
provide: StateMigrationServiceAbstraction,
useFactory: (
storageService: StorageServiceAbstraction,
secureStorageService: StorageServiceAbstraction
) =>
new StateMigrationService(
storageService,
secureStorageService,
new StateFactory(GlobalState, Account)
),
deps: [StorageServiceAbstraction, "SECURE_STORAGE"],
},
],
})
export class ServicesModule {}

View File

@ -426,7 +426,7 @@
appA11yTitle="{{ 'toggleOptions' | i18n }}"
(click)="toggleUriOptions(u)"
>
<i class="bwi bwi-lg bwi-cog-f" aria-hidden="true"></i>
<i class="bwi bwi-lg bwi-cog" aria-hidden="true"></i>
</a>
</div>
</div>

View File

@ -9,7 +9,7 @@
</li>
<li [ngClass]="{ active: selectedFavorites }">
<a href="#" appStopClick appBlurClick (click)="selectFavorites()">
<i class="bwi bwi-fw bwi-star-f" aria-hidden="true"></i>&nbsp;{{ "favorites" | i18n }}
<i class="bwi bwi-fw bwi-star" aria-hidden="true"></i>&nbsp;{{ "favorites" | i18n }}
</a>
</li>
<li [ngClass]="{ active: selectedTrash }">
@ -64,8 +64,8 @@
title="{{ 'toggleCollapse' | i18n }}"
aria-hidden="true"
[ngClass]="{
'bwi-caret-right': isCollapsed(f.node),
'bwi-caret-down': !isCollapsed(f.node)
'bwi-angle-right': isCollapsed(f.node),
'bwi-angle-down': !isCollapsed(f.node)
}"
(click)="collapse(f.node)"
appStopProp
@ -114,8 +114,8 @@
title="{{ 'toggleCollapse' | i18n }}"
aria-hidden="true"
[ngClass]="{
'bwi-caret-right': isCollapsed(c.node),
'bwi-caret-down': !isCollapsed(c.node)
'bwi-angle-right': isCollapsed(c.node),
'bwi-angle-down': !isCollapsed(c.node)
}"
(click)="collapse(c.node)"
appStopProp

View File

@ -1810,5 +1810,8 @@
},
"switchAccount": {
"message": "Switch Account"
},
"options": {
"message": "Options"
}
}

View File

@ -17,30 +17,20 @@ export class PowerMonitorMain {
// ref: https://github.com/electron/electron/issues/13767
if (!isSnapStore()) {
// System sleep
powerMonitor.on("suspend", async () => {
const options = await this.getVaultTimeoutOptions();
if (options[0] === -3) {
options[1] === "logOut"
? this.main.messagingService.send("logout", { expired: false })
: this.main.messagingService.send("lockVault");
}
powerMonitor.on("suspend", () => {
this.main.messagingService.send("systemSuspended");
});
}
if (process.platform !== "linux") {
// System locked
powerMonitor.on("lock-screen", async () => {
const options = await this.getVaultTimeoutOptions();
if (options[0] === -2) {
options[1] === "logOut"
? this.main.messagingService.send("logout", { expired: false })
: this.main.messagingService.send("lockVault");
}
powerMonitor.on("lock-screen", () => {
this.main.messagingService.send("systemLocked");
});
}
// System idle
global.setInterval(async () => {
global.setInterval(() => {
const idleSeconds: number = powerMonitor.getSystemIdleTime();
const idle = idleSeconds >= IdleLockSeconds;
if (idle) {
@ -48,21 +38,10 @@ export class PowerMonitorMain {
return;
}
const options = await this.getVaultTimeoutOptions();
if (options[0] === -4) {
options[1] === "logOut"
? this.main.messagingService.send("logout", { expired: false })
: this.main.messagingService.send("lockVault");
}
this.main.messagingService.send("systemIdle");
}
this.idle = idle;
}, IdleCheckInterval);
}
private async getVaultTimeoutOptions(): Promise<[number, string]> {
const timeout = await this.main.stateService.getVaultTimeout();
const action = await this.main.stateService.getVaultTimeoutAction();
return [timeout, action];
}
}

View File

@ -2,7 +2,7 @@
"name": "@bitwarden/desktop",
"productName": "Bitwarden",
"description": "A secure and free password manager for all of your devices.",
"version": "1.30.1",
"version": "1.31.0",
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
"homepage": "https://bitwarden.com",
"license": "GPL-3.0",

View File

@ -80,6 +80,10 @@
height: 100%;
user-select: none;
border: none;
background: transparent;
width: auto;
@include themify($themes) {
color: themed("accountSwitcherTextColor");
}
@ -114,9 +118,11 @@
0 1px 5px 0 rgba(0, 0, 0, 0.2);
border-radius: $border-radius;
a {
button {
border: none;
background: transparent;
width: 100%;
padding: 5px 10px;
display: block;
@include themify($themes) {
color: themed("textColor");
@ -141,6 +147,7 @@
.accountInfo {
display: grid;
text-align: left;
.email {
font-size: $font-size-base;
@ -173,6 +180,7 @@
.add {
margin: 4px 0;
text-align: left;
}
.accountLimitReached {

View File

@ -139,28 +139,6 @@
}
}
.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;
@ -237,3 +215,31 @@
margin-bottom: 20px;
}
}
#login-page {
flex-direction: column;
.login-header {
align-self: flex-start;
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");
}
}
}
}
}

View File

@ -1,6 +1,5 @@
import { Injectable } from "@angular/core";
import { ipcRenderer } from "electron";
import { firstValueFrom } from "rxjs";
import Swal from "sweetalert2";
import { CryptoService } from "jslib-common/abstractions/crypto.service";

View File

@ -4,7 +4,12 @@ import { Account } from "../models/account";
import { StateService as StateServiceAbstraction } from "jslib-common/abstractions/state.service";
export class StateService extends BaseStateService<Account> implements StateServiceAbstraction {
import { GlobalState } from "jslib-common/models/domain/globalState";
export class StateService
extends BaseStateService<GlobalState, Account>
implements StateServiceAbstraction
{
async addAccount(account: Account) {
// Apply desktop overides to default account values
account = new Account(account);

View File

@ -19,7 +19,7 @@ const common = {
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
exclude: /.*(fontawesome-webfont)\.svg/,
exclude: /.*(bwi-font)\.svg/,
generator: {
filename: "images/[name][ext]",
},