Compare commits

...

214 Commits

Author SHA1 Message Date
Oscar Hinton d7674f5c91
Update README.md (#1531) 2022-05-05 15:06:09 -04:00
Jake Fink 9852f2ec22
[PS-106] update jslib (#1530) 2022-05-05 17:11:39 +02:00
Micaiah Martin fec6838f90
Updated publisher to use generic (#1529) 2022-05-05 09:51:17 -05:00
Patrick H. Lauke 55a9403ee3
[PS-540] Accessibility: remove `appBlurClick`, add `aria-expanded` to cog options, links to buttons, fix unsufficient focus indication (#1514) 2022-05-05 16:36:46 +02:00
Oscar Hinton 817856bc82
Rust improvements (#1495) 2022-05-05 16:01:09 +02:00
Micaiah Martin 508292ae39
Patched build workflow (#1527) 2022-05-04 13:59:36 -05:00
Micaiah Martin 00fd2ec03f
Update publish settings to use S3 (#1521)
* Update publish settings to use S3

* Fix formatting

* Added endpoint with new domain

* Updated S3 publisher config

* added npm CD commands for workflow later.

* Updated release workflow to publish to S3

* testing release

* Reduce aws cli output

* Remove test

* Finalize release workflow
- Reverted back testing logic
- Removed dry run check for GH release since it creates it as a draf anyways
- Removed artifact_url env as it's no longer needed.

* Remove testing values

* Merge Master

* Added endpoint in config
2022-05-04 11:19:04 -04:00
Oscar Hinton 9a954710d9
Add keytar to externals (#1520) 2022-05-03 11:03:21 +02:00
Oscar Hinton a81c3c95a4
[CP-30] Add credit card pipe (#1517) 2022-05-02 19:45:24 +02:00
Kyle Spearrin 75470dc169
Forwarded email providers to username generator (#1511)
* forward email username generation

* update jslib
2022-05-02 10:57:01 -04:00
Oscar Hinton 18b5e4adfd
[EC-163] Undo move to rust implementation (#1509) 2022-05-02 12:39:38 +02:00
Patrick H. Lauke 0396d682b1
Change links to buttons, expose `aria-pressed` for toggles, add `aria-expanded` to send view's "Options" (#1437)
* Change links to buttons, expose `aria-pressed` for toggles

- also make existing `<a routerLink...>` type controls keyboard focusable with the addition of `tabindex="0"`

* Correctly set aria-pressed

now that I have a working build environment, could verify correct way to set this with my limited Angular knowledge

* Change more links to buttons, initial style changes

* Fix layout of <button> elements with .box-content-row

* Update jslib submodule

* Add `aria-expanded` to the send view's "Show options" expand/collapse control

* Fix position of "Edit" pencil when hovering over folders

* Update jslib

* Change sends list links to buttons

* Add `aria-pressed` to vault and send list buttons

Programmatically denote which of the buttons is currently active/shown in the right-most panel

* Fix incorrect "Options" expand/collapse button in add-edit view

Currently, that buttons lacks an accName because the "Options" text is outside of it.

* Add `aria-pressed` to the send left-hand column filters

* Simplify base, list, and vault styles

Since links are now buttons, no need to double up selectors for both types of elements. No need to double-up theming in base, as this also causes incorrect "x" in toasts.

* Remove unnecessary `position:relative`

Fixes issue with cut-off focus outlines, has no other adverse effect

* Fix styling for last child of action buttons

Old approach of making right padding smaller results in unsightly, off-center icon (noticeable when focus outline is visible). This visually remains the same, but reduces right-hand margin instead.
2022-04-30 16:09:41 +02:00
Thomas Rittson ef60112855
[PS-74] Fix user authentication state checks (#1464)
* Use new authStatus method, clean up account switcher

* Update naming

* update jslib
2022-04-30 09:16:46 +10:00
Thomas Rittson b467206448
[EC-156] [BEEEP] Remove factory providers in Angular DI (#1496)
* Use tokens

* Use initService

Co-authored-by: Oscar Hinton <oscar@oscarhinton.com>
2022-04-29 17:48:44 +10:00
Nils Fahldieck 55b301c267
Widen the sidebar to 600px max (#1503)
This implements the suggestion by MrBlack of the community forums in: https://community.bitwarden.com/t/manually-resizable-sidebar-columns-in-bitwarden-desktop-app/31909/3
2022-04-27 22:10:11 +02:00
github-actions[bot] a5ebb9fb52
Bumped version to 1.33.1 (#1502)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-04-25 11:40:39 -07:00
github-actions[bot] 2764c9610b
Bumped version to 1.33.0 (#1501)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-04-25 08:02:04 -07:00
Oscar Hinton 241e08b7ff
[EC-166] Move electron metadata to separate file (#1498)
* Move electron metadata to separate file

* Replace yml with json

* Update build script
2022-04-25 15:54:08 +02:00
Oscar Hinton 865e92c94c
Improve reorganization note (#1497) 2022-04-22 17:13:06 +02:00
Patrick H. Lauke c3d0a529fb
[CP-92] Add new more understandable labels to password generator checkboxes (#1461) 2022-04-22 15:26:38 +02:00
Oscar Hinton 1315b3c6cd
Add reorganization notice (#1494) 2022-04-22 09:51:13 +02:00
Dave Nicolson 9f77dd9d09
[CP-96] Capitalize menu items consistently (#1489) 2022-04-21 19:44:53 +02:00
Thomas Rittson 5082c7708a
[PS-211] [PS-212] Make Generator page accessible (#1493)
* Fix radiobutton names

* Add role=radiogroup

* Add aria-labelledby
2022-04-21 09:51:37 -04:00
dwbit 10d35d863b
Contribution Documentation edits (#1486)
Making corrections to the mobile contributions doc:

Update Crowdin contact from Kyle to dwbit.
Update 'User-to-User Support' forum category to 'Ask the Bitwarden Community category'
2022-04-21 08:02:41 -04:00
Kyle Spearrin caafbc2b73
fix disabling of generate type (#1491) 2022-04-20 11:46:12 -04:00
Thomas Rittson a4ca9bf64c
Update jslib (#1490)
* Update jslib

* Update name of UserVerificationComponent
2022-04-19 10:29:43 -05:00
Joseph Flinn 3862a19571
Bumping pinned commit of the download-artifact action to bypass the broken GitHub api (#1488) 2022-04-18 14:28:49 -07:00
Oscar Hinton 8be88a731c
Resolve rust for windows and linux (#1485) 2022-04-13 16:32:08 +02:00
Oscar Hinton 2b0d7ac72c
Bump libsecret for rust (#1480) 2022-04-13 08:04:48 -05:00
Matt Gibson e0d7d2b43a
Add descriptions to vague messages (#1476)
* Add descriptions to vague messages

* Fix typo
2022-04-12 15:36:25 +02:00
Oscar Hinton 70db11e659
Update readme with rust instructions (#1482) 2022-04-12 08:24:09 +02:00
Oscar Hinton 2e3c89269d
Resolve cross-compile for rust (#1481) 2022-04-11 19:53:16 +02:00
github-actions[bot] 31523bdf0e
Autosync the updated translations (#1478)
Co-authored-by: github-actions <>
2022-04-08 11:50:48 +02:00
Oscar Hinton 6b6666cd0d
Undo electron builder bump (#1477) 2022-04-07 22:13:58 +02:00
Oscar Hinton be1ab221f4
Bump electron dependencies (#1447)
Co-authored-by: Micaiah Martin <mmartin@bitwarden.com>
2022-04-05 19:46:07 +02:00
Oscar Hinton 78986023e7
[BEEEP] Add native rust module (#1379) 2022-04-05 16:54:44 +02:00
Addison Beck 14b9decf21
[bug] Adjust mode in render webpack.config to variable (#1473) 2022-04-04 10:45:35 -05:00
github-actions[bot] 0e9465601a
Autosync the updated translations (#1470)
Co-authored-by: github-actions <>
2022-04-01 13:10:34 +02:00
Oscar Hinton 52bb77fb66
Add electron:ignore which ignores invalid certificates (#1463) 2022-04-01 11:24:46 +02:00
Kyle Spearrin b4cec5b46f
generator updates (#1469)
* generator updates

* update jslib
2022-03-31 18:49:53 -04:00
Robyn MacCallum 72405ebe87
update jslib (#1467) 2022-03-31 11:39:27 -04:00
Patrick H. Lauke 4379274154
Add `aria-label` to password generator length slider, make it non-keyboard-focusable (#1459)
Closes https://github.com/bitwarden/desktop/issues/1458

Co-authored-by: Thomas Rittson <trittson@bitwarden.com>
2022-03-31 13:14:16 +10:00
Kyle Spearrin 9e0cc45704
Username generator (#1456)
* username generator implemented

* disable type when coming from add/edit

* restyle buttons to new icon-btn

* update generated-wrapper styles

* only show policy messages for passwords

* make generated-wrapper a standalone style

* Update src/app/vault/password-generator.component.html

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>

* aria-expanded on show options

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
2022-03-29 15:01:57 -04:00
Francesco De Feo bc21703a2b
Update macOS icon to follow Big Sur design (#837)
Co-authored-by: Hinton <oscar@oscarhinton.com>
2022-03-29 20:56:03 +02:00
Patrick H. Lauke 4ddeff2eee
Add `aria-pressed` to all eye/toggle visibility buttons (#1443)
* Add `aria-pressed` to all eye/toggle visibility buttons

Closes #1442

* Correct custom field visibility toggle `aria-pressed` value
2022-03-27 22:42:47 +02:00
Patrick H. Lauke 75af5b94d4
Fix "Custom environment", "Options" expand/collapse controls - wrong accessible name, state not exposed (#1441)
* Fix "Custom environment" expand/collapse control wrong accessible name, expose state

Closes #1440

* Correct "Options" expand/collapse accname, expose state

* Remove redundant appA11yTitle
2022-03-27 15:04:45 +02:00
Oscar Hinton cbadcccc85
Fix Mac App Store minimumSystemVersion (#1453) 2022-03-25 15:29:43 +01:00
Kyle Spearrin 21167301f1
Bump jslib and add compatability with username generation (#1450)
Co-authored-by: Hinton <oscar@oscarhinton.com>
2022-03-25 10:56:34 +01:00
github-actions[bot] ad6308eb48
Autosync the updated translations (#1451)
Co-authored-by: github-actions <>
2022-03-25 01:28:44 +01:00
Oscar Hinton fec023866e
Add utils for testing updating processes (#1448) 2022-03-24 21:23:29 +01:00
Jake Fink d2a7012d3f
PS-91 bug/mas hide update option (#1446)
* PS-91 - hide update option if MAS build

* fix isMacAppStore
2022-03-24 15:55:06 -04:00
Jake Fink 6555312034
PS-91 - hide update option if MAS build (#1432) 2022-03-23 13:03:08 -04:00
github-actions[bot] e2fe0c8b09
Bump version to 1.32.2 (#1429)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
- Patch for Testflight track.
2022-03-22 13:30:43 -06:00
Oscar Hinton 3b2427e913
Change minimumSystemVersion to only apply for MAS (#1428) 2022-03-22 13:43:37 -04:00
Oscar Hinton 37e9523f00
Define Angular CLI globals to support tree shaking (#1408) 2022-03-22 10:09:19 +01:00
github-actions[bot] 09d7376f39
Bump version to 1.32.1 (#1427)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-03-21 14:09:45 -06:00
Thomas Rittson be0b6113e4
[JslibModule] Refactor to use JslibModule (#1425) 2022-03-21 20:39:35 +10:00
github-actions[bot] 75f11cdbf1
Autosync the updated translations (#1421)
Co-authored-by: github-actions <>
2022-03-18 01:13:12 +01:00
github-actions[bot] e5feda0fa6
Bump version to 1.32.0 (#1415)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-03-16 10:46:16 -06:00
Micaiah Martin 5ebed777c2
Updated actions (#1412) 2022-03-16 10:11:17 -06:00
Vince Grassia e49d317d19
Remove safari build script from Build workflow (#1413) 2022-03-15 16:47:02 -04:00
Chad Scharf 114d7455b8
Update SECURITY.md (#1411)
* Update SECURITY.md

Add link to our HackerOne program for submitting potential security issues.

* Revise language on SECURITY.md
2022-03-15 15:56:26 -04:00
Vince Grassia d1146acb1e
Add Node caching to Build workflow (#1410) 2022-03-15 15:19:12 -04:00
Micaiah Martin 703d390566
Moved linting to it's own step, removed duplicates (#1404) 2022-03-15 10:38:13 -06:00
Thomas Rittson c0170563f2
Disable disk caching in stateService (#1406)
* Disable disk caching in stateService

* Turn back on caching for the renderer

Co-authored-by: addison <addisonbeck1@gmail.com>
2022-03-15 09:25:43 -04:00
Thomas Rittson f46405a508
Update jslib (#1403) 2022-03-15 15:15:49 +10:00
Micaiah Martin 6d429465fb
Added manual build trigger (#1405) 2022-03-14 16:53:59 -06:00
Matt Gibson 81457f6c05
Add jit entitlement (#1395)
* Add arm64 package

* temporarily remove workflow ignore

* Don't delete universal dist when completing arm64

* Add allow-jit entitlement

* Revert "Don't delete universal dist when completing arm64"

This reverts commit b06d638345.

* Revert "temporarily remove workflow ignore"

This reverts commit 8007983547.

* Revert "Add arm64 package"

This reverts commit c8359dae4d.

Co-authored-by: Hinton <oscar@oscarhinton.com>
2022-03-11 10:01:19 -06:00
github-actions[bot] aee27f9570
Autosync the updated translations (#1399)
Co-authored-by: github-actions <>
2022-03-11 02:25:09 +01:00
Joseph Flinn f845bcf6c1
Update hotfix release branch name to hotfix-rc (#1396) 2022-03-09 12:46:13 -08:00
Micaiah Martin b134eba27b
Renewed certs and profiles (#1393) 2022-03-04 10:25:28 -07:00
Vincent Salucci e84be59075
[Captcha] Implement captcha for 2fa (#1390)
* [Captcha] Implement captcha for 2fa

* Update jslib

* Added remaining items necessary for captcha // updated login to match 2fa style
2022-03-03 18:20:29 -06:00
github-actions[bot] edc5245173
Autosync the updated translations (#1394)
Co-authored-by: github-actions <>
2022-03-04 01:14:56 +01:00
Addison Beck 7bad97dd82
[dep] Update jslib (#1392) 2022-03-03 20:34:39 +01:00
Oscar Hinton 3071bec03f
Update NSIS build settings (#1389) 2022-03-03 19:50:35 +01:00
Thomas Rittson 80415f8cd5
Update jslib (#1387) 2022-03-02 08:49:07 +10:00
Daniel James Smith 03c279865f
BEEEP: Add missing languages (#1382)
* Pull in jslib

* Register missing languages
2022-03-01 13:52:53 +01:00
Addison Beck 54025f269a
[feature] Implement scope warning for exports (#1384) 2022-02-28 17:44:10 +01:00
Thomas Rittson 4ce5e5fbdc
Update Help menu options (#1383) 2022-02-28 11:23:14 +01:00
Addison Beck c738366eef
[dep] Update jslib (#1381) 2022-02-25 11:27:43 -05:00
Micaiah Martin 0114d96e18
[BEEEP] - Ignored workflow files from triggering builds (#1375) 2022-02-25 08:14:19 -06:00
Micaiah Martin 1eac8f0c0f
Added dry run logic (#1376) 2022-02-25 08:13:39 -06:00
Daniel James Smith 8880e9700e
BEEEP: Colorize hidden custom field when value visible (#1380) 2022-02-25 14:27:54 +01:00
github-actions[bot] 2f289ebd1f
Autosync the updated translations (#1378)
Co-authored-by: github-actions <>
2022-02-25 12:29:23 +01:00
Oscar Hinton 3aa52a5537
Add eslint (#1369) 2022-02-24 20:50:19 +01:00
Robyn MacCallum 233f876bbb
Accessibility fixes for account switcher menu (#1373) 2022-02-24 14:11:54 -05:00
Thomas Rittson d7276850a2
Update jslib (#1374)
* Update jslib

* Update i18n
2022-02-24 09:36:15 -05:00
Jake Fink e570551a5a
fix desktop close window shortcut (#1372)
* fix syntax not adding additional items

* lint
2022-02-23 09:58:59 -05:00
Vincent Salucci 7401204b70
[Icons] Added missing custom field sort icon (#1370) 2022-02-23 07:54:33 -06:00
github-actions[bot] 685ffbcac5
Bumped version to 1.31.4 (#1362)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-02-22 05:51:46 -08:00
Chad Scharf f9e6d13708
We're Hiring (#1367)
Added link to README.md for Bitwarden Careers page.
2022-02-22 14:04:32 +01:00
Micaiah Martin f798995764
Added linter workflow (#1355) 2022-02-18 13:28:17 -06:00
github-actions[bot] f6298b2684
Autosync the updated translations (#1360)
Co-authored-by: github-actions <>
2022-02-18 10:21:18 +01:00
Thomas Rittson 3881223a73
Exclude jslib from prettier hook (#1343)
* Exlude jslib from prettier hook
2022-02-17 10:37:41 +10:00
Matt Gibson d2df8dacad
Add label enforcement (#1346)
* Enforce Hold label

* Linting

Co-authored-by: Micaiah Martin <77340197+mimartin12@users.noreply.github.com>
2022-02-16 08:43:26 -06:00
Addison Beck 10ffdce5b9
[lib] Update jslib (#1347) 2022-02-15 15:05:31 -05:00
github-actions[bot] d6d74e178c
Bumped version to 1.31.3 (#1345)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-02-15 09:04:33 -05:00
Addison Beck 2e09265d3a
[bug] Fix tab order regression (#1340) 2022-02-14 15:43:05 -05:00
Thomas Rittson 659c9ea78d
Update i18n string and id for tray menu Lock item (#1334) 2022-02-14 08:40:06 -05:00
Addison Beck 884615c23a
update jslib (#1337) 2022-02-14 08:32:55 -05:00
Oscar Hinton 2470d8ce25
Resolve preferences not setting correct focus (#1336) 2022-02-14 14:23:05 +01:00
github-actions[bot] 6b6468e061
Bumped version to 1.31.2 (#1328)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-02-11 14:21:09 -08:00
github-actions[bot] 45b144361c
Autosync the updated translations (#1313)
Co-authored-by: github-actions <>
2022-02-11 22:46:20 +01:00
Addison Beck 881bb3cb49
Update jslib (#1327)
* Update jslib

* Add a null check

* Reworked condition

* Ran prettier
2022-02-11 15:34:36 -05:00
Addison Beck fd4c41b043
update jslib (#1324)
* update jslib

* Update jslib
2022-02-11 12:51:48 -05:00
Oscar Hinton 7a8a78b4df
Bump jslib (https://github.com/bitwarden/jslib/pull/673) (#1322) 2022-02-11 16:31:13 +01:00
Oscar Hinton f82e2fbb03
Bump jslib (performance and hasKeyStored fix) (#1321) 2022-02-11 14:53:49 +01:00
Matt Gibson e9a3c586f8
Fix/load lock component (#1319)
* Update jslib

* update jslib
2022-02-11 03:58:31 -06:00
Thomas Rittson f54a614d6b
Update jslib (#1318) 2022-02-11 00:30:53 -05:00
Thomas Rittson 95b5d68566
Update jslib (#1317) 2022-02-11 15:02:04 +10:00
Thomas Rittson 3ffb658db6
Update jslib (#1316) 2022-02-11 14:29:47 +10:00
github-actions[bot] 530f25c88a
Bump version to 1.31.1 (#1308)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-02-10 13:47:25 -07:00
Joseph Flinn 97d367dab8
Switching where we are pulling our snap token from (#1307) 2022-02-10 10:57:32 -08:00
Micaiah Martin 0a545c88b2
Update release.yml (#1306) 2022-02-10 10:51:50 -07:00
github-actions[bot] 0f6ee08dd5
Bump version to 1.31.0 (#1304)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-02-10 09:40:21 -07:00
Addison Beck b227ae13f6
Remove unused import (#1298) 2022-02-09 21:34:01 -05:00
Addison Beck 79f6a33596
[bug] Remove redundant token clean call (#1303)
* [bug] Remove redundant token clean call

* bump jslib
2022-02-09 17:06:30 -05:00
Addison Beck 1e80c4335f
[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:42:16 -05:00
Vincent Salucci c51b8523b7
[Help] Update links to new pattern (#1300)
* [Help] Update links to new pattern

* Update jslib
2022-02-08 18:02:17 -06:00
Joseph Flinn bd2ed43498
Fixing safari ref logic (#1299) 2022-02-08 07:41:58 -08:00
Oscar Hinton 243afc9da0
Client & Version headers (#1297) 2022-02-08 15:29:01 +01:00
Thomas Rittson 762e3f8198
Update client for authService refactor (#1239) 2022-02-08 09:26:53 +10:00
Thomas Rittson 4e75a25492
Make husky pre-commit hook executable (#1295) 2022-02-08 08:33:25 +10:00
Addison Beck 143a262743
[chore] Update jslib (#1296) 2022-02-07 12:08:15 -05:00
Addison Beck c1a3178538
Update jslib (#1293)
* Update jslib

* [style] Ran prettier
2022-02-03 14:39:49 -05:00
Oscar Hinton 166c459da4
Potentially improve path detection of regedit vbs files (#1288) 2022-02-03 19:39:23 +01:00
Daniel James Smith 94b561382d
fix announcement of security header (#1292) 2022-02-03 17:58:55 +01:00
Daniel James Smith 2b58861296
[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>
2022-02-03 17:46:14 +01:00
Vincent Salucci 3e8705d548
[Icons] FF - requested icon changes (#1291)
* [Icons] Remove FA

* Icon changes // webpack correction
2022-02-03 10:28:34 -06:00
Oscar Hinton 1e877f6cf8
Set minimumSystemVersion (#1287) 2022-02-03 14:13:50 +01:00
Joseph Flinn 9151fc0164
Switching the way we are pulling secrets for the EV SSL cert (#1285) 2022-02-02 14:35:03 -08:00
Addison Beck 03eed41d86
[bug] Remove scroll from login screen (#1283) 2022-02-02 10:32:06 -05:00
Addison Beck cad6e9481f
[bug] Attempt to resolve windows portable build issues (#1280) 2022-02-02 10:28:36 -05:00
Daniel James Smith 270411dff7
Fix icons on the settings headers (#1279) 2022-02-02 15:50:48 +01:00
Vincent Salucci 7c68e3802e
[bug] Add options string (#1281) 2022-02-01 23:06:29 -06:00
Addison Beck 48ff9f61ae
[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:31:30 -05:00
Addison Beck 2b22a39d45
[chore] Update jslib (#1277)
* [chore] Update jslib

* [bug] Correct value for system theme dropdown key

* [chore] Update jslib
2022-01-31 18:00:37 -05:00
Addison Beck c1ba54f646
[bug] Patch the windows menu bar regressions (#1273)
* [bug] Patch the windows menu bar regressions

* [chore] Update jslib
2022-01-28 11:27:30 -05:00
Daniel James Smith bb597e96a7
Use ThemeType instead of string (#1275) 2022-01-28 14:46:07 +01:00
github-actions[bot] 6aa9e7611a
Autosync the updated translations (#1274)
Co-authored-by: github-actions <>
2022-01-28 11:05:05 +01:00
Oscar Hinton 3c99920435
Add support for handling multiple accounts with native messaging (#1266) 2022-01-27 20:52:06 +01:00
Oscar Hinton a7c5f1ad45
Fix webpack using double dots (#1272) 2022-01-27 19:36:50 +01:00
Vincent Salucci ec3c95d736
[Icons] Update Font Sheet (#1245)
* [Icons] Update Font Sheet

* Added import statement for styles

* updated to clone icon

* Changed save to save changes icon

* Revert to using base bwi class

* Updated import order for bwi icon styles

* Converted new account switcher ui updates

* Bump jslib

* Fix occurances where bwi-eye-slash was used instead of btw-eye-slash-2

* Move settings cog to the left side

* Updated eye/eye-slash icon references

* Update jslib

* Update jslib

* Update fallback image for cipher icon

* Update jslib

Co-authored-by: Hinton <oscar@oscarhinton.com>
2022-01-27 11:21:53 -06:00
Addison Beck 058be7e895
[bug] Disable the preferences screen if there is no active unlocked vault (#1270) 2022-01-26 12:32:00 -05:00
Addison Beck ff7dd4ad8f
[bug] Don't monitor vault timeout action valueChanges until after init (#1271) 2022-01-26 09:33:37 -05:00
Addison Beck 9271ec37b9
[bug] Ensure remembering email always happens for desktop (#1269)
* [bug] Ensure remembering email always happens for desktop

* update jslib
2022-01-25 19:00:49 +01:00
Addison Beck d80fdc8a78
Adjust an iterator used to check for locked vaults on reloadProcess (#1268) 2022-01-25 09:49:57 -05:00
Addison Beck 555ee1c230
[bug] Checkout all vaults before reloading process (#1267) 2022-01-25 09:36:30 -05:00
Addison Beck c458b4d8a9
[bug] Move enableBrowserIntegration to global settings (#1265)
* [bug] Move enableBrowserIntegration to global settings

* [style] Ran prettier

* [chore] Update jslib
2022-01-24 11:35:24 -05:00
Oscar Hinton 7d46e5c145
Add explicit init to native messaging service (#1262) 2022-01-24 16:10:32 +01:00
Addison Beck ca41cdf8b5
[chore] Update jslib (#1264) 2022-01-24 08:45:35 -05:00
Addison Beck d211b3fcd2
Unset active account when adding additional accounts (#1258) 2022-01-24 07:28:39 -05:00
Addison Beck ef48ba1ae2
[bug] Correct DI setup for SystemService (#1257)
The DI refactor created a bad initlizer for SystemService that left out the reload callback.
This callback is null in prod, so I just set up a factory initlizer that used null for the callback value.

This fixes a bug causing clipboard clearing to not function, as platformUtilsService was not correctly injected.
2022-01-22 15:24:12 -05:00
Robyn MacCallum 33704b016f
Beeep/remove sad faces (#1255)
* Replace sad face with searching image

* Change css variable name

* Added aria-hidden="true" to images

* Run prettier
2022-01-21 14:37:28 -05:00
Oscar Hinton a64273f829
Hide account switcher border if no accounts (#1254) 2022-01-21 17:57:09 +01:00
Daniel James Smith cb4e6debf3
Fix display of account limit (#1253) 2022-01-21 14:15:24 +01:00
github-actions[bot] 2e7f8a127d
Autosync the updated translations (#1250)
Co-authored-by: github-actions <>
2022-01-21 11:51:41 +01:00
Thomas Rittson c6eaf3a31e
Move KeyConnector call from client to syncService (#1252) 2022-01-21 19:32:44 +10:00
Daniel James Smith 37b03b09a1
[Account Switching] Design changes to settings menu (#1244)
* Design changes to settings menu

* Remove black border on settings headers

* Pull in jslib

* Only load account related settings when authed

* Hide account related settings when not authed

* Change settings titles

* Changes discussed with Danielle
2022-01-20 22:56:15 +01:00
Oscar Hinton d1c01a2bb0
Remove account switching border (#1249) 2022-01-20 17:15:29 +01:00
Oscar Hinton 0edee78da1
Change account switching dropdown to align with the right side (#1248) 2022-01-20 16:32:05 +01:00
Addison Beck 80c9196e44
[chore] Update jslib (#1247) 2022-01-20 09:26:24 -05:00
Addison Beck 032d2be990
[bug] Remove old mac-bar styling (#1246)
With the account switching work a header was added to the desktop app that new acts as a home for the mac window controls.
Previously we needed a special home for these controls, but since moving them we are not just creating empty space.
Removing this class and the divs that use it corrects the behavior.
2022-01-20 07:44:20 -05:00
Daniel James Smith a684c102d7
Bump electron from 16.0.2 to 16.0.7 (#1243)
* Pull jslib to bump electron from 16.0.2 to 16.0.7

* Updates to package-lock.json
2022-01-20 12:22:22 +01:00
Daniel James Smith 12bb8b060c
[Account switching] Design changes (#1240)
* Various design changes to the account switcher

* Hide serverUrl on cloud accounts

* Display account limit reached instead of add account
2022-01-19 17:03:34 +01:00
Addison Beck b796fe094f
[Bug] [Account Switching] Improve State Management Performance (#1237)
* [dep] Implement new StateService factory parameter from jslib

* [bug] Ensure setLastActive uses the correct userId

Sometimes, because of how often it fires, setLastActive can cause accounts to override each other. To make sure the correct userId is always used we now subscribe to activeUser in the appComponent and pass that value into any setLastActive calls.

* [bug] Show loader when logging out

When logging out of a large vault the application can appear to hang. This commit turns on the app component loader while logout is doing work.

* [bug] Stop tracking activity without an active user

* [style] Ran prettier

* [chore] Update jslib
2022-01-19 11:00:28 -05:00
Daniel James Smith 8b521aa35e
[Account switching] Hide active account (#1241)
* Hide active account from dropdown

* No longer need to check if selected account is active before switching

* Applied feedback from PR review
2022-01-18 21:43:14 +01:00
Oscar Hinton 3cacf8a8c4
Rename package to @bitwarden/desktop (#1242) 2022-01-18 14:04:21 +01:00
Vince Grassia dad1a95fb0
Update Version Bump action to latest (#1238) 2022-01-14 13:32:16 -05:00
github-actions[bot] 5b6e5fc4fa
Autosync the updated translations (#1236)
Co-authored-by: github-actions <>
2022-01-14 11:21:53 +01:00
Oscar Hinton 4644d03b28
Exclude dist-safari from prettier (#1234) 2022-01-13 18:10:59 +01:00
Daniel James Smith abede3e5af
Fix logging out of all accounts instead of one (#1231)
When logOut is triggered and the userId is not provided. Retrieve currentUserId
2022-01-13 15:43:00 +01:00
Daniel James Smith 7818ffc2fb
[Account Switching] Fix menus (#1232)
* Fix enabled/disabling menu items with locked state

* Fix the empty about menu (title)

Moved the items to the help menu
2022-01-13 14:29:49 +01:00
Oscar Hinton b6117d6801
Re-add native dependencies to src/package.json (#1233) 2022-01-13 10:46:33 +01:00
Addison Beck 653ff8f45f
[Bug] VaultTimeout incorrectly defaults to "Never" (#1230)
* [Bug] VaultTimeout incorrectly defaults to "Never"

The default desktop vault timeout value is "On Restart", but there is no default set for this in the state service and account model.
This commit extends the StateService and Account model to consider the special vault timeout default requirements needed for desktop.

* [style] Lint fixes

* [chore] Update jslib
2022-01-12 11:37:23 -05:00
Daniel James Smith 71c2fee574
Close opened AccountSwitcher dropdown (#1228)
When a user clicks outside of an opened account switching dropdown, it should close automatically when clicking outside of the dropdown.
2022-01-12 17:20:19 +01:00
Daniel James Smith d885e3296b
Do not switch accounts if it is already the active account (#1229) 2022-01-12 17:20:07 +01:00
Addison Beck f32b917a9f
[Account Switching] Misc Bug Fixes and Refactors (#1223)
* [bug] Pull serverUrl directly from stateService for the account switcher

Create a small extended Account model for handling the switchers server url, and pull environment urls from disk where they actually live

* [refactor] Add a message handler for switching accounts

* This allows for logic reuse between manually switching accounts and automatically switching accounts on login
* This commit also adds a loading spinner to app root while syncing after a switch

* [bug] Remove vertical scrollbar

* An old styling fix to add extra height and padding seems to be now creating an unecassary scroll bar. It is likely that since making more use of flexbox for our containers that this issue has been resolved without the manually added extra hight & padding

* [refactor] Turn down activity monitoring

Saving last activity is a disk call, and we currently do this a lot more than is necassary. For example:
* We track mousedown & click, which is redundant
* We track every mouse movement regardless of if an action is taken. This seems inappropriate for use in locking behavior.

* [bug] Address potential race condition when locking

Sometimes when swapping between an unlocked account and a locked account a race condition occurs that swaps the user but doesn't redirect to the lock screen
This commit just adds some awaits and restructures lock order of operations to be more in line with other message handlers

* [refactor] Change click event to mousedown event for the account switcher

This is simply a little snappier, and ensures we stay ahead of change detection and don't get stuck not properly interpreting the action

* [chore] Update jslib

* [chore] Linter fixes

* [chore] Linter fixes

* [chore] Update jslib

* [chore] Update jslib
2022-01-12 09:23:00 -05:00
Daniel James Smith 2b64ec5375
Fix null ref when building/updating the MenuBar (#1227) 2022-01-12 15:19:18 +01:00
Oscar Hinton f6f0bd2bfb
Remove webpack-node-externals and use asarUnpack (#1221) 2022-01-12 11:32:02 +01:00
Oscar Hinton 23a7072341
Disable spellchecker (#1225) 2022-01-10 18:27:14 +01:00
Daniel James Smith d64b00977c
Fix menu separators (#1220)
* Fix separators appearing when they shouldn't

* Use const instead of let
2022-01-07 15:54:35 +01:00
github-actions[bot] 695e8389d8
Autosync the updated translations (#1222)
Co-authored-by: github-actions <>
2022-01-07 13:50:31 +01:00
Daniel James Smith dd73a45f64
Add ts files to prettier (#1219)
* Add ts files to prettier

* Add scss to prettier

* Add all filetypes to prettier and ignore via .prettierignore

* Add --ignore-unknown to prettier
2022-01-06 23:06:58 +01:00
Daniel James Smith a1bbbcf4bf
Update year in copyright (#1216) 2022-01-05 19:44:08 +01:00
Oscar Hinton 27b1ee2ab3
Whitelist rxjs in nodeExternals (#1218) 2022-01-04 20:51:21 +01:00
github-actions[bot] f01856d34f
Autosync the updated translations (#1213)
Co-authored-by: github-actions <>
2022-01-01 17:50:34 +01:00
Robyn MacCallum ef5e8e1e2a
rename fb to formBuilder (#1214) 2021-12-31 10:52:28 -05:00
Addison Beck ed78a79042
[bug] Apply background color for light theme account switcher (#1211) 2021-12-28 14:53:37 -05:00
github-actions[bot] 017a3bffd6
Autosync the updated translations (#1210)
Co-authored-by: github-actions <>
2021-12-24 01:08:21 +01:00
Micaiah Martin ad965e54e4
Rename .pkg to .pkg.archive in release pipeline (#1208)
* Add step to change .pkg file name
* Linting
2021-12-21 14:25:29 -07:00
Oscar Hinton 6e69b61128
Fix .git-blame-ignore-revs filename (#1207) 2021-12-21 17:05:54 +01:00
Daniel James Smith 406f4bf62c
Fixed prettier issue (#1206) 2021-12-21 15:22:12 +01:00
Linus Aarnio 83c3635932
Add credit card logos to allow displaying icons based on brand (#1149)
Co-authored-by: Hinton <oscar@oscarhinton.com>
2021-12-21 13:21:41 +01:00
Micaiah Martin 3c2094bb04
Patch version check to allow for redeployments. (#1204)
* Added logic for redployment

* Applied linting

- Prettier config was already updated by Oscar. b4df834b16
2021-12-20 10:49:16 -07:00
Oscar Hinton f7ae94199f
Add .git-blame-ignore-revs (#1203) 2021-12-20 16:49:00 +01:00
Oscar Hinton 521feae535
Apply Prettier (#1202) 2021-12-20 15:47:17 +01:00
Oscar Hinton b4df834b16
Add Prettier configuration (#1201) 2021-12-20 14:17:39 +01:00
Vince Grassia b33d37e610
Update with linter suggestions (#1199) 2021-12-17 12:26:26 -05:00
Daniel James Smith 3ad71103dd
Bump electron dependencies (#1194)
* Pull in jslib

* Bumping electron dependencies that relate tojslib

* Bump electron-rebuild

* Bump electron-builder

* Revert "Bump electron-builder"

This reverts commit 8cada43567.

* Bump jslib
2021-12-17 11:15:07 +01:00
github-actions[bot] da296559c0
Autosync the updated translations (#1198)
Co-authored-by: github-actions <>
2021-12-17 01:15:45 +01:00
Addison Beck 0b306ca1a7
[Account Switching] [Feature] Add the ability to maintain state for up to 5 accounts at once (#1079)
* [refactor] Remove references to deprecated services

* [feature] Implement account switching

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

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

* [feature] Enable locking any account from the menu

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

* [style] Fix lint complaints

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

* [style] Fix lint complaints

* rollup

* [style] Fix lint complaints

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

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

* [bug] Init the state service on start

* [feature] Limit account switching to 5 account maximum

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

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

* [bug] Add missing awaits

* [bug] Update app menu on logout

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

* [bug] Move authenticationStatus display information out of jslib

* [bug] Remove unused active style from scss

* [refactor] Rewrite the menu bar

* [style] Fix lint complaints

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

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

* [bug] Relocated several settings items to persistant storage

* [bug] Correct account switcher styles on all themes

* [chore] Include state migration service in services

* [bug] Swap to next account on logout

* [bug] Correct DI service

* [bug] fix loginGuard deps in services.module

* [chore] update jslib

* [bug] Remove badly merged scss

* [chore] update jslib

* [review] Code review cleanup

* [review] Code review cleanup

Co-authored-by: Hinton <oscar@oscarhinton.com>
2021-12-15 17:32:00 -05:00
Daniel James Smith 5865f08b37
Bump node to v16 (#1189)
* Pull in jslib

* Update .nvmrc

* Bump engines required to node 16 and npm 8

* Bump @types/node to 16
The dep on node 14.18 will get cleaned up once we bump electron

* Modify build.yml to build with node 16 and npm 8

* Update requirements in README.md

* Removed install of npm8 and renamed action step
npm8 is included in node v16

* Pull jslib
2021-12-13 17:16:44 +01:00
github-actions[bot] 74c9c1972f
Bumped version to 1.30.1 (#1191)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2021-12-10 10:05:34 -05:00
Oscar Hinton a10259ecab
Bump angular to v12 (#1186) 2021-12-09 20:29:24 +01:00
github-actions[bot] 9feb147654
Bumped version to 1.30.0 (#1187)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2021-12-08 09:02:21 -05:00
Vince Grassia 822c791cf9
Fix typo (#1185) 2021-12-08 08:48:37 -05:00
Oscar Hinton b70d2fb3c3
Fix development build not working with bitwarden:// protocol (#1165) 2021-12-07 21:04:52 +01:00
Oscar Hinton 5fd1da0c58
Replace toaster library (#1183) 2021-12-07 20:42:31 +01:00
github-actions[bot] 2822d748f1
Autosync the updated translations (#1174)
Co-authored-by: github-actions <>
2021-12-07 17:01:42 +01:00
Oscar Hinton 04cfdb246d
BEEEP: Refactor services DI (#1175) 2021-12-06 12:03:02 +01:00
Micaiah Martin 00d57d0310
Added version bump workflow (#1177)
* Added version bump workflow

* Fixed job name
2021-11-30 15:52:54 -07:00
Thomas Rittson b6db41c26c
Update jslib (#1176)
* Update jslib

* Update component constructors

* Update jslib
2021-11-29 10:21:48 +10:00
Thomas Rittson b83058ecab
Apply AppInputVerbatim directive (#1170) 2021-11-24 08:03:43 +10:00
271 changed files with 34174 additions and 22897 deletions

View File

@ -12,4 +12,4 @@ insert_final_newline = true
[*.{js,ts,scss,html}]
charset = utf-8
indent_style = space
indent_size = 4
indent_size = 2

9
.eslintignore Normal file
View File

@ -0,0 +1,9 @@
dist
build
jslib
webpack.main.js
webpack.renderer.js
src/scripts/duo.js
desktop_native
**/node_modules

32
.eslintrc.json Normal file
View File

@ -0,0 +1,32 @@
{
"root": true,
"env": {
"browser": true,
"node": true
},
"extends": ["./jslib/shared/eslintrc.json"],
"rules": {
"import/order": [
"error",
{
"alphabetize": {
"order": "asc"
},
"newlines-between": "always",
"pathGroups": [
{
"pattern": "jslib-*/**",
"group": "external",
"position": "after"
},
{
"pattern": "src/**/*",
"group": "parent",
"position": "before"
}
],
"pathGroupsExcludedImportTypes": ["builtin"]
}
]
}
}

2
.git-blame-ignore-revs Normal file
View File

@ -0,0 +1,2 @@
# Apply Prettier https://github.com/bitwarden/desktop/pull/1202
521feae535d83166e620c3c28dfc3e7b0314a00e

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

View File

@ -6,7 +6,7 @@ body:
attributes:
value: |
Thanks for taking the time to fill out this bug report!
Please do not submit feature requests. The [Community Forums](https://community.bitwarden.com) has a section for submitting, voting for, and discussing product feature requests.
- type: textarea
id: reproduce

View File

@ -1,4 +1,5 @@
## Type of change
- [ ] Bug fix
- [ ] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
@ -6,27 +7,26 @@
- [ ] Other
## Objective
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
## Code changes
<!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes-->
<!--Also refer to any related changes or PRs in other repositories-->
* **file.ext:** Description of what was changed and why
- **file.ext:** Description of what was changed and why
## Screenshots
<!--Required for any UI changes. Delete if not applicable-->
## Testing requirements
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
## Before you submit
- [ ] I have checked for **linting** errors (`npm run lint`) (required)
- [ ] This change requires a **documentation update** (notify the documentation team)
- [ ] This change has particular **deployment requirements** (notify the DevOps team)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ on:
workflow_dispatch:
inputs: {}
schedule:
- cron: '0 0 * * 5'
- cron: "0 0 * * 5"
jobs:
crowdin-sync:
@ -15,7 +15,7 @@ jobs:
_CROWDIN_PROJECT_ID: "299360"
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Login to Azure
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a

16
.github/workflows/enforce-labels.yml vendored Normal file
View File

@ -0,0 +1,16 @@
---
name: Enforce PR labels
on:
pull_request:
types: [labeled, unlabeled, opened, edited, synchronize]
jobs:
enforce-label:
name: EnforceLabel
runs-on: ubuntu-20.04
steps:
- name: Enforce Label
uses: yogevbd/enforce-label-action@8d1e1709b1011e6d90400a0e6cf7c0b77aa5efeb
with:
BANNED_LABELS: "hold"
BANNED_LABELS_DESCRIPTION: "PRs on hold cannot be merged"

View File

@ -3,6 +3,16 @@ name: Release
on:
workflow_dispatch:
inputs:
release_type:
description: 'Release Options'
required: true
default: 'Initial Release'
type: choice
options:
- Initial Release
- Redeploy
- Dry Run
jobs:
setup:
@ -13,10 +23,11 @@ jobs:
branch-name: ${{ steps.branch.outputs.branch-name }}
steps:
- name: Branch check
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
run: |
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix" ]]; then
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
echo "==================================="
echo "[!] Can only release from the 'rc' or 'hotfix' branches"
echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches"
echo "==================================="
exit 1
fi
@ -39,7 +50,8 @@ jobs:
echo "Latest version: $latest_ver"
ver=${{ steps.retrieve-version.outputs.package_version }}
echo "Version: $ver"
if [ "$latest_ver" = "$ver" ]; then
if [ "$latest_ver" = "$ver" ] && \
[ "${{ github.event.inputs.release_type }}" == "Initial Release" ]; then
echo "Version has not been bumped!"
exit 1
fi
@ -50,42 +62,69 @@ jobs:
BRANCH_NAME=$(basename ${{ github.ref }})
echo "::set-output name=branch-name::$BRANCH_NAME"
- name: Login to Azure
uses: Azure/login@ec3c14589bd3e9312b3cc8c41e6860e258df9010
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
with:
keyvault: "bitwarden-prod-kv"
secrets: "aws-electron-access-id, aws-electron-access-key"
- name: Download all artifacts
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
with:
workflow: build.yml
workflow_conclusion: success
branch: ${{ steps.branch.outputs.branch-name }}
path: ./artifacts
- name: Rename .pkg to .pkg.archive
env:
PKG_VERSION: ${{ steps.retrieve-version.outputs.package_version }}
working-directory: ./artifacts
run: mv Bitwarden-${{ env.PKG_VERSION }}-universal.pkg Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive
- name: Publish artifacts to S3
env:
AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.aws-electron-access-id }}
AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.aws-electron-access-key }}
AWS_DEFAULT_REGION: 'us-west-2'
run: |
aws s3 cp ./artifacts s3://public-s3-electron-artifacts/desktop/ \
--acl "public-read" \
--recursive \
--quiet
- name: Create release
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 # v2.8.5
env:
PKG_VERSION: ${{ steps.retrieve-version.outputs.package_version }}
with:
artifacts: "Bitwarden-${{ env.PKG_VERSION }}-amd64.deb,
Bitwarden-${{ env.PKG_VERSION }}-x86_64.rpm,
Bitwarden-${{ env.PKG_VERSION }}-x64.freebsd,
bitwarden_${{ env.PKG_VERSION }}_amd64.snap,
Bitwarden-${{ env.PKG_VERSION }}-x86_64.AppImage,
latest-linux.yml,
Bitwarden-Portable-${{ env.PKG_VERSION }}.exe,
Bitwarden-Installer-${{ env.PKG_VERSION }}.exe,
Bitwarden-${{ env.PKG_VERSION }}-ia32-store.appx,
Bitwarden-${{ env.PKG_VERSION }}-ia32.appx,
Bitwarden-${{ env.PKG_VERSION }}-ia32.nsis.7z,
Bitwarden-${{ env.PKG_VERSION }}-x64-store.appx,
Bitwarden-${{ env.PKG_VERSION }}-x64.appx,
Bitwarden-${{ env.PKG_VERSION }}-x64.nsis.7z,
Bitwarden-${{ env.PKG_VERSION }}-arm64-store.appx,
Bitwarden-${{ env.PKG_VERSION }}-arm64.appx,
Bitwarden-${{ env.PKG_VERSION }}-arm64.nsis.7z,
bitwarden.${{ env.PKG_VERSION }}.nupkg,
latest.yml,
Bitwarden-${{ env.PKG_VERSION }}-universal-mac.zip,
Bitwarden-${{ env.PKG_VERSION }}-universal.dmg,
Bitwarden-${{ env.PKG_VERSION }}-universal.dmg.blockmap,
latest-mac.yml,
Bitwarden-${{ env.PKG_VERSION }}-universal.pkg"
artifacts: "artifacts/Bitwarden-${{ env.PKG_VERSION }}-amd64.deb,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-x86_64.rpm,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-x64.freebsd,
artifacts/bitwarden_${{ env.PKG_VERSION }}_amd64.snap,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-x86_64.AppImage,
artifacts/Bitwarden-Portable-${{ env.PKG_VERSION }}.exe,
artifacts/Bitwarden-Installer-${{ env.PKG_VERSION }}.exe,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-ia32-store.appx,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-ia32.appx,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-ia32.nsis.7z,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-x64-store.appx,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-x64.appx,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-x64.nsis.7z,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-arm64-store.appx,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-arm64.appx,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-arm64.nsis.7z,
artifacts/bitwarden.${{ env.PKG_VERSION }}.nupkg,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-universal-mac.zip,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-universal.dmg,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-universal.dmg.blockmap,
artifacts/Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive"
commit: ${{ github.sha }}
tag: v${{ env.PKG_VERSION }}
name: Version ${{ env.PKG_VERSION }}
@ -93,7 +132,6 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
snap:
name: Deploy Snap
runs-on: ubuntu-20.04
@ -104,16 +142,28 @@ 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
- name: Download Snap artifact
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
with:
workflow: build.yml
workflow_conclusion: success
@ -121,15 +171,12 @@ jobs:
artifacts: bitwarden_${{ env._PKG_VERSION }}_amd64.snap
path: ./dist
- name: Test
run: ls -alht dist
- name: Deploy to Snap Store
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
run: |
snapcraft upload dist/bitwarden_${{ env._PKG_VERSION }}_amd64.snap --release stable
snapcraft logout
choco:
name: Deploy Choco
runs-on: windows-2019
@ -150,7 +197,7 @@ jobs:
run: New-Item -ItemType directory -Path ./dist
- name: Download choco artifact
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
with:
workflow: build.yml
workflow_conclusion: success
@ -159,6 +206,7 @@ jobs:
path: ./dist
- name: Push to Chocolatey
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
shell: pwsh
run: |
cd dist

65
.github/workflows/version-bump.yml vendored Normal file
View File

@ -0,0 +1,65 @@
---
name: Version Bump
on:
workflow_dispatch:
inputs:
version_number:
description: "New Version"
required: true
jobs:
bump_version:
name: "Create version_bump_${{ github.event.inputs.version_number }} branch"
runs-on: ubuntu-20.04
steps:
- name: Checkout Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
- name: Create Version Branch
run: |
git switch -c version_bump_${{ github.event.inputs.version_number }}
git push -u origin version_bump_${{ github.event.inputs.version_number }}
- name: Checkout Version Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
with:
ref: version_bump_${{ github.event.inputs.version_number }}
- name: Bump Version - Package
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
with:
version: ${{ github.event.inputs.version_number }}
file_path: "./src/package.json"
- name: Commit files
run: |
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
- name: Push changes
run: git push -u origin version_bump_${{ github.event.inputs.version_number }}
- name: Create Version PR
env:
PR_BRANCH: "version_bump_${{ github.event.inputs.version_number }}"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
BASE_BRANCH: master
TITLE: "Bump version to ${{ github.event.inputs.version_number }}"
run: |
gh pr create --title "$TITLE" \
--base "$BASE" \
--head "$PR_BRANCH" \
--label "version update" \
--label "automated pr" \
--body "
## Type of change
- [ ] Bug fix
- [ ] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [ ] Build/deploy pipeline (DevOps)
- [X] Other
## Objective
Automated version bump to ${{ github.event.inputs.version_number }}"

11
.github/workflows/workflow-linter.yml vendored Normal file
View File

@ -0,0 +1,11 @@
---
name: Workflow Linter
on:
pull_request:
paths:
- .github/workflows/**
jobs:
call-workflow:
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@master

1
.husky/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
_

4
.husky/pre-commit Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged

2
.nvmrc
View File

@ -1 +1 @@
v14.17.0
v16.13.1

14
.prettierignore Normal file
View File

@ -0,0 +1,14 @@
# Build directories
build
dist
dist-safari
desktop_native
jslib
# External libraries / auto synced locales
src/locales
src/scripts/duo.js
# Github Workflows
.github/workflows

3
.prettierrc.json Normal file
View File

@ -0,0 +1,3 @@
{
"printWidth": 100
}

4
.vscode/launch.json vendored
View File

@ -10,9 +10,7 @@
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
},
"args": [
"."
]
"args": ["."]
}
]
}

View File

@ -6,17 +6,12 @@ Please visit our [Community Forums](https://community.bitwarden.com/) for genera
Here is how you can get involved:
* **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
* **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
* **Report a bug or submit a bugfix:** Use Github issues and pull requests
* **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
* **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
* **Translate:** See the localization (l10n) section below
- **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
- **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
- **Report a bug or submit a bugfix:** Use Github issues and pull requests
- **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
- **Help other users:** Go to the [Ask the Bitwarden Community category](https://community.bitwarden.com/c/support/) on the Community Forums
- **Translate:** See the localization (l10n) section below
## Contributor Agreement
@ -24,9 +19,9 @@ Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/deskt
## Pull Request Guidelines
* use `npm run lint` and fix any linting suggestions before submitting a pull request
* commit any pull requests against the `master` branch
* include a link to your Community Forums post
- use `npm run lint` and fix any linting suggestions before submitting a pull request
- commit any pull requests against the `master` branch
- include a link to your Community Forums post
# Localization (l10n)
@ -36,6 +31,6 @@ We use a translation tool called [Crowdin](https://crowdin.com) to help manage o
If you are interested in helping translate the Bitwarden desktop app into another language (or make a translation correction), please register an account at Crowdin and join our project here: https://crowdin.com/project/bitwarden-desktop
If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/profile/kspearrin).
If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/profile/dwbit).
You can read Crowdin's getting started guide for translators here: https://support.crowdin.com/crowdin-intro/

View File

@ -2,6 +2,10 @@
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/bitwarden-desktop/localized.svg)](https://crowdin.com/project/bitwarden-desktop)
[![Join the chat at https://gitter.im/bitwarden/Lobby](https://badges.gitter.im/bitwarden/Lobby.svg)](https://gitter.im/bitwarden/Lobby)
> **Archived**
>
> This repository is archived, please go to https://github.com/bitwarden/clients for future development.
# Bitwarden Desktop Application
[![Platforms](https://imgur.com/SLv9paA.png "Windows, macOS, and Linux")](https://bitwarden.com/download/)
@ -12,34 +16,60 @@ The Bitwarden desktop app is written using Electron and Angular. The application
# Build/Run
**Requirements**
## Requirements
- [Node.js](https://nodejs.org) v14.17 or greater
- NPM v7
- Windows users: To compile the native node modules used in the app you will need the *Visual C++ toolset*, available through the standard Visual Studio installer. You will also need to install the *Microsoft Build Tools 2015* and *Windows 10 SDK 17134* as additional dependencies in the Visual Studio installer.
- [Node.js](https://nodejs.org) v16.13.1 (LTS) or greater
- NPM v8
- Windows:
- To compile the native node modules used in the app you will need the _Visual C++ toolset_, available through the standard Visual Studio installer. You will also need to install the _Microsoft Build Tools 2015_ and _Windows 10 SDK 17134_ as additional dependencies in the Visual Studio installer.
- Linux:
- The following packages `build-essential libsecret-1-dev libglib2.0-dev`
**Run the app**
## Run the app
```bash
npm install
npm ci
npm run electron
```
**Debug Native Messaging**
### Debug Native Messaging
Native Messaging (communication with the browser extension) works by having the browser start a lightweight proxy application baked into our desktop binary. To setup an environment which allows
for easy debugging you will need to build the application for distribution, i.e. `npm run dist:<platform>`, start the dist version and enable desktop integration. This will write some manifests
to disk, Consult the [native manifests](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_manifests#Manifest_location) documentation for more details of the manifest
format, and the exact locations for the different platforms. *Note* that disabling the desktop integration will delete the manifests, and the files will need to be updated again.
format, and the exact locations for the different platforms. _Note_ that disabling the desktop integration will delete the manifests, and the files will need to be updated again.
The generated manifests are pre-configured with the production ID for the browser extensions. In order to use them with the development builds, the browser extension ID of the development build
needs to be added to the `allowed_extensions` section of the manifest. These IDs are generated by the browser, and can be found in the extension settings within the browser.
needs to be added to the `allowed_extensions` section of the manifest. These IDs are generated by the browser, and can be found in the extension settings within the browser.
It will then be possible to run the desktop application as usual using `npm run electron` and communicate with the browser.
# We're Hiring!
Interested in contributing in a big way? Consider joining our team! We're hiring for many positions. Please take a look at our [Careers page](https://bitwarden.com/careers/) to see what opportunities are currently open as well as what it's like to work at Bitwarden.
# Contribute
Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [`CONTRIBUTING.md`](CONTRIBUTING.md) file.
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.
## Prettier
We recently migrated to using Prettier as code formatter. All previous branches will need to updated to avoid large merge conflicts using the following steps:
1. Check out your local Branch
2. Run `git merge b4df834b16d4f5d4162a926a5a308bdb3ebc718b`
3. Resolve any merge conflicts, commit.
4. Run `npm run prettier`
5. Commit
6. Run `git merge -Xours 521feae535d83166e620c3c28dfc3e7b0314a00e`
7. Push
### Git blame
We also recommend that you configure git to ignore the prettier revision using:
```bash
git config blame.ignoreRevsFile .git-blame-ignore-revs
```

View File

@ -1,39 +1,11 @@
Bitwarden believes that working with security researchers across the globe is crucial to keeping our
users safe. If you believe you've found a security issue in our product or service, we encourage you to
notify us. We welcome working with you to resolve the issue promptly. Thanks in advance!
Bitwarden believes that working with security researchers across the globe is crucial to keeping our users safe. If you believe you've found a security issue in our product or service, we encourage you to please submit a report through our [HackerOne Program](https://hackerone.com/bitwarden/). We welcome working with you to resolve the issue promptly. Thanks in advance!
# Disclosure Policy
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every
effort to quickly resolve the issue.
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a
third-party. We may publicly disclose the issue before resolving it, if appropriate.
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or
degradation of our service. Only interact with accounts you own or with explicit permission of the
account holder.
- If you would like to encrypt your report, please use the PGP key with long ID
`0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
# In-scope
- Security issues in any current release of Bitwarden. This includes the web vault, browser extension,
and mobile apps (iOS and Android). Product downloads are available at https://bitwarden.com. Source
code is available at https://github.com/bitwarden.
# Exclusions
The following bug classes are out-of scope:
- Bugs that are already reported on any of Bitwarden's issue trackers (https://github.com/bitwarden),
or that we already know of. Note that some of our issue tracking is private.
- Issues in an upstream software dependency (ex: Xamarin, ASP.NET) which are already reported to the
upstream maintainer.
- Attacks requiring physical access to a user's device.
- Self-XSS
- Issues related to software or protocols not under Bitwarden's control
- Vulnerabilities in outdated versions of Bitwarden
- Missing security best practices that do not directly lead to a vulnerability
- Issues that do not have any impact on the general public
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every effort to quickly resolve the issue.
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a third-party. We may publicly disclose the issue before resolving it, if appropriate.
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or degradation of our service. Only interact with accounts you own or with explicit permission of the account holder.
- If you would like to encrypt your report, please use the PGP key with long ID `0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
While researching, we'd like to ask you to refrain from:
@ -42,4 +14,8 @@ While researching, we'd like to ask you to refrain from:
- Social engineering (including phishing) of Bitwarden staff or contractors
- Any physical attempts against Bitwarden property or data centers
# We want to help you!
If you have something that you feel is close to exploitation, or if you'd like some information regarding the internal API, or generally have any questions regarding the app that would help in your efforts, please email us at https://bitwarden.com/contact and ask for that information. As stated above, Bitwarden wants to help you find issues, and is more than willing to help.
Thank you for helping keep Bitwarden and our users safe!

6
desktop_native/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
target
index.node
**/node_modules
**/.DS_Store
npm-debug.log*
*.node

946
desktop_native/Cargo.lock generated Normal file
View File

@ -0,0 +1,946 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "anyhow"
version = "1.0.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bytes"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cfg-expr"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e068cb2806bbc15b439846dc16c5f89f8599f2c3e4d73d4449d38f9b2f0b6c5"
dependencies = [
"smallvec",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "convert_case"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8"
[[package]]
name = "core-foundation"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "ctor"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "cxx"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce2295fe8865279f404147e9b2328e5af0ad11a2c016e58c13acfd48a07d8a55"
dependencies = [
"cc",
"cxxbridge-flags",
"cxxbridge-macro",
"link-cplusplus",
]
[[package]]
name = "cxx-build"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0aaaa055d4908326f1b4524b23ae53758019b806c0c4f382ea240982e9766b26"
dependencies = [
"cc",
"codespan-reporting",
"once_cell",
"proc-macro2",
"quote",
"scratch",
"syn",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a670224c6686471df12560a0b97a08145082e70bd38e2b0b5383b79e46c3da7"
[[package]]
name = "cxxbridge-macro"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b700096ca0dece28d9535fdb17ab784a8ae155d7f29d39c273643e6292c9620"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "desktop_native"
version = "0.0.0"
dependencies = [
"anyhow",
"core-foundation",
"gio",
"keytar",
"libsecret",
"napi",
"napi-build",
"napi-derive",
"scopeguard",
"security-framework",
"security-framework-sys",
"tokio",
"widestring",
"windows 0.32.0",
]
[[package]]
name = "futures-channel"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
[[package]]
name = "futures-executor"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
[[package]]
name = "futures-task"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
[[package]]
name = "futures-util"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "gio"
version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96efd8a1c00d890f6b45671916e165b5e43ccec61957d443aff6d7e44f62d348"
dependencies = [
"bitflags",
"futures-channel",
"futures-core",
"futures-io",
"gio-sys",
"glib",
"libc",
"once_cell",
"thiserror",
]
[[package]]
name = "gio-sys"
version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d0fa5052773f5a56b8ae47dab09d040f5d9ce1311f4f99006e16e9a08269296"
dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
"winapi",
]
[[package]]
name = "glib"
version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa570813c504bdf7539a9400180c2dd4b789a819556fb86da7226d7d1b037b49"
dependencies = [
"bitflags",
"futures-channel",
"futures-core",
"futures-executor",
"futures-task",
"glib-macros",
"glib-sys",
"gobject-sys",
"libc",
"once_cell",
"smallvec",
"thiserror",
]
[[package]]
name = "glib-macros"
version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41bfd8d227dead0829ac142454e97531b93f576d0805d779c42bfd799c65c572"
dependencies = [
"anyhow",
"heck",
"proc-macro-crate",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "glib-sys"
version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4366377bd56697de8aaee24e673c575d2694d72e7756324ded2b0428829a7b8"
dependencies = [
"libc",
"system-deps",
]
[[package]]
name = "gobject-sys"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df6859463843c20cf3837e3a9069b6ab2051aeeadf4c899d33344f4aea83189a"
dependencies = [
"glib-sys",
"libc",
"system-deps",
]
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "keytar"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d361c55fba09829ac620b040f5425bf239b1030c3d6820a84acac8da867dca4d"
dependencies = [
"keytar-sys",
]
[[package]]
name = "keytar-sys"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe908c6896705a1cb516cd6a5d956c63f08d95ace81b93253a98cd93e1e6a65a"
dependencies = [
"cc",
"cxx",
"cxx-build",
"pkg-config",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
[[package]]
name = "libsecret"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4af5a2342942fa42d706a424e9f9914287fb8317132750fd73a241140ac38c1"
dependencies = [
"bitflags",
"gio",
"glib",
"libc",
"libsecret-sys",
"once_cell",
]
[[package]]
name = "libsecret-sys"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "287d2a0fcd95e4d7b0ac6fc9f802691a790d7e522138713b0cacebc4e63cab91"
dependencies = [
"gio-sys",
"glib-sys",
"gobject-sys",
"libc",
"pkg-config",
"system-deps",
]
[[package]]
name = "link-cplusplus"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8cae2cd7ba2f3f63938b9c724475dfb7b9861b545a90324476324ed21dbc8c8"
dependencies = [
"cc",
]
[[package]]
name = "lock_api"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "mio"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2"
dependencies = [
"libc",
"log",
"miow",
"ntapi",
"winapi",
]
[[package]]
name = "miow"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
dependencies = [
"winapi",
]
[[package]]
name = "napi"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17ec66e60f000c78dd7c6215b6fa260e0591e09805024332bc5b3f55acc12244"
dependencies = [
"ctor",
"lazy_static",
"napi-sys",
"tokio",
"windows 0.30.0",
]
[[package]]
name = "napi-build"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebd4419172727423cf30351406c54f6cc1b354a2cfb4f1dba3e6cd07f6d5522b"
[[package]]
name = "napi-derive"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74ac5287a5e94a8728fc82d16c5127acc5eb5b8ad6404ef5f82d6a4ce8d5bdd2"
dependencies = [
"convert_case",
"napi-derive-backend",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "napi-derive-backend"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "427f4f04525635cdf22005d1be62d6d671bcb5550d694a1efb480a315422b4af"
dependencies = [
"convert_case",
"once_cell",
"proc-macro2",
"quote",
"regex",
"syn",
]
[[package]]
name = "napi-sys"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a385494dac3c52cbcacb393bb3b42669e7db8ab240c7ad5115f549eb061f2cc"
[[package]]
name = "ntapi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f"
dependencies = [
"winapi",
]
[[package]]
name = "num_cpus"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
[[package]]
name = "parking_lot"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-sys",
]
[[package]]
name = "pin-project-lite"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
[[package]]
name = "proc-macro-crate"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a"
dependencies = [
"thiserror",
"toml",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "scratch"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96311ef4a16462c757bb6a39152c40f58f31cd2602a40fceb937e2bc34e6cbab"
[[package]]
name = "security-framework"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "serde"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]]
name = "slab"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
[[package]]
name = "smallvec"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "socket2"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "syn"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "system-deps"
version = "6.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1a45a1c4c9015217e12347f2a411b57ce2c4fc543913b14b6fe40483328e709"
dependencies = [
"cfg-expr",
"heck",
"pkg-config",
"toml",
"version-compare",
]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee"
dependencies = [
"bytes",
"libc",
"memchr",
"mio",
"num_cpus",
"once_cell",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"winapi",
]
[[package]]
name = "tokio-macros"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "toml"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde",
]
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "version-compare"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "widestring"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b749ebd2304aa012c5992d11a25d07b406bdbe5f79d371cb7a918ce501a19eb0"
dependencies = [
"windows_aarch64_msvc 0.30.0",
"windows_i686_gnu 0.30.0",
"windows_i686_msvc 0.30.0",
"windows_x86_64_gnu 0.30.0",
"windows_x86_64_msvc 0.30.0",
]
[[package]]
name = "windows"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbedf6db9096bc2364adce0ae0aa636dcd89f3c3f2cd67947062aaf0ca2a10ec"
dependencies = [
"windows_aarch64_msvc 0.32.0",
"windows_i686_gnu 0.32.0",
"windows_i686_msvc 0.32.0",
"windows_x86_64_gnu 0.32.0",
"windows_x86_64_msvc 0.32.0",
]
[[package]]
name = "windows-sys"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6"
dependencies = [
"windows_aarch64_msvc 0.32.0",
"windows_i686_gnu 0.32.0",
"windows_i686_msvc 0.32.0",
"windows_x86_64_gnu 0.32.0",
"windows_x86_64_msvc 0.32.0",
]
[[package]]
name = "windows_aarch64_msvc"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29277a4435d642f775f63c7d1faeb927adba532886ce0287bd985bffb16b6bca"
[[package]]
name = "windows_aarch64_msvc"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5"
[[package]]
name = "windows_i686_gnu"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1145e1989da93956c68d1864f32fb97c8f561a8f89a5125f6a2b7ea75524e4b8"
[[package]]
name = "windows_i686_gnu"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615"
[[package]]
name = "windows_i686_msvc"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4a09e3a0d4753b73019db171c1339cd4362c8c44baf1bcea336235e955954a6"
[[package]]
name = "windows_i686_msvc"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172"
[[package]]
name = "windows_x86_64_gnu"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ca64fcb0220d58db4c119e050e7af03c69e6f4f415ef69ec1773d9aab422d5a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc"
[[package]]
name = "windows_x86_64_msvc"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08cabc9f0066848fef4bc6a1c1668e6efce38b661d2aeec75d18d8617eebb5f1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"

43
desktop_native/Cargo.toml Normal file
View File

@ -0,0 +1,43 @@
[package]
edition = "2021"
exclude = ["index.node"]
license = "GPL-3.0"
name = "desktop_native"
version = "0.0.0"
[lib]
crate-type = ["cdylib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0"
napi = {version = "2", features = ["async"]}
napi-derive = "2"
scopeguard = "1.1.0"
tokio = {version = "1.17.0", features = ["full"]}
[build-dependencies]
napi-build = "1"
[target.'cfg(windows)'.dependencies]
widestring = "0.5.1"
windows = {version = "0.32.0", features = [
"alloc",
"Foundation",
"Storage_Streams",
"Win32_Foundation",
"Win32_Security_Credentials",
]}
[target.'cfg(windows)'.dev-dependencies]
keytar = "0.1.6"
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "0.9.3"
security-framework = "2.6.1"
security-framework-sys = "2.6.1"
[target.'cfg(target_os = "linux")'.dependencies]
gio = "0.15.6"
libsecret = "0.1.4"

22
desktop_native/build.js Normal file
View File

@ -0,0 +1,22 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const child_process = require("child_process");
const process = require("process");
let targets = [];
switch (process.platform) {
case "win32":
targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc"];
break;
case "darwin":
targets = ["x86_64-apple-darwin", "aarch64-apple-darwin"];
break;
default:
targets = ['x86_64-unknown-linux-gnu'];
break;
}
targets.forEach(target => {
child_process.execSync(`npm run build -- --target ${target}`, {stdio: 'inherit'});
});

5
desktop_native/build.rs Normal file
View File

@ -0,0 +1,5 @@
extern crate napi_build;
fn main() {
napi_build::setup();
}

15
desktop_native/index.d.ts vendored Normal file
View File

@ -0,0 +1,15 @@
/* tslint:disable */
/* eslint-disable */
/* auto-generated by NAPI-RS */
export namespace passwords {
/** Fetch the stored password from the keychain. */
export function getPassword(service: string, account: string): Promise<string>
/** Fetch the stored password from the keychain that was stored with Keytar. */
export function getPasswordKeytar(service: string, account: string): Promise<string>
/** Save the password to the keychain. Adds an entry if none exists otherwise updates the existing entry. */
export function setPassword(service: string, account: string, password: string): Promise<void>
/** Delete the stored password from the keychain. */
export function deletePassword(service: string, account: string): Promise<void>
}

241
desktop_native/index.js Normal file
View File

@ -0,0 +1,241 @@
const { existsSync, readFileSync } = require('fs')
const { join } = require('path')
const { platform, arch } = process
let nativeBinding = null
let localFileExisted = false
let loadError = null
function isMusl() {
// For Node 10
if (!process.report || typeof process.report.getReport !== 'function') {
try {
return readFileSync('/usr/bin/ldd', 'utf8').includes('musl')
} catch (e) {
return true
}
} else {
const { glibcVersionRuntime } = process.report.getReport().header
return !glibcVersionRuntime
}
}
switch (platform) {
case 'android':
switch (arch) {
case 'arm64':
localFileExisted = existsSync(join(__dirname, 'desktop_native.android-arm64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.android-arm64.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-android-arm64')
}
} catch (e) {
loadError = e
}
break
case 'arm':
localFileExisted = existsSync(join(__dirname, 'desktop_native.android-arm-eabi.node'))
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.android-arm-eabi.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-android-arm-eabi')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Android ${arch}`)
}
break
case 'win32':
switch (arch) {
case 'x64':
localFileExisted = existsSync(
join(__dirname, 'desktop_native.win32-x64-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.win32-x64-msvc.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-win32-x64-msvc')
}
} catch (e) {
loadError = e
}
break
case 'ia32':
localFileExisted = existsSync(
join(__dirname, 'desktop_native.win32-ia32-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.win32-ia32-msvc.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-win32-ia32-msvc')
}
} catch (e) {
loadError = e
}
break
case 'arm64':
localFileExisted = existsSync(
join(__dirname, 'desktop_native.win32-arm64-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.win32-arm64-msvc.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-win32-arm64-msvc')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Windows: ${arch}`)
}
break
case 'darwin':
switch (arch) {
case 'x64':
localFileExisted = existsSync(join(__dirname, 'desktop_native.darwin-x64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.darwin-x64.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-darwin-x64')
}
} catch (e) {
loadError = e
}
break
case 'arm64':
localFileExisted = existsSync(
join(__dirname, 'desktop_native.darwin-arm64.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.darwin-arm64.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-darwin-arm64')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on macOS: ${arch}`)
}
break
case 'freebsd':
if (arch !== 'x64') {
throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
}
localFileExisted = existsSync(join(__dirname, 'desktop_native.freebsd-x64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.freebsd-x64.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-freebsd-x64')
}
} catch (e) {
loadError = e
}
break
case 'linux':
switch (arch) {
case 'x64':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'desktop_native.linux-x64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.linux-x64-musl.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-linux-x64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'desktop_native.linux-x64-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.linux-x64-gnu.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-linux-x64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 'arm64':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'desktop_native.linux-arm64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.linux-arm64-musl.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-linux-arm64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'desktop_native.linux-arm64-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.linux-arm64-gnu.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-linux-arm64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 'arm':
localFileExisted = existsSync(
join(__dirname, 'desktop_native.linux-arm-gnueabihf.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./desktop_native.linux-arm-gnueabihf.node')
} else {
nativeBinding = require('@bitwarden/desktop_native-linux-arm-gnueabihf')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Linux: ${arch}`)
}
break
default:
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
}
if (!nativeBinding) {
if (loadError) {
throw loadError
}
throw new Error(`Failed to load native binding`)
}
const { passwords } = nativeBinding
module.exports.passwords = passwords

41
desktop_native/package-lock.json generated Normal file
View File

@ -0,0 +1,41 @@
{
"name": "@bitwarden/desktop_native",
"version": "0.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@bitwarden/desktop_native",
"version": "0.1.0",
"hasInstallScript": true,
"license": "GPL-3.0",
"devDependencies": {
"@napi-rs/cli": "^2.6.2"
}
},
"node_modules/@napi-rs/cli": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.6.2.tgz",
"integrity": "sha512-EmH+DQDEBUIoqMim0cc+X96ImtcIZLFjgW5WWORpzYnA9Ug7zNPO7jCLMhIQRd/p5AdWaXrT4SVXc/aip09rKQ==",
"dev": true,
"bin": {
"napi": "scripts/index.js"
},
"engines": {
"node": ">= 10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
}
}
},
"dependencies": {
"@napi-rs/cli": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.6.2.tgz",
"integrity": "sha512-EmH+DQDEBUIoqMim0cc+X96ImtcIZLFjgW5WWORpzYnA9Ug7zNPO7jCLMhIQRd/p5AdWaXrT4SVXc/aip09rKQ==",
"dev": true
}
}
}

View File

@ -0,0 +1,32 @@
{
"name": "@bitwarden/desktop_native",
"version": "0.1.0",
"description": "",
"main": "index.node",
"scripts": {
"build": "napi build --release --platform",
"build:debug": "napi build --platform",
"build:cross-platform": "node build.js",
"test": "cargo test"
},
"author": "",
"license": "GPL-3.0",
"devDependencies": {
"@napi-rs/cli": "^2.6.2"
},
"napi": {
"name": "desktop_native",
"triples": {
"defaults": true,
"additional": [
"x86_64-unknown-linux-musl",
"aarch64-unknown-linux-gnu",
"i686-pc-windows-msvc",
"armv7-unknown-linux-gnueabihf",
"aarch64-apple-darwin",
"aarch64-unknown-linux-musl",
"aarch64-pc-windows-msvc"
]
}
}
}

39
desktop_native/src/lib.rs Normal file
View File

@ -0,0 +1,39 @@
#[macro_use]
extern crate napi_derive;
mod password;
#[napi]
pub mod passwords {
/// Fetch the stored password from the keychain.
#[napi]
pub async fn get_password(service: String, account: String) -> napi::Result<String> {
super::password::get_password(&service, &account)
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
/// Fetch the stored password from the keychain that was stored with Keytar.
#[napi]
pub async fn get_password_keytar(service: String, account: String) -> napi::Result<String> {
super::password::get_password_keytar(&service, &account)
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
/// Save the password to the keychain. Adds an entry if none exists otherwise updates the existing entry.
#[napi]
pub async fn set_password(
service: String,
account: String,
password: String,
) -> napi::Result<()> {
super::password::set_password(&service, &account, &password)
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
/// Delete the stored password from the keychain.
#[napi]
pub async fn delete_password(service: String, account: String) -> napi::Result<()> {
super::password::delete_password(&service, &account)
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
}

View File

@ -0,0 +1,59 @@
use anyhow::Result;
use security_framework::passwords::{
delete_generic_password, get_generic_password, set_generic_password,
};
pub fn get_password(service: &str, account: &str) -> Result<String> {
let result = String::from_utf8(get_generic_password(&service, &account)?)?;
Ok(result)
}
pub fn get_password_keytar(service: &str, account: &str) -> Result<String> {
get_password(service, account)
}
pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> {
let result = set_generic_password(&service, &account, password.as_bytes())?;
Ok(result)
}
pub fn delete_password(service: &str, account: &str) -> Result<()> {
let result = delete_generic_password(&service, &account)?;
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({}););
set_password("BitwardenTest", "BitwardenTest", "Random").unwrap();
assert_eq!(
"Random",
get_password("BitwardenTest", "BitwardenTest").unwrap()
);
delete_password("BitwardenTest", "BitwardenTest").unwrap();
// Ensure password is deleted
match get_password("BitwardenTest", "BitwardenTest") {
Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!(
"The specified item could not be found in the keychain.",
e.to_string()
),
}
}
#[test]
fn test_error_no_password() {
match get_password("Unknown", "Unknown") {
Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!(
"The specified item could not be found in the keychain.",
e.to_string()
),
}
}
}

View File

@ -0,0 +1,5 @@
#[cfg_attr(target_os = "linux", path = "unix.rs")]
#[cfg_attr(target_os = "windows", path = "windows.rs")]
#[cfg_attr(target_os = "macos", path = "macos.rs")]
mod password;
pub use password::*;

View File

@ -0,0 +1,91 @@
use anyhow::{anyhow, Result};
use libsecret::{password_clear_sync, password_lookup_sync, password_store_sync, Schema};
use std::collections::HashMap;
pub fn get_password(service: &str, account: &str) -> Result<String> {
let res = password_lookup_sync(
Some(&get_schema()),
build_attributes(service, account),
gio::Cancellable::NONE,
)?;
match res {
Some(s) => Ok(String::from(s)),
None => Err(anyhow!("No password found")),
}
}
pub fn get_password_keytar(service: &str, account: &str) -> Result<String> {
get_password(service, account)
}
pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> {
let result = password_store_sync(
Some(&get_schema()),
build_attributes(service, account),
Some(&libsecret::COLLECTION_DEFAULT),
&format!("{}/{}", service, account),
password,
gio::Cancellable::NONE,
)?;
Ok(result)
}
pub fn delete_password(service: &str, account: &str) -> Result<()> {
let result = password_clear_sync(
Some(&get_schema()),
build_attributes(service, account),
gio::Cancellable::NONE,
)?;
Ok(result)
}
fn get_schema() -> Schema {
let mut attributes = std::collections::HashMap::new();
attributes.insert("service", libsecret::SchemaAttributeType::String);
attributes.insert("account", libsecret::SchemaAttributeType::String);
libsecret::Schema::new(
"org.freedesktop.Secret.Generic",
libsecret::SchemaFlags::NONE,
attributes,
)
}
fn build_attributes<'a>(service: &'a str, account: &'a str) -> HashMap<&'a str, &'a str> {
let mut attributes = HashMap::new();
attributes.insert("service", service);
attributes.insert("account", account);
attributes
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({}););
set_password("BitwardenTest", "BitwardenTest", "Random").unwrap();
assert_eq!(
"Random",
get_password("BitwardenTest", "BitwardenTest").unwrap()
);
delete_password("BitwardenTest", "BitwardenTest").unwrap();
// Ensure password is deleted
match get_password("BitwardenTest", "BitwardenTest") {
Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!("No password found", e.to_string()),
}
}
#[test]
fn test_error_no_password() {
match get_password("BitwardenTest", "BitwardenTest") {
Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!("No password found", e.to_string()),
}
}
}

View File

@ -0,0 +1,180 @@
use anyhow::{anyhow, Result};
use widestring::{U16CString, U16String};
use windows::Win32::{
Foundation::{GetLastError, ERROR_NOT_FOUND, FILETIME, PWSTR, WIN32_ERROR},
Security::Credentials::{
CredDeleteW, CredFree, CredReadW, CredWriteW, CREDENTIALW, CRED_FLAGS,
CRED_PERSIST_ENTERPRISE, CRED_TYPE_GENERIC,
},
};
const CRED_FLAGS_NONE: u32 = 0;
pub fn get_password<'a>(service: &str, account: &str) -> Result<String> {
let target_name = U16CString::from_str(target_name(service, account))?;
let mut credential: *mut CREDENTIALW = std::ptr::null_mut();
let credential_ptr = &mut credential;
let result = unsafe {
CredReadW(
PWSTR(target_name.as_ptr()),
CRED_TYPE_GENERIC.0,
CRED_FLAGS_NONE,
credential_ptr,
)
};
scopeguard::defer!({
unsafe { CredFree(credential as *mut _) };
});
if !result.as_bool() {
return Err(anyhow!(convert_error(unsafe { GetLastError() })));
}
let password = unsafe {
U16String::from_ptr(
(*credential).CredentialBlob as *const u16,
(*credential).CredentialBlobSize as usize / 2,
)
.to_string_lossy()
};
Ok(String::from(password))
}
// Remove this after sufficient releases
pub fn get_password_keytar<'a>(service: &str, account: &str) -> Result<String> {
let target_name = U16CString::from_str(target_name(service, account))?;
let mut credential: *mut CREDENTIALW = std::ptr::null_mut();
let credential_ptr = &mut credential;
let result = unsafe {
CredReadW(
PWSTR(target_name.as_ptr()),
CRED_TYPE_GENERIC.0,
CRED_FLAGS_NONE,
credential_ptr,
)
};
scopeguard::defer!({
unsafe { CredFree(credential as *mut _) };
});
if !result.as_bool() {
return Err(anyhow!(unsafe { GetLastError() }.0.to_string()));
}
let password = unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(
(*credential).CredentialBlob,
(*credential).CredentialBlobSize as usize,
))
};
Ok(String::from(password))
}
pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> {
let target_name = U16CString::from_str(target_name(service, account))?;
let user_name = U16CString::from_str(account)?;
let last_written = FILETIME {
dwLowDateTime: 0,
dwHighDateTime: 0,
};
let credential = U16CString::from_str(password)?;
let credential_len = password.len() as u32 * 2;
let credential = CREDENTIALW {
Flags: CRED_FLAGS(CRED_FLAGS_NONE),
Type: CRED_TYPE_GENERIC,
TargetName: PWSTR(target_name.as_ptr()),
Comment: PWSTR::default(),
LastWritten: last_written,
CredentialBlobSize: credential_len,
CredentialBlob: credential.as_ptr() as *mut u8,
Persist: CRED_PERSIST_ENTERPRISE,
AttributeCount: 0,
Attributes: std::ptr::null_mut(),
TargetAlias: PWSTR::default(),
UserName: PWSTR(user_name.as_ptr()),
};
let result = unsafe { CredWriteW(&credential, 0) };
if !result.as_bool() {
return Err(anyhow!(unsafe { GetLastError() }.0.to_string()));
}
Ok(())
}
pub fn delete_password(service: &str, account: &str) -> Result<()> {
let target_name = U16CString::from_str(target_name(service, account))?;
unsafe {
CredDeleteW(
PWSTR(target_name.as_ptr()),
CRED_TYPE_GENERIC.0,
CRED_FLAGS_NONE,
)
.ok()?
};
Ok(())
}
fn target_name(service: &str, account: &str) -> String {
format!("{}/{}", service, account)
}
// Convert the internal WIN32 errors to descriptive messages
fn convert_error(code: WIN32_ERROR) -> String {
match code {
ERROR_NOT_FOUND => String::from("Password not found."),
_ => code.0.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({}););
set_password("BitwardenTest", "BitwardenTest", "Random").unwrap();
assert_eq!(
"Random",
get_password("BitwardenTest", "BitwardenTest").unwrap()
);
delete_password("BitwardenTest", "BitwardenTest").unwrap();
// Ensure password is deleted
match get_password("BitwardenTest", "BitwardenTest") {
Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!("Password not found.", e.to_string()),
}
}
#[test]
fn test_get_password_keytar() {
scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({}););
keytar::set_password("BitwardenTest", "BitwardenTest", "HelloFromKeytar").unwrap();
assert_eq!(
"HelloFromKeytar",
get_password_keytar("BitwardenTest", "BitwardenTest").unwrap()
);
}
#[test]
fn test_error_no_password() {
match get_password("BitwardenTest", "BitwardenTest") {
Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!("Password not found.", e.to_string()),
}
}
}

121
electron-builder.json Normal file
View File

@ -0,0 +1,121 @@
{
"extraMetadata": { "name": "bitwarden" },
"productName": "Bitwarden",
"appId": "com.bitwarden.desktop",
"buildDependenciesFromSource": true,
"copyright": "Copyright © 2015-2022 Bitwarden Inc.",
"directories": { "buildResources": "resources", "output": "dist", "app": "build" },
"afterSign": "scripts/after-sign.js",
"asarUnpack": ["**/*.node"],
"files": ["**/*", "!**/node_modules/@bitwarden/desktop-native/**/*"],
"publish": {
"provider": "generic",
"url": "https://artifacts.bitwarden.com/desktop"
},
"mac": {
"electronUpdaterCompatibility": ">=0.0.1",
"category": "public.app-category.productivity",
"darkModeSupport": true,
"gatekeeperAssess": false,
"hardenedRuntime": true,
"entitlements": "resources/entitlements.mac.plist",
"entitlementsInherit": "resources/entitlements.mac.plist",
"extendInfo": {
"ITSAppUsesNonExemptEncryption": false,
"CFBundleLocalizations": [
"en",
"cs",
"da",
"de",
"es",
"et",
"fi",
"fr",
"hr",
"hu",
"id",
"it",
"ja",
"nb",
"nl",
"pl",
"pt-BR",
"pt-PT",
"ro",
"ru",
"sk",
"sv",
"tr",
"uk",
"vi",
"zh-Hans",
"zh-Hant"
],
"CFBundleDevelopmentRegion": "en"
},
"target": ["dmg", "zip"]
},
"win": {
"electronUpdaterCompatibility": ">=0.0.1",
"target": ["portable", "nsis-web", "appx"],
"sign": "./sign.js",
"extraResources": [
{ "from": "node_modules/regedit/vbs", "to": "regedit/vbs", "filter": ["**/*"] },
{ "from": "resources/native-messaging.bat", "to": "native-messaging.bat" }
]
},
"linux": {
"category": "Utility",
"synopsis": "A secure and free password manager for all of your devices.",
"target": ["deb", "freebsd", "rpm", "AppImage", "snap"],
"desktop": { "Name": "Bitwarden", "Type": "Application", "GenericName": "Password Manager" }
},
"dmg": {
"icon": "dmg.icns",
"contents": [
{ "x": 150, "y": 185, "type": "file" },
{ "x": 390, "y": 180, "type": "link", "path": "/Applications" }
],
"window": { "width": 540, "height": 380 }
},
"mas": {
"entitlements": "resources/entitlements.mas.plist",
"entitlementsInherit": "resources/entitlements.mas.inherit.plist",
"hardenedRuntime": false,
"extendInfo": { "LSMinimumSystemVersion": "10.14.0" }
},
"nsisWeb": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": false,
"artifactName": "${productName}-Installer-${version}.${ext}",
"uninstallDisplayName": "${productName}",
"deleteAppDataOnUninstall": true
},
"portable": { "artifactName": "${productName}-Portable-${version}.${ext}" },
"appx": {
"artifactName": "${productName}-${version}-${arch}.${ext}",
"backgroundColor": "#175DDC",
"applicationId": "bitwardendesktop",
"identityName": "8bitSolutionsLLC.bitwardendesktop",
"publisher": "CN=14D52771-DE3C-4886-B8BF-825BA7690418",
"publisherDisplayName": "8bit Solutions LLC",
"languages": ["en-US"]
},
"deb": {
"artifactName": "${productName}-${version}-${arch}.${ext}",
"depends": ["libnotify4", "libxtst6", "libnss3", "libsecret-1-0", "libxss1"]
},
"appImage": {
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"rpm": { "artifactName": "${productName}-${version}-${arch}.${ext}" },
"freebsd": { "artifactName": "${productName}-${version}-${arch}.${ext}" },
"snap": {
"autoStart": true,
"confinement": "strict",
"plugs": ["default", "password-manager-service"],
"stagePackages": ["default"]
},
"protocols": [{ "name": "Bitwarden", "schemes": ["bitwarden"] }]
}

2
jslib

@ -1 +1 @@
Subproject commit f4c66b2c8c243935bf25f689b16afaa5d6345f1b
Subproject commit 80c834b52a8b00f88250a47a9bbd40c269fc2cba

17753
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,5 @@
{
"name": "bitwarden",
"productName": "Bitwarden",
"name": "@bitwarden/desktop",
"description": "A secure and free password manager for all of your devices.",
"version": "0.0.0",
"keywords": [
@ -27,8 +26,8 @@
"symlink:win": "rm -rf ./jslib && cmd /c mklink /J .\\jslib ..\\jslib",
"symlink:mac": "npm run symlink:lin",
"symlink:lin": "rm -rf ./jslib && ln -s ../jslib ./jslib",
"lint": "tslint 'src/**/*.ts'",
"lint:fix": "tslint 'src/**/*.ts' --fix",
"lint": "eslint . && prettier --check .",
"lint:fix": "eslint . --fix",
"build": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\"",
"build:dev": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main:dev\" \"npm run build:renderer:dev\"",
"build:main": "cross-env NODE_ENV=production webpack --config webpack.main.js",
@ -37,6 +36,7 @@
"build:renderer:dev": "cross-env NODE_ENV=development webpack --config webpack.renderer.js",
"build:renderer:watch": "cross-env NODE_ENV=development webpack --config webpack.renderer.js --watch",
"electron": "npm run build:main:dev && concurrently -k -n Main,Rend -c yellow,cyan \"electron --inspect=5858 ./build --watch\" \"npm run build:renderer:watch\"",
"electron:ignore": "npm run build:main:dev && concurrently -k -n Main,Rend -c yellow,cyan \"electron --inspect=5858 --ignore-certificate-errors ./build --watch\" \"npm run build:renderer:watch\"",
"clean:dist": "rimraf ./dist/*",
"clean:l10n": "git push origin --delete l10n_master",
"pack:dir": "npm run clean:dist && electron-builder --dir -p never",
@ -58,241 +58,79 @@
"publish:mac": "npm run build && npm run clean:dist && electron-builder --mac -p always",
"publish:mac:mas": "npm run dist:mac:mas && npm run upload:mas",
"publish:win": "npm run build && npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p always -c.win.certificateSubjectName=\"8bit Solutions LLC\"",
"upload:mas": "xcrun altool --upload-app --type osx --file \"$(find ./dist/mas-universal/Bitwarden*.pkg)\" --username $APPLE_ID_USERNAME --password $APPLE_ID_PASSWORD"
},
"build": {
"appId": "com.bitwarden.desktop",
"buildDependenciesFromSource": true,
"copyright": "Copyright © 2015-2021 Bitwarden Inc.",
"directories": {
"buildResources": "resources",
"output": "dist",
"app": "build"
},
"afterSign": "scripts/after-sign.js",
"mac": {
"electronUpdaterCompatibility": ">=0.0.1",
"category": "public.app-category.productivity",
"darkModeSupport": true,
"gatekeeperAssess": false,
"hardenedRuntime": true,
"entitlements": "resources/entitlements.mac.plist",
"entitlementsInherit": "resources/entitlements.mac.plist",
"extendInfo": {
"ITSAppUsesNonExemptEncryption": false,
"CFBundleLocalizations": [
"en",
"cs",
"da",
"de",
"es",
"et",
"fi",
"fr",
"hr",
"hu",
"id",
"it",
"ja",
"nb",
"nl",
"pl",
"pt-BR",
"pt-PT",
"ro",
"ru",
"sk",
"sv",
"tr",
"uk",
"vi",
"zh-Hans",
"zh-Hant"
],
"CFBundleDevelopmentRegion": "en"
},
"target": [
"dmg",
"zip"
]
},
"win": {
"electronUpdaterCompatibility": ">=0.0.1",
"target": [
"portable",
"nsis-web",
"appx"
],
"sign": "./sign.js",
"extraResources": [
{
"from": "node_modules/regedit/vbs",
"to": "regedit/vbs",
"filter": [
"**/*"
]
},
{
"from": "resources/native-messaging.bat",
"to": "native-messaging.bat"
}
]
},
"linux": {
"category": "Utility",
"synopsis": "A secure and free password manager for all of your devices.",
"target": [
"deb",
"freebsd",
"rpm",
"AppImage",
"snap"
],
"desktop": {
"Name": "Bitwarden",
"Type": "Application",
"GenericName": "Password Manager"
}
},
"dmg": {
"icon": "dmg.icns",
"contents": [
{
"x": 150,
"y": 185,
"type": "file"
},
{
"x": 390,
"y": 180,
"type": "link",
"path": "/Applications"
}
],
"window": {
"width": 540,
"height": 380
}
},
"mas": {
"entitlements": "resources/entitlements.mas.plist",
"entitlementsInherit": "resources/entitlements.mas.inherit.plist",
"hardenedRuntime": false,
"asarUnpack": [
"node_modules/keytar"
]
},
"nsisWeb": {
"oneClick": false,
"perMachine": true,
"allowToChangeInstallationDirectory": true,
"artifactName": "${productName}-Installer-${version}.${ext}",
"uninstallDisplayName": "${productName}",
"deleteAppDataOnUninstall": true
},
"portable": {
"artifactName": "${productName}-Portable-${version}.${ext}"
},
"appx": {
"artifactName": "${productName}-${version}-${arch}.${ext}",
"backgroundColor": "#175DDC",
"applicationId": "bitwardendesktop",
"identityName": "8bitSolutionsLLC.bitwardendesktop",
"publisher": "CN=14D52771-DE3C-4886-B8BF-825BA7690418",
"publisherDisplayName": "8bit Solutions LLC",
"languages": [
"en-US"
]
},
"deb": {
"artifactName": "${productName}-${version}-${arch}.${ext}",
"depends": [
"libnotify4",
"libxtst6",
"libnss3",
"libsecret-1-0",
"libxss1"
]
},
"appImage": {
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"rpm": {
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"freebsd": {
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"snap": {
"autoStart": true,
"confinement": "strict",
"plugs": [
"default",
"password-manager-service"
],
"stagePackages": [
"default"
],
"publish": [
"github"
]
},
"protocols": [
{
"name": "Bitwarden",
"schemes": [
"bitwarden"
]
}
]
"publish:win:dev": "npm run build && npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p always",
"upload:mas": "xcrun altool --upload-app --type osx --file \"$(find ./dist/mas-universal/Bitwarden*.pkg)\" --username $APPLE_ID_USERNAME --password $APPLE_ID_PASSWORD",
"prettier": "prettier --write .",
"prepare": "husky install"
},
"devDependencies": {
"@angular/compiler-cli": "^11.2.10",
"@ngtools/webpack": "^11.2.10",
"@types/node": "^14.14.43",
"@angular/compiler-cli": "^12.2.13",
"@ngtools/webpack": "^12.2.13",
"@types/node": "^16.11.12",
"@types/node-ipc": "^9.1.4",
"clean-webpack-plugin": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^5.12.1",
"@typescript-eslint/parser": "^5.12.1",
"clean-webpack-plugin": "^4.0.0",
"concurrently": "^6.0.2",
"copy-webpack-plugin": "^6.4.0",
"copy-webpack-plugin": "^10.0.0",
"cross-env": "^7.0.3",
"css-loader": "^5.2.4",
"del": "^6.0.0",
"css-loader": "^6.5.1",
"electron-builder": "22.11.7",
"electron-notarize": "^1.1.1",
"electron-rebuild": "^3.2.3",
"electron-rebuild": "^3.2.5",
"electron-reload": "^1.5.0",
"file-loader": "^6.2.0",
"font-awesome": "4.7.0",
"html-loader": "^1.3.2",
"html-webpack-plugin": "^4.5.1",
"mini-css-extract-plugin": "^1.5.0",
"node-loader": "^1.0.3",
"eslint": "^8.9.0",
"eslint-config-prettier": "^8.4.0",
"eslint-import-resolver-typescript": "^2.5.0",
"eslint-plugin-import": "^2.25.4",
"html-loader": "^3.0.1",
"html-webpack-plugin": "^5.5.0",
"husky": "^7.0.4",
"lint-staged": "^12.1.3",
"mini-css-extract-plugin": "^2.4.5",
"node-loader": "^2.0.0",
"prettier": "^2.5.1",
"rimraf": "^3.0.2",
"sass": "^1.32.11",
"sass-loader": "^10.1.1",
"sass-loader": "^12.4.0",
"tapable": "^1.1.3",
"ts-loader": "^8.1.0",
"ts-loader": "^9.2.5",
"tsconfig-paths-webpack-plugin": "^3.5.1",
"tslint": "~6.1.0",
"tslint-loader": "^3.5.4",
"typescript": "4.1.5",
"webpack": "^4.46.0",
"webpack-cli": "^4.6.0",
"webpack-merge": "^5.7.3",
"webpack-node-externals": "^3.0.0"
"typescript": "4.3.5",
"webpack": "^5.64.4",
"webpack-cli": "^4.9.1",
"webpack-merge": "^5.8.0"
},
"dependencies": {
"@angular/animations": "^12.2.13",
"@angular/cdk": "^12.2.13",
"@angular/common": "^12.2.13",
"@angular/compiler": "^12.2.13",
"@angular/core": "^12.2.13",
"@angular/forms": "^12.2.13",
"@angular/platform-browser": "^12.2.13",
"@angular/platform-browser-dynamic": "^12.2.13",
"@angular/router": "^12.2.13",
"@bitwarden/jslib-angular": "file:jslib/angular",
"@bitwarden/jslib-common": "file:jslib/common",
"@bitwarden/jslib-electron": "file:jslib/electron",
"angular2-toaster": "^11.0.1",
"@nodert-win10-rs4/windows.security.credentials.ui": "^0.4.4",
"forcefocus": "^1.1.0",
"keytar": "^7.9.0",
"ngx-toastr": "14.1.4",
"node-ipc": "^9.1.4",
"nord": "^0.2.1",
"regedit": "^3.0.3",
"sweetalert2": "^10.16.6"
"rxjs": "^7.4.0",
"sweetalert2": "^10.16.6",
"zone.js": "0.11.4"
},
"engines": {
"node": "~14",
"npm": "~7"
"node": "~16",
"npm": "~8"
},
"lint-staged": {
"./!(jslib)**": "prettier --ignore-unknown --write",
"*.ts": "eslint --fix"
}
}

View File

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 717 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 KiB

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 B

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -1,53 +1,62 @@
require('dotenv').config();
const path = require('path');
const fse = require('fs-extra');
const { notarize } = require('electron-notarize');
const { deepAssign } = require('builder-util');
/* eslint-disable @typescript-eslint/no-var-requires, no-console */
require("dotenv").config();
const path = require("path");
const { deepAssign } = require("builder-util");
const { notarize } = require("electron-notarize");
const fse = require("fs-extra");
exports.default = run;
async function run(context) {
console.log('## After sign');
// console.log(context);
console.log("## After sign");
// console.log(context);
const appName = context.packager.appInfo.productFilename;
const appPath = `${context.appOutDir}/${appName}.app`;
const macBuild = context.electronPlatformName === 'darwin';
const copyPlugIn = ['darwin', 'mas'].includes(context.electronPlatformName);
const appName = context.packager.appInfo.productFilename;
const appPath = `${context.appOutDir}/${appName}.app`;
const macBuild = context.electronPlatformName === "darwin";
const copyPlugIn = ["darwin", "mas"].includes(context.electronPlatformName);
if (copyPlugIn) {
// Copy Safari plugin to work-around https://github.com/electron-userland/electron-builder/issues/5552
const plugIn = path.join(__dirname, '../PlugIns');
if (fse.existsSync(plugIn)) {
fse.mkdirSync(path.join(appPath, 'Contents/PlugIns'));
fse.copySync(path.join(plugIn, 'safari.appex'), path.join(appPath, 'Contents/PlugIns/safari.appex'));
if (copyPlugIn) {
// Copy Safari plugin to work-around https://github.com/electron-userland/electron-builder/issues/5552
const plugIn = path.join(__dirname, "../PlugIns");
if (fse.existsSync(plugIn)) {
fse.mkdirSync(path.join(appPath, "Contents/PlugIns"));
fse.copySync(
path.join(plugIn, "safari.appex"),
path.join(appPath, "Contents/PlugIns/safari.appex")
);
// Resign to sign safari extension
if (context.electronPlatformName === 'mas') {
const masBuildOptions = deepAssign({}, context.packager.platformSpecificBuildOptions, context.packager.config.mas);
if (context.targets.some(e => e.name === 'mas-dev')) {
deepAssign(masBuildOptions, {
type: 'development',
});
}
if (context.packager.packagerOptions.prepackaged == null) {
await context.packager.sign(appPath, context.appOutDir, masBuildOptions, context.arch);
}
} else {
await context.packager.signApp(context, true);
}
// Resign to sign safari extension
if (context.electronPlatformName === "mas") {
const masBuildOptions = deepAssign(
{},
context.packager.platformSpecificBuildOptions,
context.packager.config.mas
);
if (context.targets.some((e) => e.name === "mas-dev")) {
deepAssign(masBuildOptions, {
type: "development",
});
}
if (context.packager.packagerOptions.prepackaged == null) {
await context.packager.sign(appPath, context.appOutDir, masBuildOptions, context.arch);
}
} else {
await context.packager.signApp(context, true);
}
}
}
if (macBuild) {
console.log('### Notarizing ' + appPath);
const appleId = process.env.APPLE_ID_USERNAME || process.env.APPLEID;
const appleIdPassword = process.env.APPLE_ID_PASSWORD || `@keychain:AC_PASSWORD`;
return await notarize({
appBundleId: 'com.bitwarden.desktop',
appPath: appPath,
appleId: appleId,
appleIdPassword: appleIdPassword,
});
}
if (macBuild) {
console.log("### Notarizing " + appPath);
const appleId = process.env.APPLE_ID_USERNAME || process.env.APPLEID;
const appleIdPassword = process.env.APPLE_ID_PASSWORD || `@keychain:AC_PASSWORD`;
return await notarize({
appBundleId: "com.bitwarden.desktop",
appPath: appPath,
appleId: appleId,
appleIdPassword: appleIdPassword,
});
}
}

1
scripts/dev/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
data

View File

@ -0,0 +1,19 @@
version: "3"
services:
minio:
image: minio/minio
command: server /data --console-address ":9001"
ports:
- "9000:9000"
- "9001:9001"
# environment:
# MINIO_ROOT_USER: minioadmin
# MINIO_ROOT_PASSWORD: minioadmin
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
volumes:
- ./data:/data

31
sign.js
View File

@ -1,22 +1,21 @@
exports.default = async function(configuration) {
if (
parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 &&
configuration.path.slice(-4) == ".exe"
) {
console.log(`[*] Signing file: ${configuration.path}`)
/* eslint-disable @typescript-eslint/no-var-requires, no-console */
exports.default = async function (configuration) {
if (parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 && configuration.path.slice(-4) == ".exe") {
console.log(`[*] Signing file: ${configuration.path}`);
require("child_process").execSync(
`azuresigntool sign -v ` +
`-kvu ${process.env.SIGNING_VAULT_URL} ` +
`-kvi ${process.env.SIGNING_CLIENT_ID} ` +
`-kvt ${process.env.SIGNING_TENANT_ID} ` +
`-kvs ${process.env.SIGNING_CLIENT_SECRET} ` +
`-kvc ${process.env.SIGNING_CERT_NAME} ` +
`-fd ${configuration.hash} ` +
`-du ${configuration.site} ` +
`-tr http://timestamp.digicert.com ` +
`${configuration.path}`,
`-kvu ${process.env.SIGNING_VAULT_URL} ` +
`-kvi ${process.env.SIGNING_CLIENT_ID} ` +
`-kvt ${process.env.SIGNING_TENANT_ID} ` +
`-kvs ${process.env.SIGNING_CLIENT_SECRET} ` +
`-kvc ${process.env.SIGNING_CERT_NAME} ` +
`-fd ${configuration.hash} ` +
`-du ${configuration.site} ` +
`-tr http://timestamp.digicert.com ` +
`${configuration.path}`,
{
stdio: "inherit"
stdio: "inherit",
}
);
}

View File

@ -1,64 +1,93 @@
<div class="modal fade" role="dialog" aria-modal="true" attr.aria-label="{{'settings' | i18n}}">
<div class="modal-dialog" role="document">
<form class="modal-content" (ngSubmit)="submit()">
<div class="modal-body">
<div class="box">
<div class="box-header">
{{'selfHostedEnvironment' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="baseUrl">{{'baseUrl' | i18n}}</label>
<input id="baseUrl" type="text" name="BaseUrl" [(ngModel)]="baseUrl"
placeholder="{{'ex' | i18n}} https://bitwarden.company.com">
</div>
</div>
<div class="box-footer">
{{'selfHostedEnvironmentFooter' | i18n}}
</div>
</div>
<div class="box">
<div class="box-header">
<button type="button" (click)="toggleCustom()" appA11yTitle="{{'toggleVisibility' | i18n}}">
<i class="fa fa-plus-square-o" [hidden]="showCustom" aria-hidden="true"></i>
<i class="fa fa-minus-square-o" [hidden]="!showCustom" aria-hidden="true"></i>
{{'customEnvironment' | i18n}}
</button>
</div>
<div class="box-content" [hidden]="!showCustom">
<div class="box-content-row" appBoxRow>
<label for="webVaultUrl">{{'webVaultUrl' | i18n}}</label>
<input id="webVaultUrl" type="text" name="WebVaultUrl" [(ngModel)]="webVaultUrl">
</div>
<div class="box-content-row" appBoxRow>
<label for="apiUrl">{{'apiUrl' | i18n}}</label>
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl">
</div>
<div class="box-content-row" appBoxRow>
<label for="identityUrl">{{'identityUrl' | i18n}}</label>
<input id="identityUrl" type="text" name="IdentityUrl" [(ngModel)]="identityUrl">
</div>
<div class="box-content-row" appBoxRow>
<label for="notificationsUrl">{{'notificationsUrl' | i18n}}</label>
<input id="notificationsUrl" type="text" name="NotificationsUrl"
[(ngModel)]="notificationsUrl">
</div>
<div class="box-content-row" appBoxRow>
<label for="iconsUrl">{{'iconsUrl' | i18n}}</label>
<input id="iconsUrl" type="text" name="IconsUrl" [(ngModel)]="iconsUrl">
</div>
</div>
<div class="box-footer" [hidden]="!showCustom">
{{'customEnvironmentFooter' | i18n}}
</div>
</div>
<div class="modal fade" role="dialog" aria-modal="true" attr.aria-label="{{ 'settings' | i18n }}">
<div class="modal-dialog" role="document">
<form class="modal-content" (ngSubmit)="submit()">
<div class="modal-body">
<div class="box">
<div class="box-header">
{{ "selfHostedEnvironment" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="baseUrl">{{ "baseUrl" | i18n }}</label>
<input
id="baseUrl"
type="text"
name="BaseUrl"
[(ngModel)]="baseUrl"
placeholder="{{ 'ex' | i18n }} https://bitwarden.company.com"
appInputVerbatim
/>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}">
<i class="fa fa-save fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
<div class="box-footer">
{{ "selfHostedEnvironmentFooter" | i18n }}
</div>
</div>
<div class="box">
<div class="box-header">
<button type="button" (click)="toggleCustom()" [attr.aria-expanded]="showCustom">
<i class="bwi bwi-plus-square" [hidden]="showCustom" aria-hidden="true"></i>
<i class="bwi bwi-minus-square" [hidden]="!showCustom" aria-hidden="true"></i>
{{ "customEnvironment" | i18n }}
</button>
</div>
<div class="box-content" [hidden]="!showCustom">
<div class="box-content-row" appBoxRow>
<label for="webVaultUrl">{{ "webVaultUrl" | i18n }}</label>
<input
id="webVaultUrl"
type="text"
name="WebVaultUrl"
[(ngModel)]="webVaultUrl"
appInputVerbatim
/>
</div>
</form>
</div>
<div class="box-content-row" appBoxRow>
<label for="apiUrl">{{ "apiUrl" | i18n }}</label>
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl" appInputVerbatim />
</div>
<div class="box-content-row" appBoxRow>
<label for="identityUrl">{{ "identityUrl" | i18n }}</label>
<input
id="identityUrl"
type="text"
name="IdentityUrl"
[(ngModel)]="identityUrl"
appInputVerbatim
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="notificationsUrl">{{ "notificationsUrl" | i18n }}</label>
<input
id="notificationsUrl"
type="text"
name="NotificationsUrl"
[(ngModel)]="notificationsUrl"
appInputVerbatim
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="iconsUrl">{{ "iconsUrl" | i18n }}</label>
<input
id="iconsUrl"
type="text"
name="IconsUrl"
[(ngModel)]="iconsUrl"
appInputVerbatim
/>
</div>
</div>
<div class="box-footer" [hidden]="!showCustom">
{{ "customEnvironmentFooter" | i18n }}
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="primary" appA11yTitle="{{ 'save' | i18n }}">
<i class="bwi bwi-save-changes bwi-lg bwi-fw" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div>
</form>
</div>
</div>

View File

@ -1,18 +1,20 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { EnvironmentComponent as BaseEnvironmentComponent } from 'jslib-angular/components/environment.component';
import { EnvironmentComponent as BaseEnvironmentComponent } from "jslib-angular/components/environment.component";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
@Component({
selector: 'app-environment',
templateUrl: 'environment.component.html',
selector: "app-environment",
templateUrl: "environment.component.html",
})
export class EnvironmentComponent extends BaseEnvironmentComponent {
constructor(platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
i18nService: I18nService) {
super(platformUtilsService, environmentService, i18nService);
}
constructor(
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
i18nService: I18nService
) {
super(platformUtilsService, environmentService, i18nService);
}
}

View File

@ -1,23 +1,31 @@
<form id="hint-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<h1>{{'passwordHint' | i18n}}</h1>
<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 appAutofocus>
</div>
</div>
<div class="box-footer">
{{'enterEmailToGetHint' | i18n}}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{'submit' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{'cancel' | i18n}}</a>
<div class="content">
<h1>{{ "passwordHint" | i18n }}</h1>
<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
appAutofocus
appInputVerbatim
/>
</div>
</div>
<div class="box-footer">
{{ "enterEmailToGetHint" | i18n }}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<b [hidden]="form.loading">{{ "submit" | i18n }}</b>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button type="button" routerLink="/login" class="btn block">{{ "cancel" | i18n }}</button>
</div>
</div>
</form>

View File

@ -1,20 +1,24 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Component } from "@angular/core";
import { Router } from "@angular/router";
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 { HintComponent as BaseHintComponent } from 'jslib-angular/components/hint.component';
import { HintComponent as BaseHintComponent } from "jslib-angular/components/hint.component";
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";
@Component({
selector: 'app-hint',
templateUrl: 'hint.component.html',
selector: "app-hint",
templateUrl: "hint.component.html",
})
export class HintComponent extends BaseHintComponent {
constructor(router: Router, platformUtilsService: PlatformUtilsService,
i18nService: I18nService, apiService: ApiService, logService: LogService) {
super(router, i18nService, apiService, platformUtilsService, logService);
}
constructor(
router: Router,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
apiService: ApiService,
logService: LogService
) {
super(router, i18nService, apiService, platformUtilsService, logService);
}
}

View File

@ -1,48 +1,75 @@
<form id="lock-page" (ngSubmit)="submit()">
<div class="content">
<p aria-hidden="true"><i class="fa fa-lock fa-4x text-muted"></i></p>
<p>{{'yourVaultIsLocked' | i18n}}</p>
<div class="box last">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow *ngIf="!hideInput">
<div class="row-main" *ngIf="pinLock">
<label for="pin">{{'pin' | i18n}}</label>
<input id="pin" type="{{showPassword ? 'text' : 'password'}}" name="PIN" class="monospaced"
[(ngModel)]="pin" required>
</div>
<div class="row-main" *ngIf="!pinLock">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword"
class="monospaced" [(ngModel)]="masterPassword" required>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
</div>
<div class="box-footer">
{{'loggedInAsOn' | i18n : email : webVaultHostname}}
</div>
</div>
<div class="buttons with-rows">
<div class="buttons-row" *ngIf="supportsBiometric && biometricLock">
<button type="button" class="btn block" [ngClass]="{'primary font-weight-bold': hideInput}"
appBlurClick (click)="unlockBiometric()">
{{biometricText | i18n}}
</button>
</div>
<div class="buttons-row">
<button type="submit" class="btn primary block" appBlurClick *ngIf="!hideInput">
<i class="fa fa-unlock-alt" aria-hidden="true"></i> <b>{{'unlock' | i18n}}</b>
</button>
<button type="button" class="btn block" appBlurClick (click)="logOut()">
{{'logOut' | i18n}}
</button>
</div>
<div class="content">
<p aria-hidden="true"><i class="bwi bwi-lock bwi-4x text-muted"></i></p>
<p>{{ "yourVaultIsLocked" | i18n }}</p>
<div class="box last">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow *ngIf="!hideInput">
<div class="row-main" *ngIf="pinLock">
<label for="pin">{{ "pin" | i18n }}</label>
<input
id="pin"
type="{{ showPassword ? 'text' : 'password' }}"
name="PIN"
class="monospaced"
[(ngModel)]="pin"
required
appInputVerbatim
/>
</div>
<div class="row-main" *ngIf="!pinLock">
<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">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword()"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
</div>
<div class="box-footer">
{{ "loggedInAsOn" | i18n: email:webVaultHostname }}
</div>
</div>
<div class="buttons with-rows">
<div class="buttons-row" *ngIf="supportsBiometric && biometricLock">
<button
type="button"
class="btn block"
[ngClass]="{ 'primary font-weight-bold': hideInput }"
(click)="unlockBiometric()"
>
{{ biometricText | i18n }}
</button>
</div>
<div class="buttons-row">
<button type="submit" class="btn primary block" *ngIf="!hideInput">
<i class="bwi bwi-unlock" aria-hidden="true"></i> <b>{{ "unlock" | i18n }}</b>
</button>
<button type="button" class="btn block" (click)="logOut()">
{{ "logOut" | i18n }}
</button>
</div>
</div>
</div>
</form>

View File

@ -1,101 +1,107 @@
import {
Component,
NgZone,
OnDestroy,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ipcRenderer } from 'electron';
import { Component, NgZone, OnDestroy } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { ipcRenderer } from "electron";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.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 { StorageService } from 'jslib-common/abstractions/storage.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { LockComponent as BaseLockComponent } from "jslib-angular/components/lock.component";
import { ApiService } from "jslib-common/abstractions/api.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.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 { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
import { LockComponent as BaseLockComponent } from 'jslib-angular/components/lock.component';
import { ConstantsService } from 'jslib-common/services/constants.service';
const BroadcasterSubscriptionId = 'LockComponent';
const BroadcasterSubscriptionId = "LockComponent";
@Component({
selector: 'app-lock',
templateUrl: 'lock.component.html',
selector: "app-lock",
templateUrl: "lock.component.html",
})
export class LockComponent extends BaseLockComponent implements OnDestroy {
private deferFocus: boolean = null;
private deferFocus: boolean = null;
constructor(router: Router, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, messagingService: MessagingService,
userService: UserService, cryptoService: CryptoService,
storageService: StorageService, vaultTimeoutService: VaultTimeoutService,
environmentService: EnvironmentService, stateService: StateService,
apiService: ApiService, private route: ActivatedRoute,
private broadcasterService: BroadcasterService, private ngZone: NgZone,
logService: LogService, keyConnectorService: KeyConnectorService) {
super(router, i18nService, platformUtilsService, messagingService, userService, cryptoService,
storageService, vaultTimeoutService, environmentService, stateService, apiService, logService,
keyConnectorService);
}
constructor(
router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
messagingService: MessagingService,
cryptoService: CryptoService,
vaultTimeoutService: VaultTimeoutService,
environmentService: EnvironmentService,
stateService: StateService,
apiService: ApiService,
private route: ActivatedRoute,
private broadcasterService: BroadcasterService,
ngZone: NgZone,
logService: LogService,
keyConnectorService: KeyConnectorService
) {
super(
router,
i18nService,
platformUtilsService,
messagingService,
cryptoService,
vaultTimeoutService,
environmentService,
stateService,
apiService,
logService,
keyConnectorService,
ngZone
);
}
async ngOnInit() {
await super.ngOnInit();
const autoPromptBiometric = !await this.storageService.get<boolean>(ConstantsService.disableAutoBiometricsPromptKey);
async ngOnInit() {
await super.ngOnInit();
const autoPromptBiometric = !(await this.stateService.getNoAutoPromptBiometrics());
this.route.queryParams.subscribe(params => {
if (this.supportsBiometric && params.promptBiometric && autoPromptBiometric) {
setTimeout(async() => {
if (await ipcRenderer.invoke('windowVisible')) {
this.unlockBiometric();
}
}, 1000);
this.route.queryParams.subscribe((params) => {
if (this.supportsBiometric && params.promptBiometric && autoPromptBiometric) {
setTimeout(async () => {
if (await ipcRenderer.invoke("windowVisible")) {
this.unlockBiometric();
}
}, 1000);
}
});
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
case "windowIsFocused":
if (this.deferFocus === null) {
this.deferFocus = !message.windowIsFocused;
if (!this.deferFocus) {
this.focusInput();
}
} else if (this.deferFocus && message.windowIsFocused) {
this.focusInput();
this.deferFocus = false;
}
});
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case 'windowHidden':
this.onWindowHidden();
break;
case 'windowIsFocused':
if (this.deferFocus === null) {
this.deferFocus = !message.windowIsFocused;
if (!this.deferFocus) {
this.focusInput();
}
} else if (this.deferFocus && message.windowIsFocused) {
this.focusInput();
this.deferFocus = false;
}
break;
default:
}
});
});
this.messagingService.send('getWindowIsFocused');
}
break;
default:
}
});
});
this.messagingService.send("getWindowIsFocused");
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
onWindowHidden() {
this.showPassword = false;
}
onWindowHidden() {
this.showPassword = false;
}
private focusInput() {
document.getElementById(this.pinLock ? 'pin' : 'masterPassword').focus();
}
private focusInput() {
document.getElementById(this.pinLock ? "pin" : "masterPassword").focus();
}
}

View File

@ -1,56 +1,104 @@
<form id="login-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise" attr.aria-hidden="{{showingModal}}">
<div id="login-page">
<div class="login-header">
<button
type="button"
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 }}
</button>
</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>
</div>
<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>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="box-content-row" [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
<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>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
</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="fa fa-sign-in" aria-hidden="true"></i> {{'logIn' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/register" class="btn block">
<i class="fa fa-pencil-square-o" aria-hidden="true"></i> {{'createAccount' | i18n}}
</a>
</div>
<div class="buttons-row">
<a (click)="launchSsoBrowser('desktop', 'bitwarden://sso-callback')" class="btn block">
<i class="fa fa-bank" aria-hidden="true"></i> {{'enterpriseSingleSignOn' | i18n}}
</a>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword()"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
</div>
<div class="sub-options">
<a routerLink="/hint">{{'getMasterPasswordHint' | i18n}}</a>
</div>
<div class="box last" [hidden]="!showCaptcha()">
<div class="box-content">
<div class="box-content-row">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
</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>
<div class="buttons with-rows">
<div class="buttons-row">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<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>
<button type="button" routerLink="/register" class="btn block">
<i class="bwi bwi-pencil-square" aria-hidden="true"></i> {{ "createAccount" | i18n }}
</button>
</div>
<div class="buttons-row">
<button
type="button"
(click)="launchSsoBrowser('desktop', 'bitwarden://sso-callback')"
class="btn block"
>
<i class="bwi bwi-bank" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | i18n }}
</button>
</div>
</div>
<div class="sub-options">
<button type="button" routerLink="/hint">{{ "getMasterPasswordHint" | i18n }}</button>
</div>
</div>
</form>
</form>
</div>
<ng-template #environment></ng-template>

View File

@ -1,113 +1,128 @@
import {
Component,
NgZone,
OnDestroy,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { Component, NgZone, OnDestroy, ViewChild, ViewContainerRef } from "@angular/core";
import { Router } from "@angular/router";
import { Router } from '@angular/router';
import { LoginComponent as BaseLoginComponent } from "jslib-angular/components/login.component";
import { ModalService } from "jslib-angular/services/modal.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.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";
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 { SyncService } from "jslib-common/abstractions/sync.service";
import { EnvironmentComponent } from './environment.component';
import { EnvironmentComponent } from "./environment.component";
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.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';
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';
import { ModalService } from 'jslib-angular/services/modal.service';
import { LoginComponent as BaseLoginComponent } from 'jslib-angular/components/login.component';
const BroadcasterSubscriptionId = 'LoginComponent';
const BroadcasterSubscriptionId = "LoginComponent";
@Component({
selector: 'app-login',
templateUrl: 'login.component.html',
selector: "app-login",
templateUrl: "login.component.html",
})
export class LoginComponent extends BaseLoginComponent implements OnDestroy {
@ViewChild('environment', { read: ViewContainerRef, static: true }) environmentModal: ViewContainerRef;
@ViewChild("environment", { read: ViewContainerRef, static: true })
environmentModal: ViewContainerRef;
showingModal = false;
showingModal = false;
private deferFocus: boolean = null;
protected alwaysRememberEmail = true;
constructor(authService: AuthService, router: Router, i18nService: I18nService,
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) {
super(authService, router, platformUtilsService, i18nService, stateService, environmentService,
passwordGenerationService, cryptoFunctionService, storageService, logService);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
}
private deferFocus: boolean = null;
async ngOnInit() {
await super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case 'windowHidden':
this.onWindowHidden();
break;
case 'windowIsFocused':
if (this.deferFocus === null) {
this.deferFocus = !message.windowIsFocused;
if (!this.deferFocus) {
this.focusInput();
}
} else if (this.deferFocus && message.windowIsFocused) {
this.focusInput();
this.deferFocus = false;
}
break;
default:
}
});
});
this.messagingService.send('getWindowIsFocused');
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
syncService: SyncService,
private modalService: ModalService,
platformUtilsService: PlatformUtilsService,
stateService: StateService,
environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService,
cryptoFunctionService: CryptoFunctionService,
private broadcasterService: BroadcasterService,
ngZone: NgZone,
private messagingService: MessagingService,
logService: LogService
) {
super(
authService,
router,
platformUtilsService,
i18nService,
stateService,
environmentService,
passwordGenerationService,
cryptoFunctionService,
logService,
ngZone
);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async settings() {
const [modal, childComponent] = await this.modalService.openViewRef(EnvironmentComponent, this.environmentModal);
modal.onShown.subscribe(() => {
this.showingModal = true;
});
modal.onClosed.subscribe(() => {
this.showingModal = false;
});
childComponent.onSaved.subscribe(() => {
modal.close();
});
}
onWindowHidden() {
this.showPassword = false;
}
async submit() {
await super.submit();
if (this.captchaSiteKey) {
const content = document.getElementById('content') as HTMLDivElement;
content.setAttribute('style', 'width:335px');
async ngOnInit() {
await super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
case "windowIsFocused":
if (this.deferFocus === null) {
this.deferFocus = !message.windowIsFocused;
if (!this.deferFocus) {
this.focusInput();
}
} else if (this.deferFocus && message.windowIsFocused) {
this.focusInput();
this.deferFocus = false;
}
break;
default:
}
});
});
this.messagingService.send("getWindowIsFocused");
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async settings() {
const [modal, childComponent] = await this.modalService.openViewRef(
EnvironmentComponent,
this.environmentModal
);
modal.onShown.subscribe(() => {
this.showingModal = true;
});
modal.onClosed.subscribe(() => {
this.showingModal = false;
});
childComponent.onSaved.subscribe(() => {
modal.close();
});
}
onWindowHidden() {
this.showPassword = false;
}
async submit() {
await super.submit();
if (this.captchaSiteKey) {
const content = document.getElementById("content") as HTMLDivElement;
content.setAttribute("style", "width:335px");
}
}
}

View File

@ -1,70 +1,89 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="premiumTitle">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="premiumTitle">
{{'premiumMembership' | i18n}}
</div>
<div class="box-content box-content-padded">
<div *ngIf="!isPremium">
<p class="text-center lead">{{'premiumNotCurrentMember' | i18n}}</p>
<p>{{'premiumSignUpAndGet' | i18n}}</p>
<ul class="fa-ul">
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpStorage' | i18n}}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpTwoStep' | i18n}}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpReports' | i18n}}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpTotp' | i18n}}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpSupport' | i18n}}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpFuture' | i18n}}
</li>
</ul>
<p class="text-center lead no-margin">
{{'premiumPrice' | i18n : (price | currency:'$')}}
</p>
</div>
<div *ngIf="isPremium">
<p class="text-center lead">{{'premiumCurrentMember' | i18n}}</p>
<p class="text-center">{{'premiumCurrentMemberThanks' | i18n}}</p>
</div>
</div>
</div>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="premiumTitle">
{{ "premiumMembership" | i18n }}
</div>
<div class="box-content box-content-padded">
<div *ngIf="!isPremium">
<p class="text-center lead">{{ "premiumNotCurrentMember" | i18n }}</p>
<p>{{ "premiumSignUpAndGet" | i18n }}</p>
<ul class="bwi-ul">
<li>
<i class="bwi bwi-li bwi-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpStorage" | i18n }}
</li>
<li>
<i class="bwi bwi-li bwi-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpTwoStep" | i18n }}
</li>
<li>
<i class="bwi bwi-li bwi-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpReports" | i18n }}
</li>
<li>
<i class="bwi bwi-li bwi-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpTotp" | i18n }}
</li>
<li>
<i class="bwi bwi-li bwi-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpSupport" | i18n }}
</li>
<li>
<i class="bwi bwi-li bwi-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpFuture" | i18n }}
</li>
</ul>
<p class="text-center lead no-margin">
{{ "premiumPrice" | i18n: (price | currency: "$") }}
</p>
</div>
<div class="modal-footer">
<button type="button" class="primary" appBlurClick (click)="manage()" *ngIf="isPremium">
<b>{{'premiumManage' | i18n}}</b>
</button>
<button #purchaseBtn type="button" class="primary" appBlurClick (click)="purchase()" *ngIf="!isPremium"
[disabled]="purchaseBtn.loading">
<b>{{'premiumPurchase' | i18n}}</b>
</button>
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
<div class="right" *ngIf="!isPremium">
<button #refreshBtn type="button" appBlurClick (click)="refresh()" [disabled]="refreshBtn.loading"
appA11yTitle="{{'premiumRefresh' | i18n}}" [appApiAction]="refreshPromise">
<i class="fa fa-refresh fa-lg fa-fw" [hidden]="refreshBtn.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!refreshBtn.loading"
aria-hidden="true"></i>
</button>
</div>
<div *ngIf="isPremium">
<p class="text-center lead">{{ "premiumCurrentMember" | i18n }}</p>
<p class="text-center">{{ "premiumCurrentMemberThanks" | i18n }}</p>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="primary" (click)="manage()" *ngIf="isPremium">
<b>{{ "premiumManage" | i18n }}</b>
</button>
<button
#purchaseBtn
type="button"
class="primary"
(click)="purchase()"
*ngIf="!isPremium"
[disabled]="purchaseBtn.loading"
>
<b>{{ "premiumPurchase" | i18n }}</b>
</button>
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
<div class="right" *ngIf="!isPremium">
<button
#refreshBtn
type="button"
(click)="refresh()"
[disabled]="refreshBtn.loading"
appA11yTitle="{{ 'premiumRefresh' | i18n }}"
[appApiAction]="refreshPromise"
>
<i
class="bwi bwi-refresh bwi-lg bwi-fw"
[hidden]="refreshBtn.loading"
aria-hidden="true"
></i>
<i
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!refreshBtn.loading"
aria-hidden="true"
></i>
</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,21 +1,24 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { PremiumComponent as BasePremiumComponent } from 'jslib-angular/components/premium.component';
import { PremiumComponent as BasePremiumComponent } from "jslib-angular/components/premium.component";
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";
@Component({
selector: 'app-premium',
templateUrl: 'premium.component.html',
selector: "app-premium",
templateUrl: "premium.component.html",
})
export class PremiumComponent extends BasePremiumComponent {
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
apiService: ApiService, userService: UserService,
logService: LogService) {
super(i18nService, platformUtilsService, apiService, userService, logService);
}
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
logService: LogService,
stateService: StateService
) {
super(i18nService, platformUtilsService, apiService, logService, stateService);
}
}

View File

@ -1,92 +1,148 @@
<form id="register-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<h1>{{'createAccount' | i18n}}</h1>
<div class="box">
<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
[appAutofocus]="email === ''">
</div>
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{'masterPass' | i18n}}
<strong class="sub-label text-{{masterPasswordScoreColor}}"
*ngIf="masterPasswordScoreText">
{{masterPasswordScoreText}}
</strong>
</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPassword" class="monospaced" [(ngModel)]="masterPassword" required
[appAutofocus]="email !== ''" (input)="updatePasswordStrength()">
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="progress">
<div class="progress-bar bg-{{masterPasswordScoreColor}}" role="progressbar" aria-valuenow="0"
aria-valuemin="0" aria-valuemax="100" [ngStyle]="{width: (masterPasswordScoreWidth + '%')}"
attr.aria-valuenow="{{masterPasswordScoreWidth}}"></div>
</div>
</div>
</div>
<div class="box-footer">
{{'masterPassDesc' | i18n}}
</div>
<div class="content">
<h1>{{ "createAccount" | i18n }}</h1>
<div class="box">
<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
[appAutofocus]="email === ''"
appInputVerbatim
/>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPasswordRetype" class="monospaced" [(ngModel)]="confirmMasterPassword" required>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint">
</div>
<div class="box-content-row" [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{ "masterPass" | i18n }}
<strong
class="sub-label text-{{ masterPasswordScoreColor }}"
*ngIf="masterPasswordScoreText"
>
{{ masterPasswordScoreText }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
[appAutofocus]="email !== ''"
(input)="updatePasswordStrength()"
appInputVerbatim
/>
</div>
<div class="box-footer">
{{'masterPassHintDesc' | i18n}}
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword(false)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
<div class="progress">
<div
class="progress-bar bg-{{ masterPasswordScoreColor }}"
role="progressbar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
[ngStyle]="{ width: masterPasswordScoreWidth + '%' }"
attr.aria-valuenow="{{ masterPasswordScoreWidth }}"
></div>
</div>
</div>
<div class="box last" *ngIf="showTerms">
<div class="box-footer checkbox">
<input type="checkbox" id="acceptPolicies" [(ngModel)]="acceptPolicies" name="AcceptPolicies">
<label for="acceptPolicies">
{{'acceptPolicies' | i18n}}<br>
<a href="https://bitwarden.com/terms/" target="_blank"
rel="noopener">{{'termsOfService' | i18n}}</a>,
<a href="https://bitwarden.com/privacy/" target="_blank"
rel="noopener">{{'privacyPolicy' | i18n}}</a>
</label>
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{'submit' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{'cancel' | i18n}}</a>
</div>
</div>
<div class="box-footer">
{{ "masterPassDesc" | i18n }}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="confirmMasterPassword"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword(true)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint" />
</div>
<div class="box-content-row" [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
</div>
<div class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
<div class="box last" *ngIf="showTerms">
<div class="box-footer checkbox">
<input
type="checkbox"
id="acceptPolicies"
[(ngModel)]="acceptPolicies"
name="AcceptPolicies"
/>
<label for="acceptPolicies">
{{ "acceptPolicies" | i18n }}<br />
<a href="https://bitwarden.com/terms/" target="_blank" rel="noopener">{{
"termsOfService" | i18n
}}</a
>,
<a href="https://bitwarden.com/privacy/" target="_blank" rel="noopener">{{
"privacyPolicy" | i18n
}}</a>
</label>
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<b [hidden]="form.loading">{{ "submit" | i18n }}</b>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button type="button" routerLink="/login" class="btn block">{{ "cancel" | i18n }}</button>
</div>
</div>
</form>

View File

@ -1,62 +1,73 @@
import {
Component,
NgZone,
OnDestroy,
OnInit,
} from '@angular/core';
import { Router } from '@angular/router';
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.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';
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 { RegisterComponent as BaseRegisterComponent } from "jslib-angular/components/register.component";
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService } from "jslib-common/abstractions/crypto.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";
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 { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
import { RegisterComponent as BaseRegisterComponent } from 'jslib-angular/components/register.component';
const BroadcasterSubscriptionId = 'RegisterComponent';
const BroadcasterSubscriptionId = "RegisterComponent";
@Component({
selector: 'app-register',
templateUrl: 'register.component.html',
selector: "app-register",
templateUrl: "register.component.html",
})
export class RegisterComponent extends BaseRegisterComponent implements OnInit, OnDestroy {
constructor(authService: AuthService, router: Router,
i18nService: I18nService, cryptoService: CryptoService,
apiService: ApiService, stateService: StateService,
platformUtilsService: PlatformUtilsService, passwordGenerationService: PasswordGenerationService,
environmentService: EnvironmentService, private broadcasterService: BroadcasterService,
private ngZone: NgZone, logService: LogService) {
super(authService, router, i18nService, cryptoService, apiService, stateService, platformUtilsService,
passwordGenerationService, environmentService, logService);
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
cryptoService: CryptoService,
apiService: ApiService,
stateService: StateService,
platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService,
environmentService: EnvironmentService,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
logService: LogService
) {
super(
authService,
router,
i18nService,
cryptoService,
apiService,
stateService,
platformUtilsService,
passwordGenerationService,
environmentService,
logService
);
}
async ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case 'windowHidden':
this.onWindowHidden();
break;
default:
}
});
});
async ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
default:
}
});
});
super.ngOnInit();
}
super.ngOnInit();
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
onWindowHidden() {
this.showPassword = false;
}
onWindowHidden() {
this.showPassword = false;
}
}

View File

@ -1,18 +1,26 @@
<div id="remove-password-page" *ngIf="!loading">
<div class="content">
<h1>{{'removeMasterPassword' | i18n}}</h1>
<p>{{'convertOrganizationEncryptionDesc' | i18n : organization.name}}</p>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="actionPromise" appBlurClick
(click)="convert()">
<b [hidden]="continuing">{{'removeMasterPassword' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!continuing" aria-hidden="true"></i>
</button>
<button type="button" class="btn secondary block" [disabled]="actionPromise" appBlurClick
(click)="leave()">
<b [hidden]="leaving">{{'leaveOrganization' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!leaving" aria-hidden="true"></i>
</button>
</div>
<div class="content">
<h1>{{ "removeMasterPassword" | i18n }}</h1>
<p>{{ "convertOrganizationEncryptionDesc" | i18n: organization.name }}</p>
<div class="buttons">
<button
type="submit"
class="btn primary block"
[disabled]="actionPromise"
(click)="convert()"
>
<b [hidden]="continuing">{{ "removeMasterPassword" | i18n }}</b>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!continuing" aria-hidden="true"></i>
</button>
<button
type="button"
class="btn secondary block"
[disabled]="actionPromise"
(click)="leave()"
>
<b [hidden]="leaving">{{ "leaveOrganization" | i18n }}</b>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!leaving" aria-hidden="true"></i>
</button>
</div>
</div>
</div>

View File

@ -1,10 +1,9 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { RemovePasswordComponent as BaseRemovePasswordComponent } from 'jslib-angular/components/remove-password.component';
import { RemovePasswordComponent as BaseRemovePasswordComponent } from "jslib-angular/components/remove-password.component";
@Component({
selector: 'app-remove-password',
templateUrl: 'remove-password.component.html',
selector: "app-remove-password",
templateUrl: "remove-password.component.html",
})
export class RemovePasswordComponent extends BaseRemovePasswordComponent {
}
export class RemovePasswordComponent extends BaseRemovePasswordComponent {}

View File

@ -1,101 +1,157 @@
<form id="set-password-page" #form>
<div class="content">
<img class="logo-image" alt="Bitwarden">
<p class="lead">{{'setMasterPassword' | i18n}}</p>
<div class="box text-center" *ngIf="syncLoading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
{{'loading' | i18n}}
</div>
<div *ngIf="!syncLoading">
<div class="box">
<app-callout type="tip">{{'ssoCompleteRegistration' | i18n}}</app-callout>
<app-callout type="warning" title="{{'resetPasswordPolicyAutoEnroll' | i18n}}"
*ngIf="resetPasswordAutoEnroll">
{{'resetPasswordAutoEnrollInviteWarning' | i18n}}
</app-callout>
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
</app-callout>
</div>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}}
<strong class="sub-label text-{{masterPasswordScoreColor}}"
*ngIf="masterPasswordScoreText">
{{masterPasswordScoreText}}
</strong>
</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPassword" class="monospaced" [(ngModel)]="masterPassword" required
(input)="updatePasswordStrength()" appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="progress">
<div class="progress-bar bg-{{masterPasswordScoreColor}}" role="progressbar"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"
[ngStyle]="{width: (masterPasswordScoreWidth + '%')}"
attr.aria-valuenow="{{masterPasswordScoreWidth}}">
</div>
</div>
</div>
</div>
<div class="box-footer">
{{'masterPassDesc' | i18n}}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<input id="masterPasswordRetype" type="password" name="MasterPasswordRetype"
class="monospaced" [(ngModel)]="masterPasswordRetype" required appInputVerbatim
autocomplete="new-password">
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint">
</div>
</div>
<div class="box-footer">
{{'masterPassHintDesc' | i18n}}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<i *ngIf="form.loading" class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"
aria-hidden="true"></i>
<span>{{'submit' | i18n}}</span>
</button>
<button class="btn block" (click)="logOut()">
<span>{{'logOut' | i18n}}</span>
</button>
</div>
</form>
</div>
<div class="content">
<img class="logo-image" alt="Bitwarden" />
<p class="lead">{{ "setMasterPassword" | i18n }}</p>
<div class="box text-center" *ngIf="syncLoading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }}
</div>
<div *ngIf="!syncLoading">
<div class="box">
<app-callout type="tip">{{ "ssoCompleteRegistration" | i18n }}</app-callout>
<app-callout
type="warning"
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
*ngIf="resetPasswordAutoEnroll"
>
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
</app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
</div>
<form
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
autocomplete="off"
>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword"
>{{ "masterPass" | i18n }}
<strong
class="sub-label text-{{ masterPasswordScoreColor }}"
*ngIf="masterPasswordScoreText"
>
{{ masterPasswordScoreText }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
(input)="updatePasswordStrength()"
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword(false)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
<div class="progress">
<div
class="progress-bar bg-{{ masterPasswordScoreColor }}"
role="progressbar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
[ngStyle]="{ width: masterPasswordScoreWidth + '%' }"
attr.aria-valuenow="{{ masterPasswordScoreWidth }}"
></div>
</div>
</div>
</div>
<div class="box-footer">
{{ "masterPassDesc" | i18n }}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="password"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
autocomplete="new-password"
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword(true)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint" />
</div>
</div>
<div class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<i
*ngIf="form.loading"
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span>{{ "submit" | i18n }}</span>
</button>
<button class="btn block" (click)="logOut()">
<span>{{ "logOut" | i18n }}</span>
</button>
</div>
</form>
</div>
</div>
</form>

View File

@ -1,96 +1,104 @@
import {
Component,
NgZone,
OnDestroy,
} from '@angular/core';
import { Component, NgZone, OnDestroy } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { SetPasswordComponent as BaseSetPasswordComponent } from "jslib-angular/components/set-password.component";
import { ApiService } from "jslib-common/abstractions/api.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.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 { 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 { ApiService } from 'jslib-common/abstractions/api.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 { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
const BroadcasterSubscriptionId = 'SetPasswordComponent';
import {
SetPasswordComponent as BaseSetPasswordComponent,
} from 'jslib-angular/components/set-password.component';
const BroadcasterSubscriptionId = "SetPasswordComponent";
@Component({
selector: 'app-set-password',
templateUrl: 'set-password.component.html',
selector: "app-set-password",
templateUrl: "set-password.component.html",
})
export class SetPasswordComponent extends BaseSetPasswordComponent implements OnDestroy {
constructor(apiService: ApiService, i18nService: I18nService,
cryptoService: CryptoService, messagingService: MessagingService,
userService: UserService, passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService, policyService: PolicyService, router: Router,
syncService: SyncService, route: ActivatedRoute,
private broadcasterService: BroadcasterService, private ngZone: NgZone) {
super(i18nService, cryptoService, messagingService, userService, passwordGenerationService,
platformUtilsService, policyService, router, apiService, syncService, route);
}
constructor(
apiService: ApiService,
i18nService: I18nService,
cryptoService: CryptoService,
messagingService: MessagingService,
passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService,
policyService: PolicyService,
router: Router,
syncService: SyncService,
route: ActivatedRoute,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
stateService: StateService
) {
super(
i18nService,
cryptoService,
messagingService,
passwordGenerationService,
platformUtilsService,
policyService,
router,
apiService,
syncService,
route,
stateService
);
}
get masterPasswordScoreWidth() {
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
}
get masterPasswordScoreWidth() {
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
}
get masterPasswordScoreColor() {
switch (this.masterPasswordScore) {
case 4:
return 'success';
case 3:
return 'primary';
case 2:
return 'warning';
default:
return 'danger';
get masterPasswordScoreColor() {
switch (this.masterPasswordScore) {
case 4:
return "success";
case 3:
return "primary";
case 2:
return "warning";
default:
return "danger";
}
}
get masterPasswordScoreText() {
switch (this.masterPasswordScore) {
case 4:
return this.i18nService.t("strong");
case 3:
return this.i18nService.t("good");
case 2:
return this.i18nService.t("weak");
default:
return this.masterPasswordScore != null ? this.i18nService.t("weak") : null;
}
}
async ngOnInit() {
await super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
default:
}
}
});
});
}
get masterPasswordScoreText() {
switch (this.masterPasswordScore) {
case 4:
return this.i18nService.t('strong');
case 3:
return this.i18nService.t('good');
case 2:
return this.i18nService.t('weak');
default:
return this.masterPasswordScore != null ? this.i18nService.t('weak') : null;
}
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async ngOnInit() {
await super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case 'windowHidden':
this.onWindowHidden();
break;
default:
}
});
});
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
onWindowHidden() {
this.showPassword = false;
}
onWindowHidden() {
this.showPassword = false;
}
}

View File

@ -1,195 +1,354 @@
<div class="modal fade" role="dialog" aria-modal="true" attr.aria-label="{{'settings' | i18n}}">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body form">
<div class="box">
<div class="box-header">
{{'security' | i18n}}
</div>
<div class="box-content box-content-padded">
<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>
<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>
<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>
</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>
<div class="modal fade" role="dialog" aria-modal="true" attr.aria-label="{{ 'settings' | i18n }}">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body form">
<div class="box">
<div class="box-header">
{{ "settingsTitle" | i18n: currentUserEmail }}
</div>
<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="box">
<div class="box-header">
{{'options' | i18n}}
</div>
<div class="box-content box-content-padded">
<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>
</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>
</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>
<div class="form-group">
<div class="checkbox">
<label for="enableBrowserIntegration">
<input id="enableBrowserIntegration" type="checkbox" name="EnableBrowserIntegration"
[(ngModel)]="enableBrowserIntegration" (change)="saveBrowserIntegration()">
{{'enableBrowserIntegration' | i18n}}
</label>
</div>
<small class="help-block">{{'enableBrowserIntegrationDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableBrowserIntegrationFingerprint">
<input id="enableBrowserIntegrationFingerprint" type="checkbox" name="EnableBrowserIntegrationFingerprint"
[(ngModel)]="enableBrowserIntegrationFingerprint" (change)="saveBrowserIntegrationFingerprint()" [disabled]="!enableBrowserIntegration">
{{'enableBrowserIntegrationFingerprint' | i18n}}
</label>
</div>
<small class="help-block">{{'enableBrowserIntegrationFingerprintDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableTray">
<input id="enableTray" type="checkbox" name="EnableTray" [(ngModel)]="enableTray"
(change)="saveTray()">
{{enableTrayText}}
</label>
</div>
<small class="help-block">{{enableTrayDescText}}</small>
</div>
<div class="form-group" *ngIf="showMinToTray">
<div class="checkbox">
<label for="enableMinToTray">
<input id="enableMinToTray" type="checkbox" name="EnableMinToTray"
[(ngModel)]="enableMinToTray" (change)="saveMinToTray()">
{{enableMinToTrayText}}
</label>
</div>
<small class="help-block">{{enableMinToTrayDescText}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableCloseToTray">
<input id="enableCloseToTray" type="checkbox" name="EnableCloseToTray"
[(ngModel)]="enableCloseToTray" (change)="saveCloseToTray()">
{{enableCloseToTrayText}}
</label>
</div>
<small class="help-block">{{enableCloseToTrayDescText}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="startToTray">
<input id="startToTray" type="checkbox" name="StartToTray" [(ngModel)]="startToTray"
(change)="saveStartToTray()">
{{startToTrayText}}
</label>
</div>
<small class="help-block">{{startToTrayDescText}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="openAtLogin">
<input id="openAtLogin" type="checkbox" name="OpenAtLogin" [(ngModel)]="openAtLogin"
(change)="saveOpenAtLogin()">
{{'openAtLogin' | i18n}}
</label>
</div>
<small class="help-block">{{'openAtLoginDesc' | i18n}}</small>
</div>
<div class="form-group" *ngIf="showAlwaysShowDock">
<div class="checkbox">
<label for="alwaysShowDock">
<input id="alwaysShowDock" type="checkbox" name="AlwaysShowDock" [(ngModel)]="alwaysShowDock"
(change)="saveAlwaysShowDock()">
{{'alwaysShowDock' | i18n}}
</label>
</div>
<small class="help-block">{{'alwaysShowDockDesc' | i18n}}</small>
</div>
<div class="form-group">
<label for="theme">{{'theme' | i18n}}</label>
<select id="theme" name="Theme" [(ngModel)]="theme" (change)="saveTheme()">
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
<small class="help-block">{{'themeDesc' | i18n}}</small>
</div>
<div class="form-group">
<label for="locale">{{'language' | i18n}}</label>
<select id="locale" name="Locale" [(ngModel)]="locale" (change)="saveLocale()">
<option *ngFor="let o of localeOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
<small class="help-block">{{'languageDesc' | i18n}}</small>
</div>
</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>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
</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>
<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>
</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>
<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()"
>
<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>
<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>
<small class="help-block">{{ "disableFaviconDesc" | i18n }}</small>
</div>
</ng-container>
</div>
</div>
<div class="box">
<div class="box-content box-content-padded">
<h2>
<button
type="button"
class="box-header-expandable"
(click)="showAppPreferences = !showAppPreferences"
[attr.aria-expanded]="showAppPreferences"
>
{{ "appPreferences" | i18n }}
<i
*ngIf="!showAppPreferences"
class="bwi bwi-angle-down bwi-sm icon"
aria-hidden="true"
></i>
<i
*ngIf="showAppPreferences"
class="bwi bwi-chevron-up bwi-sm icon"
aria-hidden="true"
></i>
</button>
</h2>
<ng-container *ngIf="showAppPreferences">
<div class="form-group">
<div class="checkbox">
<label for="enableTray">
<input
id="enableTray"
type="checkbox"
name="EnableTray"
[(ngModel)]="enableTray"
(change)="saveTray()"
/>
{{ enableTrayText }}
</label>
</div>
<small class="help-block">{{ enableTrayDescText }}</small>
</div>
<div class="form-group" *ngIf="showMinToTray">
<div class="checkbox">
<label for="enableMinToTray">
<input
id="enableMinToTray"
type="checkbox"
name="EnableMinToTray"
[(ngModel)]="enableMinToTray"
(change)="saveMinToTray()"
/>
{{ enableMinToTrayText }}
</label>
</div>
<small class="help-block">{{ enableMinToTrayDescText }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableCloseToTray">
<input
id="enableCloseToTray"
type="checkbox"
name="EnableCloseToTray"
[(ngModel)]="enableCloseToTray"
(change)="saveCloseToTray()"
/>
{{ enableCloseToTrayText }}
</label>
</div>
<small class="help-block">{{ enableCloseToTrayDescText }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="startToTray">
<input
id="startToTray"
type="checkbox"
name="StartToTray"
[(ngModel)]="startToTray"
(change)="saveStartToTray()"
/>
{{ startToTrayText }}
</label>
</div>
<small class="help-block">{{ startToTrayDescText }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="openAtLogin">
<input
id="openAtLogin"
type="checkbox"
name="OpenAtLogin"
[(ngModel)]="openAtLogin"
(change)="saveOpenAtLogin()"
/>
{{ "openAtLogin" | i18n }}
</label>
</div>
<small class="help-block">{{ "openAtLoginDesc" | i18n }}</small>
</div>
<div class="form-group" *ngIf="showAlwaysShowDock">
<div class="checkbox">
<label for="alwaysShowDock">
<input
id="alwaysShowDock"
type="checkbox"
name="AlwaysShowDock"
[(ngModel)]="alwaysShowDock"
(change)="saveAlwaysShowDock()"
/>
{{ "alwaysShowDock" | i18n }}
</label>
</div>
<small class="help-block">{{ "alwaysShowDockDesc" | i18n }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableBrowserIntegration">
<input
id="enableBrowserIntegration"
type="checkbox"
name="EnableBrowserIntegration"
[(ngModel)]="enableBrowserIntegration"
(change)="saveBrowserIntegration()"
/>
{{ "enableBrowserIntegration" | i18n }}
</label>
</div>
<small class="help-block">{{ "enableBrowserIntegrationDesc" | i18n }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableBrowserIntegrationFingerprint">
<input
id="enableBrowserIntegrationFingerprint"
type="checkbox"
name="EnableBrowserIntegrationFingerprint"
[(ngModel)]="enableBrowserIntegrationFingerprint"
(change)="saveBrowserIntegrationFingerprint()"
[disabled]="!enableBrowserIntegration"
/>
{{ "enableBrowserIntegrationFingerprint" | i18n }}
</label>
</div>
<small class="help-block">{{
"enableBrowserIntegrationFingerprintDesc" | i18n
}}</small>
</div>
<div class="form-group">
<label for="theme">{{ "theme" | i18n }}</label>
<select id="theme" name="Theme" [(ngModel)]="theme" (change)="saveTheme()">
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="help-block">{{ "themeDesc" | i18n }}</small>
</div>
<div class="form-group">
<label for="locale">{{ "language" | i18n }}</label>
<select id="locale" name="Locale" [(ngModel)]="locale" (change)="saveLocale()">
<option *ngFor="let o of localeOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="help-block">{{ "languageDesc" | i18n }}</small>
</div>
</ng-container>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div>
</div>
</div>
</div>

View File

@ -1,365 +1,407 @@
import {
Component,
OnInit,
} from '@angular/core';
import { FormControl } from '@angular/forms';
import { debounceTime } from 'rxjs/operators';
import { Component, OnInit } from "@angular/core";
import { FormControl } from "@angular/forms";
import { debounceTime } from "rxjs/operators";
import { DeviceType } from 'jslib-common/enums/deviceType';
import { ThemeType } from 'jslib-common/enums/themeType';
import { ModalService } from "jslib-angular/services/modal.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 { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { DeviceType } from "jslib-common/enums/deviceType";
import { StorageLocation } from "jslib-common/enums/storageLocation";
import { ThemeType } from "jslib-common/enums/themeType";
import { Utils } from "jslib-common/misc/utils";
import { isWindowsStore } from "jslib-electron/utils";
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 { ConstantsService } from 'jslib-common/services/constants.service';
import { ModalService } from 'jslib-angular/services/modal.service';
import { ElectronConstants } from 'jslib-electron/electronConstants';
import { Utils } from 'jslib-common/misc/utils';
import { isWindowsStore } from 'jslib-electron/utils';
import { SetPinComponent } from '../components/set-pin.component';
import { SetPinComponent } from "../components/set-pin.component";
@Component({
selector: 'app-settings',
templateUrl: 'settings.component.html',
selector: "app-settings",
templateUrl: "settings.component.html",
})
export class SettingsComponent implements OnInit {
vaultTimeoutAction: string;
pin: boolean = null;
disableFavicons: boolean = false;
enableBrowserIntegration: boolean = false;
enableBrowserIntegrationFingerprint: boolean = false;
enableMinToTray: boolean = false;
enableCloseToTray: boolean = false;
enableTray: boolean = false;
showMinToTray: boolean = false;
startToTray: boolean = false;
minimizeOnCopyToClipboard: boolean = false;
locale: string;
vaultTimeouts: any[];
localeOptions: any[];
theme: string;
themeOptions: any[];
clearClipboard: number;
clearClipboardOptions: any[];
supportsBiometric: boolean;
biometric: boolean;
biometricText: string;
noAutoPromptBiometrics: boolean;
noAutoPromptBiometricsText: string;
alwaysShowDock: boolean;
showAlwaysShowDock: boolean = false;
openAtLogin: boolean;
requireEnableTray: boolean = false;
vaultTimeoutAction: string;
pin: boolean = null;
disableFavicons = false;
enableBrowserIntegration = false;
enableBrowserIntegrationFingerprint = false;
enableMinToTray = false;
enableCloseToTray = false;
enableTray = false;
showMinToTray = false;
startToTray = false;
minimizeOnCopyToClipboard = false;
locale: string;
vaultTimeouts: any[];
localeOptions: any[];
theme: ThemeType;
themeOptions: any[];
clearClipboard: number;
clearClipboardOptions: any[];
supportsBiometric: boolean;
biometric: boolean;
biometricText: string;
noAutoPromptBiometrics: boolean;
noAutoPromptBiometricsText: string;
alwaysShowDock: boolean;
showAlwaysShowDock = false;
openAtLogin: boolean;
requireEnableTray = false;
enableTrayText: string;
enableTrayDescText: string;
enableMinToTrayText: string;
enableMinToTrayDescText: string;
enableCloseToTrayText: string;
enableCloseToTrayDescText: string;
startToTrayText: string;
startToTrayDescText: string;
enableTrayText: string;
enableTrayDescText: string;
enableMinToTrayText: string;
enableMinToTrayDescText: string;
enableCloseToTrayText: string;
enableCloseToTrayDescText: string;
startToTrayText: string;
startToTrayDescText: string;
vaultTimeout: FormControl = new FormControl(null);
vaultTimeout: FormControl = new FormControl(null);
constructor(private i18nService: I18nService, private platformUtilsService: PlatformUtilsService,
private storageService: StorageService, private vaultTimeoutService: VaultTimeoutService,
private stateService: StateService, private messagingService: MessagingService,
private cryptoService: CryptoService, private modalService: ModalService) {
const isMac = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
showSecurity = true;
showAccountPreferences = true;
showAppPreferences = true;
// Workaround to avoid ghosting trays https://github.com/electron/electron/issues/17622
this.requireEnableTray = this.platformUtilsService.getDevice() === DeviceType.LinuxDesktop;
currentUserEmail: string;
const trayKey = isMac ? 'enableMenuBar' : 'enableTray';
this.enableTrayText = this.i18nService.t(trayKey);
this.enableTrayDescText = this.i18nService.t(trayKey + 'Desc');
constructor(
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private vaultTimeoutService: VaultTimeoutService,
private stateService: StateService,
private messagingService: MessagingService,
private cryptoService: CryptoService,
private modalService: ModalService
) {
const isMac = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
const minToTrayKey = isMac ? 'enableMinToMenuBar' : 'enableMinToTray';
this.enableMinToTrayText = this.i18nService.t(minToTrayKey);
this.enableMinToTrayDescText = this.i18nService.t(minToTrayKey + 'Desc');
// Workaround to avoid ghosting trays https://github.com/electron/electron/issues/17622
this.requireEnableTray = this.platformUtilsService.getDevice() === DeviceType.LinuxDesktop;
const closeToTrayKey = isMac ? 'enableCloseToMenuBar' : 'enableCloseToTray';
this.enableCloseToTrayText = this.i18nService.t(closeToTrayKey);
this.enableCloseToTrayDescText = this.i18nService.t(closeToTrayKey + 'Desc');
const trayKey = isMac ? "enableMenuBar" : "enableTray";
this.enableTrayText = this.i18nService.t(trayKey);
this.enableTrayDescText = this.i18nService.t(trayKey + "Desc");
const startToTrayKey = isMac ? 'startToMenuBar' : 'startToTray';
this.startToTrayText = this.i18nService.t(startToTrayKey);
this.startToTrayDescText = this.i18nService.t(startToTrayKey + 'Desc');
const minToTrayKey = isMac ? "enableMinToMenuBar" : "enableMinToTray";
this.enableMinToTrayText = this.i18nService.t(minToTrayKey);
this.enableMinToTrayDescText = this.i18nService.t(minToTrayKey + "Desc");
this.vaultTimeouts = [
// { name: i18nService.t('immediately'), value: 0 },
{ name: i18nService.t('oneMinute'), value: 1 },
{ name: i18nService.t('fiveMinutes'), value: 5 },
{ name: i18nService.t('fifteenMinutes'), value: 15 },
{ name: i18nService.t('thirtyMinutes'), value: 30 },
{ name: i18nService.t('oneHour'), value: 60 },
{ name: i18nService.t('fourHours'), value: 240 },
{ name: i18nService.t('onIdle'), value: -4 },
{ name: i18nService.t('onSleep'), value: -3 },
];
const closeToTrayKey = isMac ? "enableCloseToMenuBar" : "enableCloseToTray";
this.enableCloseToTrayText = this.i18nService.t(closeToTrayKey);
this.enableCloseToTrayDescText = this.i18nService.t(closeToTrayKey + "Desc");
if (this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop) {
this.vaultTimeouts.push({ name: i18nService.t('onLocked'), value: -2 });
}
const startToTrayKey = isMac ? "startToMenuBar" : "startToTray";
this.startToTrayText = this.i18nService.t(startToTrayKey);
this.startToTrayDescText = this.i18nService.t(startToTrayKey + "Desc");
this.vaultTimeouts = this.vaultTimeouts.concat([
{ name: i18nService.t('onRestart'), value: -1 },
{ name: i18nService.t('never'), value: null },
]);
this.vaultTimeouts = [
// { name: i18nService.t('immediately'), value: 0 },
{ name: i18nService.t("oneMinute"), value: 1 },
{ name: i18nService.t("fiveMinutes"), value: 5 },
{ name: i18nService.t("fifteenMinutes"), value: 15 },
{ name: i18nService.t("thirtyMinutes"), value: 30 },
{ name: i18nService.t("oneHour"), value: 60 },
{ name: i18nService.t("fourHours"), value: 240 },
{ name: i18nService.t("onIdle"), value: -4 },
{ name: i18nService.t("onSleep"), value: -3 },
];
this.vaultTimeout.valueChanges.pipe(debounceTime(500)).subscribe(() => {
this.saveVaultTimeoutOptions();
});
const localeOptions: any[] = [];
i18nService.supportedTranslationLocales.forEach(locale => {
let name = locale;
if (i18nService.localeNames.has(locale)) {
name += (' - ' + i18nService.localeNames.get(locale));
}
localeOptions.push({ name: name, value: locale });
});
localeOptions.sort(Utils.getSortFunction(i18nService, 'name'));
localeOptions.splice(0, 0, { name: i18nService.t('default'), value: null });
this.localeOptions = localeOptions;
this.themeOptions = [
{ name: i18nService.t('default'), value: null },
{ name: i18nService.t('light'), value: ThemeType.Light },
{ name: i18nService.t('dark'), value: ThemeType.Dark },
{ name: 'Nord', value: ThemeType.Nord },
];
this.clearClipboardOptions = [
{ name: i18nService.t('never'), value: null },
{ name: i18nService.t('tenSeconds'), value: 10 },
{ name: i18nService.t('twentySeconds'), value: 20 },
{ name: i18nService.t('thirtySeconds'), value: 30 },
{ name: i18nService.t('oneMinute'), value: 60 },
{ name: i18nService.t('twoMinutes'), value: 120 },
{ name: i18nService.t('fiveMinutes'), value: 300 },
];
if (this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop) {
this.vaultTimeouts.push({ name: i18nService.t("onLocked"), value: -2 });
}
async ngOnInit() {
this.showMinToTray = this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop;
this.vaultTimeout.setValue(await this.vaultTimeoutService.getVaultTimeout());
this.vaultTimeoutAction = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey);
const pinSet = await this.vaultTimeoutService.isPinLockSet();
this.pin = pinSet[0] || pinSet[1];
this.disableFavicons = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey);
this.enableBrowserIntegration = await this.storageService.get<boolean>(
ElectronConstants.enableBrowserIntegration);
this.enableBrowserIntegrationFingerprint = await this.storageService.get<boolean>(ElectronConstants.enableBrowserIntegrationFingerprint);
this.enableMinToTray = await this.storageService.get<boolean>(ElectronConstants.enableMinimizeToTrayKey);
this.enableCloseToTray = await this.storageService.get<boolean>(ElectronConstants.enableCloseToTrayKey);
this.enableTray = await this.storageService.get<boolean>(ElectronConstants.enableTrayKey);
this.startToTray = await this.storageService.get<boolean>(ElectronConstants.enableStartToTrayKey);
this.locale = await this.storageService.get<string>(ConstantsService.localeKey);
this.theme = await this.storageService.get<string>(ConstantsService.themeKey);
this.clearClipboard = await this.storageService.get<number>(ConstantsService.clearClipboardKey);
this.minimizeOnCopyToClipboard = await this.storageService.get<boolean>(
ElectronConstants.minimizeOnCopyToClipboardKey);
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
this.biometric = await this.vaultTimeoutService.isBiometricLockSet();
this.biometricText = await this.storageService.get<string>(ConstantsService.biometricText);
this.noAutoPromptBiometrics = await this.storageService.get<boolean>(ConstantsService.disableAutoBiometricsPromptKey);
this.noAutoPromptBiometricsText = await this.storageService.get<string>(ElectronConstants.noAutoPromptBiometricsText);
this.alwaysShowDock = await this.storageService.get<boolean>(ElectronConstants.alwaysShowDock);
this.showAlwaysShowDock = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
this.openAtLogin = await this.storageService.get<boolean>(ElectronConstants.openAtLogin);
this.vaultTimeouts = this.vaultTimeouts.concat([
{ name: i18nService.t("onRestart"), value: -1 },
{ name: i18nService.t("never"), value: null },
]);
const localeOptions: any[] = [];
i18nService.supportedTranslationLocales.forEach((locale) => {
let name = locale;
if (i18nService.localeNames.has(locale)) {
name += " - " + i18nService.localeNames.get(locale);
}
localeOptions.push({ name: name, value: locale });
});
localeOptions.sort(Utils.getSortFunction(i18nService, "name"));
localeOptions.splice(0, 0, { name: i18nService.t("default"), value: null });
this.localeOptions = localeOptions;
this.themeOptions = [
{ name: i18nService.t("default"), value: ThemeType.System },
{ name: i18nService.t("light"), value: ThemeType.Light },
{ name: i18nService.t("dark"), value: ThemeType.Dark },
{ name: "Nord", value: ThemeType.Nord },
];
this.clearClipboardOptions = [
{ name: i18nService.t("never"), value: null },
{ name: i18nService.t("tenSeconds"), value: 10 },
{ name: i18nService.t("twentySeconds"), value: 20 },
{ name: i18nService.t("thirtySeconds"), value: 30 },
{ name: i18nService.t("oneMinute"), value: 60 },
{ name: i18nService.t("twoMinutes"), value: 120 },
{ name: i18nService.t("fiveMinutes"), value: 300 },
];
}
async ngOnInit() {
// App preferences
this.showMinToTray = this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop;
this.enableMinToTray = await this.stateService.getEnableMinimizeToTray();
this.enableCloseToTray = await this.stateService.getEnableCloseToTray();
this.enableTray = await this.stateService.getEnableTray();
this.startToTray = await this.stateService.getEnableStartToTray();
this.alwaysShowDock = await this.stateService.getAlwaysShowDock();
this.showAlwaysShowDock = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
this.openAtLogin = await this.stateService.getOpenAtLogin();
this.locale = await this.stateService.getLocale();
this.theme = await this.stateService.getTheme();
if ((await this.stateService.getUserId()) == null) {
return;
}
this.currentUserEmail = await this.stateService.getEmail();
// Security
this.vaultTimeout.setValue(await this.stateService.getVaultTimeout());
this.vaultTimeoutAction = await this.stateService.getVaultTimeoutAction();
this.vaultTimeout.valueChanges.pipe(debounceTime(500)).subscribe(() => {
this.saveVaultTimeoutOptions();
});
const pinSet = await this.vaultTimeoutService.isPinLockSet();
this.pin = pinSet[0] || pinSet[1];
// Account preferences
this.disableFavicons = await this.stateService.getDisableFavicon();
this.enableBrowserIntegration = await this.stateService.getEnableBrowserIntegration();
this.enableBrowserIntegrationFingerprint =
await this.stateService.getEnableBrowserIntegrationFingerprint();
this.clearClipboard = await this.stateService.getClearClipboard();
this.minimizeOnCopyToClipboard = await this.stateService.getMinimizeOnCopyToClipboard();
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
this.biometric = await this.vaultTimeoutService.isBiometricLockSet();
this.biometricText = await this.stateService.getBiometricText();
this.noAutoPromptBiometrics = await this.stateService.getNoAutoPromptBiometrics();
this.noAutoPromptBiometricsText = await this.stateService.getNoAutoPromptBiometricsText();
}
async saveVaultTimeoutOptions() {
if (this.vaultTimeoutAction === "logOut") {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("vaultTimeoutLogOutConfirmation"),
this.i18nService.t("vaultTimeoutLogOutConfirmationTitle"),
this.i18nService.t("yes"),
this.i18nService.t("cancel"),
"warning"
);
if (!confirmed) {
this.vaultTimeoutAction = "lock";
return;
}
}
async saveVaultTimeoutOptions() {
if (this.vaultTimeoutAction === 'logOut') {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('vaultTimeoutLogOutConfirmation'),
this.i18nService.t('vaultTimeoutLogOutConfirmationTitle'),
this.i18nService.t('yes'), this.i18nService.t('cancel'), 'warning');
if (!confirmed) {
this.vaultTimeoutAction = 'lock';
return;
}
}
// Avoid saving 0 since it's useless as a timeout value.
if (this.vaultTimeout.value === 0) {
return;
}
if (!this.vaultTimeout.valid) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('vaultTimeoutTooLarge'));
return;
}
await this.vaultTimeoutService.setVaultTimeoutOptions(this.vaultTimeout.value, this.vaultTimeoutAction);
// Avoid saving 0 since it's useless as a timeout value.
if (this.vaultTimeout.value === 0) {
return;
}
async updatePin() {
if (this.pin) {
const ref = this.modalService.open(SetPinComponent, { allowMultipleModals: true });
if (ref == null) {
this.pin = false;
return;
}
this.pin = await ref.onClosedPromise();
}
if (!this.pin) {
await this.cryptoService.clearPinProtectedKey();
await this.vaultTimeoutService.clear();
}
if (!this.vaultTimeout.valid) {
this.platformUtilsService.showToast(
"error",
null,
this.i18nService.t("vaultTimeoutTooLarge")
);
return;
}
async updateBiometric() {
const current = this.biometric;
if (this.biometric) {
this.biometric = false;
} else if (this.supportsBiometric) {
this.biometric = await this.platformUtilsService.authenticateBiometric();
}
if (this.biometric === current) {
return;
}
if (this.biometric) {
await this.storageService.save(ConstantsService.biometricUnlockKey, true);
} else {
await this.storageService.remove(ConstantsService.biometricUnlockKey);
await this.storageService.remove(ConstantsService.disableAutoBiometricsPromptKey);
this.noAutoPromptBiometrics = false;
}
this.vaultTimeoutService.biometricLocked = false;
await this.cryptoService.toggleKey();
await this.vaultTimeoutService.setVaultTimeoutOptions(
this.vaultTimeout.value,
this.vaultTimeoutAction
);
}
async updatePin() {
if (this.pin) {
const ref = this.modalService.open(SetPinComponent, { allowMultipleModals: true });
if (ref == null) {
this.pin = false;
return;
}
this.pin = await ref.onClosedPromise();
}
if (!this.pin) {
await this.cryptoService.clearPinProtectedKey();
await this.vaultTimeoutService.clear();
}
}
async updateBiometric() {
const current = this.biometric;
if (this.biometric) {
this.biometric = false;
} else if (this.supportsBiometric) {
this.biometric = await this.platformUtilsService.authenticateBiometric();
}
if (this.biometric === current) {
return;
}
if (this.biometric) {
await this.stateService.setBiometricUnlock(true);
} else {
await this.stateService.setBiometricUnlock(null);
await this.stateService.setNoAutoPromptBiometrics(null);
this.noAutoPromptBiometrics = false;
}
await this.stateService.setBiometricLocked(false);
await this.cryptoService.toggleKey();
}
async updateNoAutoPromptBiometrics() {
if (!this.biometric) {
this.noAutoPromptBiometrics = false;
}
async updateNoAutoPromptBiometrics() {
if (!this.biometric) {
this.noAutoPromptBiometrics = false;
}
if (this.noAutoPromptBiometrics) {
await this.stateService.setNoAutoPromptBiometrics(true);
} else {
await this.stateService.setNoAutoPromptBiometrics(null);
}
}
if (this.noAutoPromptBiometrics) {
await this.storageService.save(ConstantsService.disableAutoBiometricsPromptKey, true);
} else {
await this.storageService.remove(ConstantsService.disableAutoBiometricsPromptKey);
}
async saveFavicons() {
await this.stateService.setDisableFavicon(this.disableFavicons);
await this.stateService.setDisableFavicon(this.disableFavicons, {
storageLocation: StorageLocation.Disk,
});
this.messagingService.send("refreshCiphers");
}
async saveMinToTray() {
await this.stateService.setEnableMinimizeToTray(this.enableMinToTray);
}
async saveCloseToTray() {
if (this.requireEnableTray) {
this.enableTray = true;
await this.stateService.setEnableTray(this.enableTray);
}
async saveFavicons() {
await this.storageService.save(ConstantsService.disableFaviconKey, this.disableFavicons);
await this.stateService.save(ConstantsService.disableFaviconKey, this.disableFavicons);
this.messagingService.send('refreshCiphers');
await this.stateService.setEnableCloseToTray(this.enableCloseToTray);
}
async saveTray() {
if (
this.requireEnableTray &&
!this.enableTray &&
(this.startToTray || this.enableCloseToTray)
) {
const confirm = await this.platformUtilsService.showDialog(
this.i18nService.t("confirmTrayDesc"),
this.i18nService.t("confirmTrayTitle"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (confirm) {
this.startToTray = false;
await this.stateService.setEnableStartToTray(this.startToTray);
this.enableCloseToTray = false;
await this.stateService.setEnableCloseToTray(this.enableCloseToTray);
} else {
this.enableTray = true;
}
return;
}
async saveMinToTray() {
await this.storageService.save(ElectronConstants.enableMinimizeToTrayKey, this.enableMinToTray);
await this.stateService.setEnableTray(this.enableTray);
this.messagingService.send(this.enableTray ? "showTray" : "removeTray");
}
async saveStartToTray() {
if (this.requireEnableTray) {
this.enableTray = true;
await this.stateService.setEnableTray(this.enableTray);
}
async saveCloseToTray() {
if (this.requireEnableTray) {
this.enableTray = true;
await this.storageService.save(ElectronConstants.enableTrayKey, this.enableTray);
}
await this.stateService.setEnableStartToTray(this.startToTray);
}
await this.storageService.save(ElectronConstants.enableCloseToTrayKey, this.enableCloseToTray);
async saveLocale() {
await this.stateService.setLocale(this.locale);
}
async saveTheme() {
await this.stateService.setTheme(this.theme);
window.setTimeout(() => window.location.reload(), 200);
}
async saveMinOnCopyToClipboard() {
await this.stateService.setMinimizeOnCopyToClipboard(this.minimizeOnCopyToClipboard);
}
async saveClearClipboard() {
await this.stateService.setClearClipboard(this.clearClipboard);
}
async saveAlwaysShowDock() {
await this.stateService.setAlwaysShowDock(this.alwaysShowDock);
}
async saveOpenAtLogin() {
this.stateService.setOpenAtLogin(this.openAtLogin);
this.messagingService.send(this.openAtLogin ? "addOpenAtLogin" : "removeOpenAtLogin");
}
async saveBrowserIntegration() {
if (process.platform === "darwin" && !this.platformUtilsService.isMacAppStore()) {
await this.platformUtilsService.showDialog(
this.i18nService.t("browserIntegrationMasOnlyDesc"),
this.i18nService.t("browserIntegrationMasOnlyTitle"),
this.i18nService.t("ok"),
null,
"warning"
);
this.enableBrowserIntegration = false;
return;
} else if (isWindowsStore()) {
await this.platformUtilsService.showDialog(
this.i18nService.t("browserIntegrationWindowsStoreDesc"),
this.i18nService.t("browserIntegrationWindowsStoreTitle"),
this.i18nService.t("ok"),
null,
"warning"
);
this.enableBrowserIntegration = false;
return;
}
async saveTray() {
if (this.requireEnableTray && !this.enableTray && (this.startToTray || this.enableCloseToTray)) {
const confirm = await this.platformUtilsService.showDialog(
this.i18nService.t('confirmTrayDesc'), this.i18nService.t('confirmTrayTitle'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
await this.stateService.setEnableBrowserIntegration(this.enableBrowserIntegration);
this.messagingService.send(
this.enableBrowserIntegration ? "enableBrowserIntegration" : "disableBrowserIntegration"
);
if (confirm) {
this.startToTray = false;
await this.storageService.save(ElectronConstants.enableStartToTrayKey, this.startToTray);
this.enableCloseToTray = false;
await this.storageService.save(ElectronConstants.enableCloseToTrayKey, this.enableCloseToTray);
} else {
this.enableTray = true;
}
return;
}
await this.storageService.save(ElectronConstants.enableTrayKey, this.enableTray);
this.messagingService.send(this.enableTray ? 'showTray' : 'removeTray');
if (!this.enableBrowserIntegration) {
this.enableBrowserIntegrationFingerprint = false;
this.saveBrowserIntegrationFingerprint();
}
}
async saveStartToTray() {
if (this.requireEnableTray) {
this.enableTray = true;
await this.storageService.save(ElectronConstants.enableTrayKey, this.enableTray);
}
await this.storageService.save(ElectronConstants.enableStartToTrayKey, this.startToTray);
}
async saveLocale() {
await this.storageService.save(ConstantsService.localeKey, this.locale);
}
async saveTheme() {
await this.storageService.save(ConstantsService.themeKey, this.theme);
window.setTimeout(() => window.location.reload(), 200);
}
async saveMinOnCopyToClipboard() {
await this.storageService.save(ElectronConstants.minimizeOnCopyToClipboardKey, this.minimizeOnCopyToClipboard);
}
async saveClearClipboard() {
await this.storageService.save(ConstantsService.clearClipboardKey, this.clearClipboard);
}
async saveAlwaysShowDock() {
await this.storageService.save(ElectronConstants.alwaysShowDock, this.alwaysShowDock);
}
async saveOpenAtLogin() {
this.storageService.save(ElectronConstants.openAtLogin, this.openAtLogin);
this.messagingService.send(this.openAtLogin ? 'addOpenAtLogin' : 'removeOpenAtLogin');
}
async saveBrowserIntegration() {
if (process.platform === 'darwin' && !this.platformUtilsService.isMacAppStore()) {
await this.platformUtilsService.showDialog(
this.i18nService.t('browserIntegrationMasOnlyDesc'),
this.i18nService.t('browserIntegrationMasOnlyTitle'),
this.i18nService.t('ok'), null, 'warning');
this.enableBrowserIntegration = false;
return;
} else if (isWindowsStore()) {
await this.platformUtilsService.showDialog(
this.i18nService.t('browserIntegrationWindowsStoreDesc'),
this.i18nService.t('browserIntegrationWindowsStoreTitle'),
this.i18nService.t('ok'), null, 'warning');
this.enableBrowserIntegration = false;
return;
}
await this.storageService.save(ElectronConstants.enableBrowserIntegration, this.enableBrowserIntegration);
this.messagingService.send(this.enableBrowserIntegration ? 'enableBrowserIntegration' : 'disableBrowserIntegration');
if (!this.enableBrowserIntegration) {
this.enableBrowserIntegrationFingerprint = false;
this.saveBrowserIntegrationFingerprint();
}
}
async saveBrowserIntegrationFingerprint() {
await this.storageService.save(ElectronConstants.enableBrowserIntegrationFingerprint, this.enableBrowserIntegrationFingerprint);
}
async saveBrowserIntegrationFingerprint() {
await this.stateService.setEnableBrowserIntegrationFingerprint(
this.enableBrowserIntegrationFingerprint
);
}
}

View File

@ -1,9 +1,9 @@
<form id="sso-page" (ngSubmit)="submit()">
<div class="content">
<img class="logo-image" alt="Bitwarden">
<div class="box">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
{{'loading' | i18n}}
</div>
<div class="content">
<img class="logo-image" alt="Bitwarden" />
<div class="box">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }}
</div>
</div>
</form>

View File

@ -1,41 +1,54 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ApiService } from 'jslib-common/abstractions/api.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';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
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';
import { SsoComponent as BaseSsoComponent } from "jslib-angular/components/sso.component";
import { ApiService } from "jslib-common/abstractions/api.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";
import { I18nService } from "jslib-common/abstractions/i18n.service";
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 { SyncService } from "jslib-common/abstractions/sync.service";
@Component({
selector: 'app-sso',
templateUrl: 'sso.component.html',
selector: "app-sso",
templateUrl: "sso.component.html",
})
export class SsoComponent extends BaseSsoComponent {
constructor(authService: AuthService, router: Router,
i18nService: I18nService, syncService: SyncService, route: ActivatedRoute,
storageService: StorageService, stateService: StateService,
platformUtilsService: PlatformUtilsService, apiService: ApiService,
cryptoFunctionService: CryptoFunctionService, environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService, logService: LogService) {
super(authService, router, i18nService, route, storageService, stateService, platformUtilsService,
apiService, cryptoFunctionService, environmentService, passwordGenerationService, logService);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
this.redirectUri = 'bitwarden://sso-callback';
this.clientId = 'desktop';
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
syncService: SyncService,
route: ActivatedRoute,
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);
};
this.redirectUri = "bitwarden://sso-callback";
this.clientId = "desktop";
}
}

View File

@ -1,28 +1,33 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="twoStepTitle">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="twoStepTitle">
{{'twoStepOptions' | i18n}}
</div>
<div class="box-content">
<a href="#" appStopClick *ngFor="let p of providers" class="box-content-row"
(click)="choose(p)">
<img [src]="'images/two-factor/' + p.type + '.png'" alt="" class="img-right">
<span class="text">{{p.name}}</span>
<span class="detail">{{p.description}}</span>
</a>
<a href="#" appStopClick class="box-content-row" (click)="recover()">
<span class="text">{{'recoveryCodeTitle' | i18n}}</span>
<span class="detail">{{'recoveryCodeDesc' | i18n}}</span>
</a>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="twoStepTitle">
{{ "twoStepOptions" | i18n }}
</div>
<div class="box-content">
<button
type="button"
appStopClick
*ngFor="let p of providers"
class="box-content-row"
(click)="choose(p)"
>
<img [src]="'images/two-factor/' + p.type + '.png'" alt="" class="img-right" />
<span class="text">{{ p.name }}</span>
<span class="detail">{{ p.description }}</span>
</button>
<button type="button" appStopClick class="box-content-row" (click)="recover()">
<span class="text">{{ "recoveryCodeTitle" | i18n }}</span>
<span class="detail">{{ "recoveryCodeDesc" | i18n }}</span>
</button>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div>
</div>
</div>
</div>

View File

@ -1,21 +1,22 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import {
TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent,
} from 'jslib-angular/components/two-factor-options.component';
import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "jslib-angular/components/two-factor-options.component";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { TwoFactorService } from "jslib-common/abstractions/twoFactor.service";
@Component({
selector: 'app-two-factor-options',
templateUrl: 'two-factor-options.component.html',
selector: "app-two-factor-options",
templateUrl: "two-factor-options.component.html",
})
export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
constructor(authService: AuthService, router: Router,
i18nService: I18nService, platformUtilsService: PlatformUtilsService) {
super(authService, router, i18nService, platformUtilsService, window);
}
constructor(
twoFactorService: TwoFactorService,
router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService
) {
super(twoFactorService, router, i18nService, platformUtilsService, window);
}
}

View File

@ -1,88 +1,144 @@
<form id="two-factor-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise" attr.aria-hidden="{{showingModal}}">
<div class="content">
<h1>{{title}}</h1>
<p *ngIf="selectedProviderType === providerType.Authenticator">{{'enterVerificationCodeApp' | i18n}}</p>
<p *ngIf="selectedProviderType === providerType.Email">
{{'enterVerificationCodeEmail' | i18n : twoFactorEmail}}
</p>
<div class="box last"
*ngIf="selectedProviderType === providerType.Email || selectedProviderType === providerType.Authenticator">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code">{{'verificationCode' | i18n}}</label>
<input id="code" type="text" name="Code" [(ngModel)]="token" required appAutofocus>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
<form
id="two-factor-page"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
attr.aria-hidden="{{ showingModal }}"
>
<div id="content" class="content">
<h1>{{ title }}</h1>
<p *ngIf="selectedProviderType === providerType.Authenticator">
{{ "enterVerificationCodeApp" | i18n }}
</p>
<p *ngIf="selectedProviderType === providerType.Email">
{{ "enterVerificationCodeEmail" | i18n: twoFactorEmail }}
</p>
<div
class="box last"
*ngIf="
selectedProviderType === providerType.Email ||
selectedProviderType === providerType.Authenticator
"
>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code">{{ "verificationCode" | i18n }}</label>
<input
id="code"
type="text"
name="Code"
[(ngModel)]="token"
required
appAutofocus
appInputVerbatim
/>
</div>
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
<p>{{'insertYubiKey' | i18n}}</p>
<img src="../../images/yubikey.jpg" alt="">
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code" class="sr-only">{{'verificationCode' | i18n}}</label>
<input id="code" type="password" name="Code" [(ngModel)]="token" required appAutofocus>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn">
<div id="web-authn-frame">
<iframe id="webauthn_iframe" [allow]="webAuthnAllow"></iframe>
</div>
<div class="box first">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo">
<div id="duo-frame"><iframe id="duo_iframe"></iframe></div>
<div class="box last">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
</div>
</ng-container>
<div class="box last" *ngIf="selectedProviderType == null">
<div class="box-content">
<div class="box-content-row">
<p>{{'noTwoStepProviders' | i18n}}</p>
<p>{{'noTwoStepProviders2' | i18n}}</p>
</div>
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick *ngIf="selectedProviderType != null && selectedProviderType !== providerType.Duo &&
selectedProviderType !== providerType.OrganizationDuo">
<span [hidden]="form.loading"><i class="fa fa-sign-in" aria-hidden="true"></i>
{{'continue' | i18n}}</span>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{'cancel' | i18n}}</a>
</div>
<div class="sub-options">
<a href="#" appStopClick (click)="anotherMethod()" role="button">{{'useAnotherTwoStepMethod' | i18n}}</a>
<a href="#" appStopClick (click)="sendEmail(true)" [appApiAction]="emailPromise" role="button"
*ngIf="selectedProviderType === providerType.Email">
{{'sendVerificationCodeEmailAgain' | i18n}}
</a>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div>
</div>
</div>
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
<p>{{ "insertYubiKey" | i18n }}</p>
<img src="../../images/yubikey.jpg" alt="" />
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code" class="sr-only">{{ "verificationCode" | i18n }}</label>
<input
id="code"
type="password"
name="Code"
[(ngModel)]="token"
required
appAutofocus
appInputVerbatim
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn">
<div id="web-authn-frame">
<iframe id="webauthn_iframe" [allow]="webAuthnAllow"></iframe>
</div>
<div class="box first">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div>
</div>
</div>
</ng-container>
<ng-container
*ngIf="
selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo
"
>
<div id="duo-frame"><iframe id="duo_iframe"></iframe></div>
<div class="box last">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div>
</div>
</div>
</ng-container>
<div class="box last" *ngIf="selectedProviderType == null">
<div class="box-content">
<div class="box-content-row">
<p>{{ "noTwoStepProviders" | i18n }}</p>
<p>{{ "noTwoStepProviders2" | i18n }}</p>
</div>
</div>
</div>
<div class="box last" [hidden]="!showCaptcha()">
<div class="box-content">
<div class="box-content-row">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
</div>
</div>
<div class="buttons">
<button
type="submit"
class="btn primary block"
[disabled]="form.loading"
*ngIf="
selectedProviderType != null &&
selectedProviderType !== providerType.Duo &&
selectedProviderType !== providerType.OrganizationDuo
"
>
<span [hidden]="form.loading"
><i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ "continue" | i18n }}</span
>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button type="button" routerLink="/login" class="btn block">{{ "cancel" | i18n }}</button>
</div>
<div class="sub-options">
<button type="button" appStopClick (click)="anotherMethod()">
{{ "useAnotherTwoStepMethod" | i18n }}
</button>
<button
type="button"
appStopClick
(click)="sendEmail(true)"
[appApiAction]="emailPromise"
*ngIf="selectedProviderType === providerType.Email"
>
{{ "sendVerificationCodeEmailAgain" | i18n }}
</button>
</div>
</div>
</form>
<ng-template #twoFactorOptions></ng-template>

View File

@ -1,71 +1,94 @@
import {
Component,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { Component, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { TwoFactorComponent as BaseTwoFactorComponent } from "jslib-angular/components/two-factor.component";
import { ModalService } from "jslib-angular/services/modal.service";
import { ApiService } from "jslib-common/abstractions/api.service";
import { AppIdService } from "jslib-common/abstractions/appId.service";
import { AuthService } from "jslib-common/abstractions/auth.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";
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 { TwoFactorService } from "jslib-common/abstractions/twoFactor.service";
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
import { TwoFactorOptionsComponent } from './two-factor-options.component';
import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.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';
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 { TwoFactorOptionsComponent } from "./two-factor-options.component";
@Component({
selector: 'app-two-factor',
templateUrl: 'two-factor.component.html',
selector: "app-two-factor",
templateUrl: "two-factor.component.html",
})
export class TwoFactorComponent extends BaseTwoFactorComponent {
@ViewChild('twoFactorOptions', { read: ViewContainerRef, static: true }) twoFactorOptionsModal: ViewContainerRef;
@ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true })
twoFactorOptionsModal: ViewContainerRef;
showingModal = false;
showingModal = false;
constructor(authService: AuthService, router: Router,
i18nService: I18nService, apiService: ApiService,
platformUtilsService: PlatformUtilsService, syncService: SyncService,
environmentService: EnvironmentService, private modalService: ModalService,
stateService: StateService, storageService: StorageService, route: ActivatedRoute,
logService: LogService) {
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService,
stateService, storageService, route, logService);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
}
async anotherMethod() {
const [modal, childComponent] = await this.modalService.openViewRef(TwoFactorOptionsComponent, this.twoFactorOptionsModal);
modal.onShown.subscribe(() => {
this.showingModal = true;
});
modal.onClosed.subscribe(() => {
this.showingModal = false;
});
childComponent.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
modal.close();
this.selectedProviderType = provider;
await this.init();
});
childComponent.onRecoverSelected.subscribe(() => {
modal.close();
});
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
apiService: ApiService,
platformUtilsService: PlatformUtilsService,
syncService: SyncService,
environmentService: EnvironmentService,
private modalService: ModalService,
stateService: StateService,
route: ActivatedRoute,
logService: LogService,
twoFactorService: TwoFactorService,
appIdService: AppIdService
) {
super(
authService,
router,
i18nService,
apiService,
platformUtilsService,
window,
environmentService,
stateService,
route,
logService,
twoFactorService,
appIdService
);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
}
async anotherMethod() {
const [modal, childComponent] = await this.modalService.openViewRef(
TwoFactorOptionsComponent,
this.twoFactorOptionsModal
);
modal.onShown.subscribe(() => {
this.showingModal = true;
});
modal.onClosed.subscribe(() => {
this.showingModal = false;
});
childComponent.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
modal.close();
this.selectedProviderType = provider;
await this.init();
});
childComponent.onRecoverSelected.subscribe(() => {
modal.close();
});
}
async submit() {
await super.submit();
if (this.captchaSiteKey) {
const content = document.getElementById("content") as HTMLDivElement;
content.setAttribute("style", "width:335px");
}
}
}

View File

@ -1,78 +1,122 @@
<form id="update-temp-password-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<app-callout type="warning" title="{{'updateMasterPassword' | i18n}}">
{{'updateMasterPasswordWarning' | i18n}}
</app-callout>
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
</app-callout>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{'masterPass' | i18n}}
<strong class="sub-label text-{{masterPasswordScoreStyle.Color}}"
*ngIf="masterPasswordScoreStyle.Text">
{{masterPasswordScoreStyle.Text}}
</strong>
</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPassword" class="monospaced" [(ngModel)]="masterPassword" required
[appAutofocus]="masterPassword === ''" (input)="updatePasswordStrength()">
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="progress">
<div class="progress-bar bg-{{masterPasswordScoreStyle.Color}}" role="progressbar"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"
[ngStyle]="{width: (masterPasswordScoreStyle.Width + '%')}"
attr.aria-valuenow="{{masterPasswordScoreStyle.Width}}"></div>
</div>
</div>
<div class="content">
<app-callout type="warning" title="{{ 'updateMasterPassword' | i18n }}">
{{ "updateMasterPasswordWarning" | i18n }}
</app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{ "masterPass" | i18n }}
<strong
class="sub-label text-{{ masterPasswordScoreStyle.Color }}"
*ngIf="masterPasswordScoreStyle.Text"
>
{{ masterPasswordScoreStyle.Text }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
[appAutofocus]="masterPassword === ''"
(input)="updatePasswordStrength()"
appInputVerbatim
/>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPasswordRetype" class="monospaced" [(ngModel)]="masterPasswordRetype" required>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword(false)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
<div class="progress">
<div
class="progress-bar bg-{{ masterPasswordScoreStyle.Color }}"
role="progressbar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
[ngStyle]="{ width: masterPasswordScoreStyle.Width + '%' }"
attr.aria-valuenow="{{ masterPasswordScoreStyle.Width }}"
></div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint">
</div>
</div>
<div class="box-footer">
{{'masterPassHintDesc' | i18n}}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{'submit' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a (click)="logOut()" class="btn block">{{'logOut' | i18n}}</a>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword(true)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint" />
</div>
</div>
<div class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<b [hidden]="form.loading">{{ "submit" | i18n }}</b>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button type="button" (click)="logOut()" class="btn block">{{ "logOut" | i18n }}</button>
</div>
</div>
</form>

View File

@ -1,65 +1,80 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.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 { 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 { SyncService } from 'jslib-common/abstractions/sync.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from 'jslib-angular/components/update-temp-password.component';
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "jslib-angular/components/update-temp-password.component";
import { ApiService } from "jslib-common/abstractions/api.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 { 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";
interface MasterPasswordScore {
Color: string;
Text: string;
Width: number;
Color: string;
Text: string;
Width: number;
}
@Component({
selector: 'app-update-temp-password',
templateUrl: 'update-temp-password.component.html',
selector: "app-update-temp-password",
templateUrl: "update-temp-password.component.html",
})
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {
get masterPasswordScoreStyle(): MasterPasswordScore {
const scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
switch (this.masterPasswordScore) {
case 4:
return {
Color: 'bg-success',
Text: 'strong',
Width: scoreWidth,
};
case 3:
return {
Color: 'bg-primary',
Text: 'good',
Width: scoreWidth,
};
case 2:
return {
Color: 'bg-warning',
Text: 'weak',
Width: scoreWidth,
};
default:
return {
Color: 'bg-danger',
Text: 'weak',
Width: scoreWidth,
};
}
}
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService, policyService: PolicyService,
cryptoService: CryptoService, userService: UserService,
messagingService: MessagingService, apiService: ApiService,
syncService: SyncService, logService: LogService) {
super(i18nService, platformUtilsService, passwordGenerationService, policyService, cryptoService,
userService, messagingService, apiService, syncService, logService);
get masterPasswordScoreStyle(): MasterPasswordScore {
const scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
switch (this.masterPasswordScore) {
case 4:
return {
Color: "bg-success",
Text: "strong",
Width: scoreWidth,
};
case 3:
return {
Color: "bg-primary",
Text: "good",
Width: scoreWidth,
};
case 2:
return {
Color: "bg-warning",
Text: "weak",
Width: scoreWidth,
};
default:
return {
Color: "bg-danger",
Text: "weak",
Width: scoreWidth,
};
}
}
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService,
policyService: PolicyService,
cryptoService: CryptoService,
messagingService: MessagingService,
apiService: ApiService,
syncService: SyncService,
logService: LogService,
stateService: StateService
) {
super(
i18nService,
platformUtilsService,
passwordGenerationService,
policyService,
cryptoService,
messagingService,
apiService,
stateService,
syncService,
logService
);
}
}

View File

@ -1,24 +1,45 @@
<app-callout type="info" *ngIf="vaultTimeoutPolicy">
{{'vaultTimeoutPolicyInEffect' | i18n : vaultTimeoutPolicyHours : vaultTimeoutPolicyMinutes}}
{{ "vaultTimeoutPolicyInEffect" | i18n: vaultTimeoutPolicyHours:vaultTimeoutPolicyMinutes }}
</app-callout>
<div [formGroup]="form">
<div class="form-group">
<label for="vaultTimeout">{{'vaultTimeout' | i18n}}</label>
<select id="vaultTimeout" name="VaultTimeout" formControlName="vaultTimeout" class="form-control" appAutofocus>
<option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{o.name}}</option>
</select>
<small class="form-text text-muted">{{'vaultTimeoutDesc' | i18n}}</small>
<div class="form-group">
<label for="vaultTimeout">{{ "vaultTimeout" | i18n }}</label>
<select
id="vaultTimeout"
name="VaultTimeout"
formControlName="vaultTimeout"
class="form-control"
>
<option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="form-text text-muted">{{ "vaultTimeoutDesc" | i18n }}</small>
</div>
<div class="form-group row" *ngIf="showCustom" formGroupName="custom">
<div class="col">
<label for="hours">{{ "hours" | i18n }}</label>
<input
id="hours"
class="form-control"
type="number"
min="0"
name="hours"
formControlName="hours"
/>
</div>
<div class="form-group row" *ngIf="showCustom" formGroupName="custom">
<div class="col">
<label for="hours">{{'hours' | i18n}}</label>
<input id="hours" class="form-control" type="number" min="0" name="hours" formControlName="hours">
</div>
<div class="col">
<label for="minutes">{{'minutes' | i18n}}</label>
<input id="minutes" class="form-control" type="number" min="0" max="59" name="minutes" formControlName="minutes">
</div>
<div class="col">
<label for="minutes">{{ "minutes" | i18n }}</label>
<input
id="minutes"
class="form-control"
type="number"
min="0"
max="59"
name="minutes"
formControlName="minutes"
/>
</div>
<div class="form-group"></div> <!-- Styling fix -->
</div>
<div class="form-group"></div>
<!-- Styling fix -->
</div>

View File

@ -1,28 +1,22 @@
import { Component } from '@angular/core';
import {
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
} from '@angular/forms';
import { Component } from "@angular/core";
import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from "@angular/forms";
import {
VaultTimeoutInputComponent as VaultTimeoutInputComponentBase
} from 'jslib-angular/components/settings/vault-timeout-input.component';
import { VaultTimeoutInputComponent as VaultTimeoutInputComponentBase } from "jslib-angular/components/settings/vault-timeout-input.component";
@Component({
selector: 'app-vault-timeout-input',
templateUrl: 'vault-timeout-input.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VaultTimeoutInputComponent,
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: VaultTimeoutInputComponent,
},
],
selector: "app-vault-timeout-input",
templateUrl: "vault-timeout-input.component.html",
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VaultTimeoutInputComponent,
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: VaultTimeoutInputComponent,
},
],
})
export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {
}
export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {}

View File

@ -1,73 +1,70 @@
import { NgModule } from '@angular/core';
import {
RouterModule,
Routes,
} from '@angular/router';
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
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 { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { LockGuardService } from "jslib-angular/services/lock-guard.service";
import { HintComponent } from './accounts/hint.component';
import { LockComponent } from './accounts/lock.component';
import { LoginComponent } from './accounts/login.component';
import { RegisterComponent } from './accounts/register.component';
import { RemovePasswordComponent } from './accounts/remove-password.component';
import { SetPasswordComponent } from './accounts/set-password.component';
import { SsoComponent } from './accounts/sso.component';
import { TwoFactorComponent } from './accounts/two-factor.component';
import { UpdateTempPasswordComponent } from './accounts/update-temp-password.component';
import { LoginGuardService } from "../services/loginGuard.service";
import { SendComponent } from './send/send.component';
import { VaultComponent } from './vault/vault.component';
import { HintComponent } from "./accounts/hint.component";
import { LockComponent } from "./accounts/lock.component";
import { LoginComponent } from "./accounts/login.component";
import { RegisterComponent } from "./accounts/register.component";
import { RemovePasswordComponent } from "./accounts/remove-password.component";
import { SetPasswordComponent } from "./accounts/set-password.component";
import { SsoComponent } from "./accounts/sso.component";
import { TwoFactorComponent } from "./accounts/two-factor.component";
import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component";
import { SendComponent } from "./send/send.component";
import { VaultComponent } from "./vault/vault.component";
const routes: Routes = [
{ path: '', redirectTo: '/vault', pathMatch: 'full' },
{
path: 'lock',
component: LockComponent,
canActivate: [LockGuardService],
},
{
path: 'login',
component: LoginComponent,
canActivate: [UnauthGuardService],
},
{ path: '2fa', component: TwoFactorComponent },
{ path: 'register', component: RegisterComponent },
{
path: 'vault',
component: VaultComponent,
canActivate: [AuthGuardService],
},
{ path: 'hint', component: HintComponent },
{ path: 'set-password', component: SetPasswordComponent },
{ path: 'sso', component: SsoComponent },
{
path: 'send',
component: SendComponent,
canActivate: [AuthGuardService],
},
{
path: 'update-temp-password',
component: UpdateTempPasswordComponent,
canActivate: [AuthGuardService],
},
{
path: 'remove-password',
component: RemovePasswordComponent,
canActivate: [AuthGuardService],
data: { titleId: 'removeMasterPassword' },
},
{ path: "", redirectTo: "/vault", pathMatch: "full" },
{
path: "lock",
component: LockComponent,
canActivate: [LockGuardService],
},
{
path: "login",
component: LoginComponent,
canActivate: [LoginGuardService],
},
{ path: "2fa", component: TwoFactorComponent },
{ path: "register", component: RegisterComponent },
{
path: "vault",
component: VaultComponent,
canActivate: [AuthGuardService],
},
{ path: "hint", component: HintComponent },
{ path: "set-password", component: SetPasswordComponent },
{ path: "sso", component: SsoComponent },
{
path: "send",
component: SendComponent,
canActivate: [AuthGuardService],
},
{
path: "update-temp-password",
component: UpdateTempPasswordComponent,
canActivate: [AuthGuardService],
},
{
path: "remove-password",
component: RemovePasswordComponent,
canActivate: [AuthGuardService],
data: { titleId: "removeMasterPassword" },
},
];
@NgModule({
imports: [RouterModule.forRoot(routes, {
useHash: true,
/*enableTracing: true,*/
})],
exports: [RouterModule],
imports: [
RouterModule.forRoot(routes, {
useHash: true,
/*enableTracing: true,*/
}),
],
exports: [RouterModule],
})
export class AppRoutingModule { }
export class AppRoutingModule {}

Some files were not shown because too many files have changed in this diff Show More