Compare commits

...

36 Commits

Author SHA1 Message Date
github-actions[bot] 4693dfb1b0 Bumped version to 1.31.3 (#1345)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit d6d74e178c)
2022-02-15 09:05:19 -05:00
Addison Beck 5b21ab7c6a [bug] Fix tab order regression (#1340) 2022-02-14 15:43:54 -05:00
Oscar Hinton 3f40678599 Resolve preferences not setting correct focus (#1336)
(cherry picked from commit 2470d8ce25)
2022-02-14 14:53:43 +01:00
Thomas Rittson 81c6a8b9d7 Update i18n string and id for tray menu Lock item (#1334) 2022-02-14 08:40:31 -05:00
Addison Beck fd64b39b6a update jslib (#1337) 2022-02-14 08:35:00 -05:00
github-actions[bot] b01c2d6216 Bumped version to 1.31.2 (#1328)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit 6b6468e061)
2022-02-11 14:23:03 -08:00
Addison Beck ede3feb798 update jslib (#1324)
* update jslib

* Update jslib
2022-02-11 15:26:29 -05:00
Addison Beck 6d6f6f8743 update jslib (#1324)
* update jslib

* Update jslib
2022-02-11 12:59:25 -05:00
addison 54eded8311 Update jslib 2022-02-11 10:22:32 -05:00
Oscar Hinton 86a4a3e8d5 Bump jslib 2022-02-11 14:55:09 +01:00
Matt Gibson a879a7da7e update jslib 2022-02-11 04:47:09 -05:00
Joseph Flinn af5db8c8a1 reenabling job 2022-02-10 22:24:06 -08:00
github-actions[bot] b9ed2f6d48 Bump version to 1.31.1 (#1308)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit 530f25c88a)
2022-02-10 21:47:42 -08:00
Thomas Rittson 2dce34af68 Update jslib (#1318) 2022-02-11 00:37:25 -05:00
Thomas Rittson 5cf26ba1bc Update jslib (#1317) 2022-02-11 00:07:13 -05:00
Thomas Rittson 8f90e1317f Update jslib 2022-02-11 13:47:41 +10:00
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
26 changed files with 497 additions and 407 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

2
jslib

@ -1 +1 @@
Subproject commit 92a65b7b368a8dbf55350657674c90169b04c30b
Subproject commit 1e9c4cacce34b1c21a878bf6b5ca2c0dc3926cf8

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

@ -2,184 +2,184 @@
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body form">
<ng-container *ngIf="currentUserEmail != null">
<div class="box">
<label class="settingsTitle">{{ "settingsTitle" | i18n: currentUserEmail }} </label>
<div class="box-content box-content-padded">
<h2>
<button
type="button"
class="box-header-expandable"
(click)="showSecurity = !showSecurity"
[attr.aria-expanded]="showSecurity"
>
{{ "security" | i18n }}
<i
*ngIf="!showSecurity"
class="fa fa-chevron-down fa-sm icon"
aria-hidden="true"
></i>
<i
*ngIf="showSecurity"
class="fa fa-chevron-up fa-sm icon"
aria-hidden="true"
></i>
</button>
</h2>
<ng-container *ngIf="showSecurity">
<app-vault-timeout-input
[vaultTimeouts]="vaultTimeouts"
[formControl]="vaultTimeout"
ngDefaultControl
></app-vault-timeout-input>
<div class="form-group">
<label>{{ "vaultTimeoutAction" | i18n }}</label>
<div class="radio radio-mt-2">
<label for="vaultTimeoutActionLock">
<input
type="radio"
name="VaultTimeoutAction"
id="vaultTimeoutActionLock"
value="lock"
[(ngModel)]="vaultTimeoutAction"
(change)="saveVaultTimeoutOptions()"
/>
{{ "lock" | i18n }}
</label>
</div>
<small class="help-block">{{ "vaultTimeoutActionLockDesc" | i18n }}</small>
<div class="radio">
<label for="vaultTimeoutActionLogOut">
<input
type="radio"
name="VaultTimeoutAction"
id="vaultTimeoutActionLogOut"
value="logOut"
[(ngModel)]="vaultTimeoutAction"
(change)="saveVaultTimeoutOptions()"
/>
{{ "logOut" | i18n }}
</label>
</div>
<small class="help-block">{{ "vaultTimeoutActionLogOutDesc" | i18n }}</small>
<div class="box">
<label class="settingsTitle">{{ "settingsTitle" | i18n: currentUserEmail }} </label>
<div class="box-content box-content-padded">
<h2>
<button
id="app-settings"
type="button"
class="box-header-expandable"
(click)="showSecurity = !showSecurity"
[attr.aria-expanded]="showSecurity"
appAutofocus
>
{{ "security" | i18n }}
<i
*ngIf="!showSecurity"
class="bwi bwi-angle-down bwi-sm icon"
aria-hidden="true"
></i>
<i
*ngIf="showSecurity"
class="bwi bwi-chevron-up bwi-sm icon"
aria-hidden="true"
></i>
</button>
</h2>
<ng-container *ngIf="showSecurity">
<app-vault-timeout-input
[vaultTimeouts]="vaultTimeouts"
[formControl]="vaultTimeout"
ngDefaultControl
></app-vault-timeout-input>
<div class="form-group">
<label>{{ "vaultTimeoutAction" | i18n }}</label>
<div class="radio radio-mt-2">
<label for="vaultTimeoutActionLock">
<input
type="radio"
name="VaultTimeoutAction"
id="vaultTimeoutActionLock"
value="lock"
[(ngModel)]="vaultTimeoutAction"
(change)="saveVaultTimeoutOptions()"
/>
{{ "lock" | i18n }}
</label>
</div>
<div class="form-group">
<div class="checkbox">
<label for="pin">
<input
id="pin"
type="checkbox"
name="PIN"
[(ngModel)]="pin"
(change)="updatePin()"
/>
{{ "unlockWithPin" | i18n }}
</label>
</div>
<small class="help-block">{{ "vaultTimeoutActionLockDesc" | i18n }}</small>
<div class="radio">
<label for="vaultTimeoutActionLogOut">
<input
type="radio"
name="VaultTimeoutAction"
id="vaultTimeoutActionLogOut"
value="logOut"
[(ngModel)]="vaultTimeoutAction"
(change)="saveVaultTimeoutOptions()"
/>
{{ "logOut" | i18n }}
</label>
</div>
<div class="form-group" *ngIf="supportsBiometric">
<div class="checkbox">
<label for="biometric">
<input
id="biometric"
type="checkbox"
name="biometric"
[checked]="biometric"
(change)="updateBiometric()"
/>
{{ biometricText | i18n }}
</label>
</div>
<small class="help-block">{{ "vaultTimeoutActionLogOutDesc" | i18n }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="pin">
<input
id="pin"
type="checkbox"
name="PIN"
[(ngModel)]="pin"
(change)="updatePin()"
/>
{{ "unlockWithPin" | i18n }}
</label>
</div>
<div class="form-group" *ngIf="supportsBiometric">
<div class="checkbox">
<label for="noAutoPromptBiometrics">
<input
id="noAutoPromptBiometrics"
type="checkbox"
name="noAutoPromptBiometrics"
[(ngModel)]="noAutoPromptBiometrics"
[disabled]="!biometric"
(change)="updateNoAutoPromptBiometrics()"
/>
{{ noAutoPromptBiometricsText | i18n }}
</label>
</div>
</div>
<div class="form-group" *ngIf="supportsBiometric">
<div class="checkbox">
<label for="biometric">
<input
id="biometric"
type="checkbox"
name="biometric"
[checked]="biometric"
(change)="updateBiometric()"
/>
{{ biometricText | i18n }}
</label>
</div>
</ng-container>
</div>
</div>
<div class="form-group" *ngIf="supportsBiometric">
<div class="checkbox">
<label for="noAutoPromptBiometrics">
<input
id="noAutoPromptBiometrics"
type="checkbox"
name="noAutoPromptBiometrics"
[(ngModel)]="noAutoPromptBiometrics"
[disabled]="!biometric"
(change)="updateNoAutoPromptBiometrics()"
/>
{{ noAutoPromptBiometricsText | i18n }}
</label>
</div>
</div>
</ng-container>
</div>
<div class="box">
<div class="box-content box-content-padded">
<h2>
<button
type="button"
class="box-header-expandable"
(click)="showAccountPreferences = !showAccountPreferences"
[attr.aria-expanded]="showAccountPreferences"
</div>
<div class="box">
<div class="box-content box-content-padded">
<h2>
<button
type="button"
class="box-header-expandable"
(click)="showAccountPreferences = !showAccountPreferences"
[attr.aria-expanded]="showAccountPreferences"
>
{{ "accountPreferences" | i18n }}
<i
*ngIf="!showAccountPreferences"
class="bwi bwi-angle-down bwi-sm icon"
aria-hidden="true"
></i>
<i
*ngIf="showAccountPreferences"
class="bwi bwi-chevron-up bwi-sm icon"
aria-hidden="true"
></i>
</button>
</h2>
<ng-container *ngIf="showAccountPreferences">
<div class="form-group">
<label for="clearClipboard">{{ "clearClipboard" | i18n }}</label>
<select
id="clearClipboard"
name="ClearClipboard"
[(ngModel)]="clearClipboard"
(change)="saveClearClipboard()"
>
{{ "accountPreferences" | i18n }}
<i
*ngIf="!showAccountPreferences"
class="fa fa-chevron-down fa-sm icon"
aria-hidden="true"
></i>
<i
*ngIf="showAccountPreferences"
class="fa fa-chevron-up fa-sm icon"
aria-hidden="true"
></i>
</button>
</h2>
<ng-container *ngIf="showAccountPreferences">
<div class="form-group">
<label for="clearClipboard">{{ "clearClipboard" | i18n }}</label>
<select
id="clearClipboard"
name="ClearClipboard"
[(ngModel)]="clearClipboard"
(change)="saveClearClipboard()"
>
<option *ngFor="let o of clearClipboardOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
<small class="help-block">{{ "clearClipboardDesc" | i18n }}</small>
<option *ngFor="let o of clearClipboardOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
<small class="help-block">{{ "clearClipboardDesc" | i18n }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="minimizeOnCopyToClipboard">
<input
id="minimizeOnCopyToClipboard"
type="checkbox"
name="MinimizeOnCopyToClipboard"
[(ngModel)]="minimizeOnCopyToClipboard"
(change)="saveMinOnCopyToClipboard()"
/>
{{ "minimizeOnCopyToClipboard" | i18n }}
</label>
</div>
<div class="form-group">
<div class="checkbox">
<label for="minimizeOnCopyToClipboard">
<input
id="minimizeOnCopyToClipboard"
type="checkbox"
name="MinimizeOnCopyToClipboard"
[(ngModel)]="minimizeOnCopyToClipboard"
(change)="saveMinOnCopyToClipboard()"
/>
{{ "minimizeOnCopyToClipboard" | i18n }}
</label>
</div>
<small class="help-block">{{ "minimizeOnCopyToClipboardDesc" | i18n }}</small>
<small class="help-block">{{ "minimizeOnCopyToClipboardDesc" | i18n }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="disableFavicons">
<input
id="disableFavicons"
type="checkbox"
name="DisableFavicons"
[(ngModel)]="disableFavicons"
(change)="saveFavicons()"
/>
{{ "disableFavicon" | i18n }}
</label>
</div>
<div class="form-group">
<div class="checkbox">
<label for="disableFavicons">
<input
id="disableFavicons"
type="checkbox"
name="DisableFavicons"
[(ngModel)]="disableFavicons"
(change)="saveFavicons()"
/>
{{ "disableFavicon" | i18n }}
</label>
</div>
<small class="help-block">{{ "disableFaviconDesc" | i18n }}</small>
</div>
</ng-container>
</div>
<small class="help-block">{{ "disableFaviconDesc" | i18n }}</small>
</div>
</ng-container>
</div>
</ng-container>
</div>
<div class="box">
<div class="box-content box-content-padded">
<h2>
@ -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

@ -1,17 +1,4 @@
<div id="vault" class="vault" attr.aria-hidden="{{ showingModal }}">
<app-vault-groupings
id="groupings"
class="groupings"
(onAllClicked)="clearGroupingFilters()"
(onFavoritesClicked)="filterFavorites()"
(onCipherTypeClicked)="filterCipherType($event)"
(onFolderClicked)="filterFolder($event.id)"
(onAddFolder)="addFolder()"
(onEditFolder)="editFolder($event.id)"
(onCollectionClicked)="filterCollection($event.id)"
(onTrashClicked)="filterDeleted()"
>
</app-vault-groupings>
<app-vault-ciphers
id="items"
class="items"
@ -64,6 +51,19 @@
</div>
</div>
</div>
<app-vault-groupings
id="groupings"
class="groupings"
(onAllClicked)="clearGroupingFilters()"
(onFavoritesClicked)="filterFavorites()"
(onCipherTypeClicked)="filterCipherType($event)"
(onFolderClicked)="filterFolder($event.id)"
(onAddFolder)="addFolder()"
(onEditFolder)="editFolder($event.id)"
(onCollectionClicked)="filterCollection($event.id)"
(onTrashClicked)="filterDeleted()"
>
</app-vault-groupings>
</div>
<ng-template #passwordGenerator></ng-template>
<ng-template #attachments></ng-template>

View File

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

View File

@ -153,9 +153,9 @@ export class Main {
this.menuMain.init();
await this.trayMain.init("Bitwarden", [
{
label: this.i18nService.t("lockNow"),
label: this.i18nService.t("lockVault"),
enabled: false,
id: "lockNow",
id: "lockVault",
click: () => this.messagingService.send("lockVault"),
},
]);

View File

@ -99,10 +99,10 @@ export class MessagingMain {
) {
return;
}
const lockNowTrayMenuItem = this.main.trayMain.contextMenu.getMenuItemById("lockNow");
const lockVaultTrayMenuItem = this.main.trayMain.contextMenu.getMenuItemById("lockVault");
const activeAccount = updateRequest.accounts[updateRequest.activeUserId];
if (lockNowTrayMenuItem != null && activeAccount != null) {
lockNowTrayMenuItem.enabled = activeAccount.isAuthenticated && !activeAccount.isLocked;
if (lockVaultTrayMenuItem != null && activeAccount != null) {
lockVaultTrayMenuItem.enabled = activeAccount.isAuthenticated && !activeAccount.isLocked;
}
this.main.trayMain.updateContextMenu();
}

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.3",
"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

@ -27,7 +27,20 @@ app-root {
}
}
> .items {
order: 2;
}
> .details {
order: 3;
}
> .logo {
order: 4;
}
> .groupings {
order: 1;
width: 22%;
min-width: 175px;
max-width: 250px;

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]",
},