1
0
mirror of https://github.com/bitwarden/web synced 2025-12-06 00:03:28 +00:00

Compare commits

..

74 Commits

Author SHA1 Message Date
Joseph Flinn
ec80782d8f Version check typo (#1717)
* fixing dumb issue

* Fixing another dumb mistake

(cherry picked from commit ad0512e344)
2022-05-31 18:09:11 -07:00
Joseph Flinn
3acee89646 Updating the path for the Release Version Check (#1716)
(cherry picked from commit 8fe9504cd1)
2022-05-31 17:50:37 -07:00
Joseph Flinn
589faf3608 Updating the version check to the new Github action (#1714)
* Updating the version check to the new Github action

* Update .github/workflows/release.yml

Co-authored-by: Micaiah Martin <77340197+mimartin12@users.noreply.github.com>

* switching action branch tag to commit tag

Co-authored-by: Micaiah Martin <77340197+mimartin12@users.noreply.github.com>
(cherry picked from commit b8aa25b981)
2022-05-31 16:13:41 -07:00
github-actions[bot]
639cc461ae Bumped version to 2022.05.0 (#1715)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit 5d09ddbc8d)
2022-05-31 16:13:31 -07:00
Thomas Rittson
4562920d30 Update jslib 2022-05-27 13:12:37 +10:00
Matt Gibson
c6f4e4de64 update jslib 2022-05-26 13:19:49 -04:00
Matt Gibson
f323963a4e [PS-655] Add Organization_SponsorshipsSynced event type. (#1696)
* Add `Organization_SponsorshipsSynced` event type.

Update events display to handle events triggered by installations rather than users

* Update jslib

(cherry picked from commit 5a78853de5)
2022-05-26 13:06:46 -04:00
Thomas Rittson
f6c8b6d8cb [EC-177] Update Key Connector error message (#1705)
* Update Key Connector error message

* Update jslib
2022-05-26 07:32:52 +10:00
Robyn MacCallum
11f34be18e [SG-345] Switch in-line "+" add org button to more explicit version (#1703)
* switch in-line plus add org button to more explicit version

* Add spaces to line up text better
2022-05-25 10:23:43 -04:00
Robyn MacCallum
647e9555d0 Fix 'Link SSO' not appearing (#1702) 2022-05-23 19:44:54 -04:00
Matt Gibson
ad7b6e6388 Revert "[PS-655] Add Organization_SponsorshipsSynced event type. (#1696)"
This reverts commit aa29a07678.
2022-05-19 11:52:40 -04:00
Matt Gibson
718ca197b5 Revert "update jslib"
This reverts commit 338d593cd1.
2022-05-19 11:52:36 -04:00
Matt Gibson
338d593cd1 update jslib 2022-05-19 11:41:13 -04:00
Matt Gibson
aa29a07678 [PS-655] Add Organization_SponsorshipsSynced event type. (#1696)
* Add `Organization_SponsorshipsSynced` event type.

Update events display to handle events triggered by installations rather than users

* Update jslib

(cherry picked from commit 5a78853de5)
2022-05-19 11:39:06 -04:00
Robyn MacCallum
0722522801 Make spacing match other rows in vault filter (#1695)
* Make spacing match other rows in vault filter

* Add spaces in headings
2022-05-18 10:14:39 -04:00
Oscar Hinton
5731c6449b [EC-203] Add logic for falling back to the users vault (#1688)
* Add logic for falling back to the users vault

* Update error message

* Add jira ticket
2022-05-17 12:47:21 -04:00
Robyn MacCallum
eaf083be44 Fix switcher not appearing for users affected by single org policy (#1689) 2022-05-17 10:06:20 -04:00
Robyn MacCallum
8435c85c3c [SG-16] Fix various small bugs (#1686)
* Fix all items showing in No Folder

* Fix folders not showing for orgs

* Fix missing toasts if there are errors with org options
2022-05-17 09:23:12 -04:00
Justin Baur
3d1c2dad74 Fix Defects (#1683) 2022-05-16 09:47:24 -04:00
Jake Fink
2f632f7096 change revoke text (#1684) 2022-05-16 09:15:45 -04:00
Kyle Spearrin
9b63c81b56 remove hostname from simplelogin. update jslib (#1680) 2022-05-13 15:36:48 -04:00
Addison Beck
34cb463e94 [fix] Various Trash filter bugs from EUVR (#1681)
* [fix] Hide the Add Item button when filtering for trash

* [fix] Use correct bulk actions for trash filter
2022-05-13 15:17:19 -04:00
Addison Beck
7bbe3d7b28 [fix] Add max width to vault filter buttons to keep content unified (#1678) 2022-05-13 15:17:09 -04:00
Addison Beck
0564b52f1f [fix] Check policies when reloading organizations for filters (#1677) 2022-05-13 15:16:58 -04:00
Oscar Hinton
cb1f08d74b [EC-200] Handle an edge case where ciphers were not selectable (#1674)
(cherry picked from commit be30d47038)
2022-05-13 15:33:33 +02:00
Thomas Rittson
f3bf972d26 Revert accidental cnesting of org routes (#1670) 2022-05-13 08:32:26 +10:00
Oscar Hinton
02256aad87 [SG-279] Fix unlock button not working after logging in through SSO (#1672)
(cherry picked from commit f4f3e8c574)
2022-05-12 16:22:11 +02:00
Oscar Hinton
466e0c8c35 [SG-267] Exposed passwords report: console error (#1671)
(cherry picked from commit 3367736c7e)
2022-05-12 15:48:08 +02:00
Vincent Salucci
0543e76432 [bug:euvr] Self-hosted instance hiding subscription nav item (#1669) 2022-05-12 08:44:14 -04:00
Thomas Rittson
34527160fc Fix permission checking when accessing manage SSO (#1663) 2022-05-12 09:12:34 +10:00
Thomas Rittson
f590fe5435 Update Go Premium link (#1660) 2022-05-12 06:54:41 +10:00
Vincent Salucci
0f64c16c18 [bug:euvr] Change password init fix (#1667) 2022-05-11 15:44:45 -04:00
Vincent Salucci
1632a24b1e [bug:euvr] Create Organization Updates (#1664) 2022-05-11 15:44:18 -04:00
Addison Beck
0691af1dcd [fix] Update the navbar when creating an organization for the first time (#1668) 2022-05-11 14:51:05 -04:00
Robyn MacCallum
3e59617fb1 Use correct localization string (#1665) 2022-05-11 13:37:26 -04:00
Thomas Rittson
268b16ca98 [SG-234] Fix account menu icon colour (#1653) 2022-05-11 08:40:24 +10:00
Robyn MacCallum
9f977cfc68 Various small vault filter fixes (#1659) 2022-05-10 17:33:53 -04:00
Matt Gibson
9627782a04 Feature/self hosted families for enterprise (#1658)
* Remove F4E card from vault page

* Billing Sync Api Keys / Free Bitwarden Families Page updates (#1597)

* Work on billing sync key maintenance

* Work on i18n

* Work on localizations

* Work on sync status text

* Work on sync status copy

* Assign default null value

* Work on billing sync UI

* Work on sponsorships page

* Add inline form validation

* Fix lint

* Prettier

* Final touches on designs from Figma

* Polish

* Address PR feedback

* Use more generic validator

* Update jslib

* Update jslib

* Remove comment

* [PS-248] Feature/manage billing sync connection (#1601)

* Fix modal footer rounded corners

* Add billing sync messages

* Add manage billing sync to self hosted org subscription component

* Handle create vs update in component

* Update src/app/settings/billing-sync-key.component.html

Co-authored-by: Justin Baur <admin@justinbaur.com>

* update jslib

* Linter fixes

* Remove duplicate modalService

Co-authored-by: Justin Baur <admin@justinbaur.com>

* Fix/f4e self hosting section (#1616)

* Translate messages

* Move download license subform to below download license button

* Chore/merge/self hosted families for enterprise (#1617)

* [EC-142] Fix error during import of 1pux containing new email field format (#1585)

* Pull in changes made on https://github.com/bitwarden/jslib/pull/758

* Update package-lock.json

* Add descriptions to vague messages (#1586)

* Add descriptions to vague messages

* Fix typo

* Autosync the updated translations (#1590)

Co-authored-by: github-actions <>

* Bumping the pinned commit for the download-artifact to bypass the github api issue (#1598)

* Update jslib (#1602)

* Update jslib

* Update name of UserVerificationComponent

* Bumped version to 2.28.0 (#1603)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* [EC-161] Bump braintree (#1606)

* [PS-211] [PS-212] Make Generator page accessible (#1607)

* Fix grouping of radiobutton inputs

* Add role=radiogroup

* Add aria-labelledBy to radio button groups

* Add reorganization notice (#1610)

* Add aria attributes to password gen options (#1611)

* [EC-143] [BEEEP] Allow linking to ciphers (#1579)

Co-authored-by: Matt Gibson <mgibson@bitwarden.com>

* update jslib

Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com>
Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
Co-authored-by: Oscar Hinton <oscar@oscarhinton.com>

* Chore/merge/self hosted families for enterprise (#1621)

* [EC-142] Fix error during import of 1pux containing new email field format (#1585)

* Pull in changes made on https://github.com/bitwarden/jslib/pull/758

* Update package-lock.json

* Add descriptions to vague messages (#1586)

* Add descriptions to vague messages

* Fix typo

* Autosync the updated translations (#1590)

Co-authored-by: github-actions <>

* Bumping the pinned commit for the download-artifact to bypass the github api issue (#1598)

* Update jslib (#1602)

* Update jslib

* Update name of UserVerificationComponent

* Bumped version to 2.28.0 (#1603)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* [EC-161] Bump braintree (#1606)

* [PS-211] [PS-212] Make Generator page accessible (#1607)

* Fix grouping of radiobutton inputs

* Add role=radiogroup

* Add aria-labelledBy to radio button groups

* Add reorganization notice (#1610)

* Add aria attributes to password gen options (#1611)

* [EC-143] [BEEEP] Allow linking to ciphers (#1579)

Co-authored-by: Matt Gibson <mgibson@bitwarden.com>

* Fix login sponsorship redirect (#1620)

Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com>
Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
Co-authored-by: Oscar Hinton <oscar@oscarhinton.com>

* Change to new name (#1642)

* Chore/merge/self hosted families for enterprise (#1651)

* [EC-142] Fix error during import of 1pux containing new email field format (#1585)

* Pull in changes made on https://github.com/bitwarden/jslib/pull/758

* Update package-lock.json

* Add descriptions to vague messages (#1586)

* Add descriptions to vague messages

* Fix typo

* Autosync the updated translations (#1590)

Co-authored-by: github-actions <>

* Bumping the pinned commit for the download-artifact to bypass the github api issue (#1598)

* Update jslib (#1602)

* Update jslib

* Update name of UserVerificationComponent

* Bumped version to 2.28.0 (#1603)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* [EC-161] Bump braintree (#1606)

* [PS-211] [PS-212] Make Generator page accessible (#1607)

* Fix grouping of radiobutton inputs

* Add role=radiogroup

* Add aria-labelledBy to radio button groups

* Add reorganization notice (#1610)

* Add aria attributes to password gen options (#1611)

* [EC-143] [BEEEP] Allow linking to ciphers (#1579)

Co-authored-by: Matt Gibson <mgibson@bitwarden.com>

* Fix login sponsorship redirect (#1620)

* Contribution Documentation edits (#1599)

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'

* Add description for the A-Z & a-z items (#1615)

* Add description for reports message (#1600)

Add "Vault Health Reports can be used to evaluate the security of your Bitwarden Personal or Organization Vault" description to the source string, "Identify and close security gaps in your online accounts by clicking the reports below."

* [PS-301] Load OssModule from BitwardenLicense (#1626)

* Bumped version to 2.28.1 (#1629)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* [EC-154] [BEEEP] Remove factory providers in Angular DI (#1609)

* use InjectionTokens

* Use InitService

* PS-79 Updated two-factor component to to align to jslib change to send the deviceId on 2fa email resend code (#1624)

* [PS-74] Fix user authentication state checks (#1632)

* Update to use new authStatus method

* Delete unused services and import

* update jslib

* [PS-381] Fix locale being empty when not configuring a language (#1631)

* Forwarded email providers to username generator (#1628)

* forwarded emails

* firefox relay

* remove firefox relay

* update jslib ref

* remove dupe logService

* Update localization description for 'random' (#1633)

Adding description string for 'random'

* DEVOPS-758 - Move Web deploy from GitHub Pages to CloudFlare Pages (#1627)

* [EC-189] Resolve password reprompt not appearing on linkable cipher (#1643)

* [SG-220] End User Vault Refresh (#1640)

* Add premium badge component (#1525)

* [Vault Refresh] Nav update and Options -> Preferences (#1530)

* Update jslib

* [End User Vault Refresh] Security sub-page (#1538)

* [End User Vault Refresh] Security section

* Updated routing module

* Update routing for change-password

* Updated buttons of all modified classes // imported button module

* Converted modified class to use bit-callout

* removed comments

* Update small button to current cl button

* Update jslib and consequential updates

* [End User Vault Refresh] Vault - remove Org and Provider cards (#1529)

* Update reports page (#1536)

* [End User Vault Refresh] Organizations - updated nav and route permissions (#1551)

* Add Organizations link to navbar

* Update route permissions and guards

* Use NavigationPermissionsService to unify route permissions

* Rename "My Vault" to "Vaults" (#1569)

* [euvr] Adjust Vault width based on card visibility (#1588)

* [SG-31 End User Vault Refresh] Account Menu updates (#1596)

* Add menuModule

* Use bit-menu for account menu

* Fix styling, replace CSS with TW

* Change out bootstrap styling

* Fix styling

* Fix styling

* Rename My Account to Account Settings

* WIP use Avatar for account menu

* Revert "WIP use Avatar for account menu"

This reverts commit d58bea4874.

* Update jslib from feature branch

* [End User Vault Refresh] SG-16: Organization filters (#1595)

* [feature] Base implementation of EUVR filter changes

* [refactor] Relocated vault-filters to app/modules

* [refactor] Reuse vault-filters component for organizations

* [refactor] Remove unused org filter component

* [bug] .gitmodules branch change

* [bug] Load organization filters after sync during login

* [refactor] Introduce a SharedModule

* [refactor] Created a home for loose components

* [refactor] Convert VaultComponent and OrgVaultComponent into a pair of modules

* [refactor] Implement <bit-menu> for organization filter actions

* [feature] Improve a11y standards of the vault filters module

* [bug] Recreate package-lock.json

* Fix build issue

* [bug] Remove duplicate this.go() call

* [fix] Use correct filter-buttons class

Co-authored-by: addison <addisonbeck1@gmail.com>
Co-authored-by: Hinton <oscar@oscarhinton.com>

* [SG-32] Add Ownership badge to vault items (#1623)

* [feature] Base implementation of EUVR filter changes

* [refactor] Relocated vault-filters to app/modules

* [refactor] Reuse vault-filters component for organizations

* [refactor] Remove unused org filter component

* [bug] .gitmodules branch change

* [bug] Load organization filters after sync during login

* [refactor] Introduce a SharedModule

* [refactor] Created a home for loose components

* [refactor] Convert VaultComponent and OrgVaultComponent into a pair of modules

* [refactor] Implement <bit-menu> for organization filter actions

* [feature] Improve a11y standards of the vault filters module

* [bug] Recreate package-lock.json

* Fix build issue

* [bug] Remove duplicate this.go() call

* Add organization owner badge to vault items

* Fix capitalization

* Re-organize new components into modules

* Use tailwind css class

Co-authored-by: addison <addisonbeck1@gmail.com>
Co-authored-by: Hinton <oscar@oscarhinton.com>

* [EUVR] Merge master into feature branch (#1637)

* Update jslib (#1602)

* Update jslib

* Update name of UserVerificationComponent

* Bumped version to 2.28.0 (#1603)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* [EC-161] Bump braintree (#1606)

* [PS-211] [PS-212] Make Generator page accessible (#1607)

* Fix grouping of radiobutton inputs

* Add role=radiogroup

* Add aria-labelledBy to radio button groups

* Add reorganization notice (#1610)

* Add aria attributes to password gen options (#1611)

* [EC-143] [BEEEP] Allow linking to ciphers (#1579)

Co-authored-by: Matt Gibson <mgibson@bitwarden.com>

* Fix login sponsorship redirect (#1620)

* Contribution Documentation edits (#1599)

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'

* Add description for the A-Z & a-z items (#1615)

* Add description for reports message (#1600)

Add "Vault Health Reports can be used to evaluate the security of your Bitwarden Personal or Organization Vault" description to the source string, "Identify and close security gaps in your online accounts by clicking the reports below."

* [PS-301] Load OssModule from BitwardenLicense (#1626)

* Bumped version to 2.28.1 (#1629)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* [EC-154] [BEEEP] Remove factory providers in Angular DI (#1609)

* use InjectionTokens

* Use InitService

* PS-79 Updated two-factor component to to align to jslib change to send the deviceId on 2fa email resend code (#1624)

* [PS-74] Fix user authentication state checks (#1632)

* Update to use new authStatus method

* Delete unused services and import

* update jslib

* [PS-381] Fix locale being empty when not configuring a language (#1631)

* Forwarded email providers to username generator (#1628)

* forwarded emails

* firefox relay

* remove firefox relay

* update jslib ref

* remove dupe logService

* Update localization description for 'random' (#1633)

Adding description string for 'random'

* DEVOPS-758 - Move Web deploy from GitHub Pages to CloudFlare Pages (#1627)

* Update jslib

* Run npm i after merge with master

* Update name of UserVerificationComponent

* Fix lazy loading of routing modules

* Routing modules should have routing in their name

* Revert "Fix lazy loading of routing modules"

This reverts commit 59d4e6e06c.

* Do not eagerly load feature modules

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Oscar Hinton <oscar@oscarhinton.com>
Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
Co-authored-by: Matt Gibson <mgibson@bitwarden.com>
Co-authored-by: dwbit <98768076+dwbit@users.noreply.github.com>
Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>

* Do not render org options menu until loaded (#1638)

* [SG-31 End User Vault Refresh] Update cipher options menu (#1593)

* Update Vault cipher option menus

* Update Send list to use same style

* [SG-207] [EUVR] Remove Organizations from Settings page (#1619)

* [fix] Cut off overflow text for link buttons (#1639)

* [SG-225] Remove BaseGuard (#1641)

* [SG-34 End User Vault Refresh] Organization Switcher (#1550)

* [euvr] Subscription/Billing updates (#1576)

* [euvr] Subscription changes

* Revert testing bang

* Removed final instance of getUserBilling

* Moved to feature/endUserVaultRefresh remote branch and updated to latest

* Removed org-billing changes

* Updated premium component header

* Updated stateservice path

* Updated billing component name

* Reverting org-billing decouple

* Using tailwind classes for CL objects

* Added TODO

* Removed divider for components within new tab nav

* Update jslib/add components to loose-components module

* Updated routing lazy load module name to match existing pattern

* Fixed bug with redirect // Added button type // Removed headers for tabbed pages

* Revert changes to .gitmodules

* [dep] Update jslib

Co-authored-by: Oscar Hinton <oscar@oscarhinton.com>
Co-authored-by: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com>
Co-authored-by: Vincent Salucci <vincesalucci21@gmail.com>
Co-authored-by: addison <addisonbeck1@gmail.com>
Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
Co-authored-by: Matt Gibson <mgibson@bitwarden.com>
Co-authored-by: dwbit <98768076+dwbit@users.noreply.github.com>
Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>

* [EC-196] Move provider last in navbar (#1647)

* [euvr] Settings -> Account Settings label (#1648)

* Check for null cipher in edit (#1646)

Null ciphers signify a _new_ cipher, so no password repromt is required

* [fix] Add missing Create Org button to filters (#1650)

* update jslib

* Fix package-lock

* Fix component declarations

Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com>
Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
Co-authored-by: Oscar Hinton <oscar@oscarhinton.com>
Co-authored-by: dwbit <98768076+dwbit@users.noreply.github.com>
Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>
Co-authored-by: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com>
Co-authored-by: Vincent Salucci <vincesalucci21@gmail.com>
Co-authored-by: addison <addisonbeck1@gmail.com>
Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com>

* Update jslib

* update jslib

Co-authored-by: Justin Baur <admin@justinbaur.com>
Co-authored-by: Justin Baur <136baur@gmail.com>
Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com>
Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
Co-authored-by: Oscar Hinton <oscar@oscarhinton.com>
Co-authored-by: dwbit <98768076+dwbit@users.noreply.github.com>
Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>
Co-authored-by: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com>
Co-authored-by: Vincent Salucci <vincesalucci21@gmail.com>
Co-authored-by: addison <addisonbeck1@gmail.com>
Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com>
2022-05-10 16:11:59 -05:00
Addison Beck
c5877cd063 [fix] Remove an extra sync call (#1657) 2022-05-10 13:42:16 -04:00
Robyn MacCallum
136e8897ae Check for null cipher in edit (#1656) 2022-05-10 13:33:16 -04:00
Addison Beck
81c6a4b1df [fix] Override collection filter init to consider organization vault (#1652)
* [fix] Override collection filter init to consider organization vault

* [dep] Update jslib
2022-05-10 09:04:39 -04:00
Thomas Rittson
da62cec6f0 Remove testing requirements from PR template (#1654) 2022-05-10 15:03:35 +02:00
Robyn MacCallum
474df5ba5e [SG-232 & SG-251] Fix color issues with organization badge (#1649)
* Fix color issues with organization badge

* Use tokenService to get account name

* Remove unused import
2022-05-10 07:15:29 -04:00
Thomas Rittson
2c609fc6fd [EC-151] Hide Subscription/Billing information for Provider-managed organizations (#1614) 2022-05-10 17:41:52 +10:00
Addison Beck
f8a2fae82b [fix] Add missing Create Org button to filters (#1650) 2022-05-09 21:29:18 -04:00
Matt Gibson
2f04c07262 Check for null cipher in edit (#1646)
Null ciphers signify a _new_ cipher, so no password repromt is required
2022-05-09 13:53:31 -05:00
Vincent Salucci
f81195c920 [euvr] Settings -> Account Settings label (#1648) 2022-05-09 12:13:27 -04:00
Oscar Hinton
d031b53c74 [EC-196] Move provider last in navbar (#1647) 2022-05-09 18:07:31 +02:00
Thomas Rittson
468007a984 [SG-220] End User Vault Refresh (#1640)
* Add premium badge component (#1525)

* [Vault Refresh] Nav update and Options -> Preferences (#1530)

* Update jslib

* [End User Vault Refresh] Security sub-page (#1538)

* [End User Vault Refresh] Security section

* Updated routing module

* Update routing for change-password

* Updated buttons of all modified classes // imported button module

* Converted modified class to use bit-callout

* removed comments

* Update small button to current cl button

* Update jslib and consequential updates

* [End User Vault Refresh] Vault - remove Org and Provider cards (#1529)

* Update reports page (#1536)

* [End User Vault Refresh] Organizations - updated nav and route permissions (#1551)

* Add Organizations link to navbar

* Update route permissions and guards

* Use NavigationPermissionsService to unify route permissions

* Rename "My Vault" to "Vaults" (#1569)

* [euvr] Adjust Vault width based on card visibility (#1588)

* [SG-31 End User Vault Refresh] Account Menu updates (#1596)

* Add menuModule

* Use bit-menu for account menu

* Fix styling, replace CSS with TW

* Change out bootstrap styling

* Fix styling

* Fix styling

* Rename My Account to Account Settings

* WIP use Avatar for account menu

* Revert "WIP use Avatar for account menu"

This reverts commit d58bea4874.

* Update jslib from feature branch

* [End User Vault Refresh] SG-16: Organization filters (#1595)

* [feature] Base implementation of EUVR filter changes

* [refactor] Relocated vault-filters to app/modules

* [refactor] Reuse vault-filters component for organizations

* [refactor] Remove unused org filter component

* [bug] .gitmodules branch change

* [bug] Load organization filters after sync during login

* [refactor] Introduce a SharedModule

* [refactor] Created a home for loose components

* [refactor] Convert VaultComponent and OrgVaultComponent into a pair of modules

* [refactor] Implement <bit-menu> for organization filter actions

* [feature] Improve a11y standards of the vault filters module

* [bug] Recreate package-lock.json

* Fix build issue

* [bug] Remove duplicate this.go() call

* [fix] Use correct filter-buttons class

Co-authored-by: addison <addisonbeck1@gmail.com>
Co-authored-by: Hinton <oscar@oscarhinton.com>

* [SG-32] Add Ownership badge to vault items (#1623)

* [feature] Base implementation of EUVR filter changes

* [refactor] Relocated vault-filters to app/modules

* [refactor] Reuse vault-filters component for organizations

* [refactor] Remove unused org filter component

* [bug] .gitmodules branch change

* [bug] Load organization filters after sync during login

* [refactor] Introduce a SharedModule

* [refactor] Created a home for loose components

* [refactor] Convert VaultComponent and OrgVaultComponent into a pair of modules

* [refactor] Implement <bit-menu> for organization filter actions

* [feature] Improve a11y standards of the vault filters module

* [bug] Recreate package-lock.json

* Fix build issue

* [bug] Remove duplicate this.go() call

* Add organization owner badge to vault items

* Fix capitalization

* Re-organize new components into modules

* Use tailwind css class

Co-authored-by: addison <addisonbeck1@gmail.com>
Co-authored-by: Hinton <oscar@oscarhinton.com>

* [EUVR] Merge master into feature branch (#1637)

* Update jslib (#1602)

* Update jslib

* Update name of UserVerificationComponent

* Bumped version to 2.28.0 (#1603)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* [EC-161] Bump braintree (#1606)

* [PS-211] [PS-212] Make Generator page accessible (#1607)

* Fix grouping of radiobutton inputs

* Add role=radiogroup

* Add aria-labelledBy to radio button groups

* Add reorganization notice (#1610)

* Add aria attributes to password gen options (#1611)

* [EC-143] [BEEEP] Allow linking to ciphers (#1579)

Co-authored-by: Matt Gibson <mgibson@bitwarden.com>

* Fix login sponsorship redirect (#1620)

* Contribution Documentation edits (#1599)

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'

* Add description for the A-Z & a-z items (#1615)

* Add description for reports message (#1600)

Add "Vault Health Reports can be used to evaluate the security of your Bitwarden Personal or Organization Vault" description to the source string, "Identify and close security gaps in your online accounts by clicking the reports below."

* [PS-301] Load OssModule from BitwardenLicense (#1626)

* Bumped version to 2.28.1 (#1629)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* [EC-154] [BEEEP] Remove factory providers in Angular DI (#1609)

* use InjectionTokens

* Use InitService

* PS-79 Updated two-factor component to to align to jslib change to send the deviceId on 2fa email resend code (#1624)

* [PS-74] Fix user authentication state checks (#1632)

* Update to use new authStatus method

* Delete unused services and import

* update jslib

* [PS-381] Fix locale being empty when not configuring a language (#1631)

* Forwarded email providers to username generator (#1628)

* forwarded emails

* firefox relay

* remove firefox relay

* update jslib ref

* remove dupe logService

* Update localization description for 'random' (#1633)

Adding description string for 'random'

* DEVOPS-758 - Move Web deploy from GitHub Pages to CloudFlare Pages (#1627)

* Update jslib

* Run npm i after merge with master

* Update name of UserVerificationComponent

* Fix lazy loading of routing modules

* Routing modules should have routing in their name

* Revert "Fix lazy loading of routing modules"

This reverts commit 59d4e6e06c.

* Do not eagerly load feature modules

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Oscar Hinton <oscar@oscarhinton.com>
Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
Co-authored-by: Matt Gibson <mgibson@bitwarden.com>
Co-authored-by: dwbit <98768076+dwbit@users.noreply.github.com>
Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>

* Do not render org options menu until loaded (#1638)

* [SG-31 End User Vault Refresh] Update cipher options menu (#1593)

* Update Vault cipher option menus

* Update Send list to use same style

* [SG-207] [EUVR] Remove Organizations from Settings page (#1619)

* [fix] Cut off overflow text for link buttons (#1639)

* [SG-225] Remove BaseGuard (#1641)

* [SG-34 End User Vault Refresh] Organization Switcher (#1550)

* [euvr] Subscription/Billing updates (#1576)

* [euvr] Subscription changes

* Revert testing bang

* Removed final instance of getUserBilling

* Moved to feature/endUserVaultRefresh remote branch and updated to latest

* Removed org-billing changes

* Updated premium component header

* Updated stateservice path

* Updated billing component name

* Reverting org-billing decouple

* Using tailwind classes for CL objects

* Added TODO

* Removed divider for components within new tab nav

* Update jslib/add components to loose-components module

* Updated routing lazy load module name to match existing pattern

* Fixed bug with redirect // Added button type // Removed headers for tabbed pages

* Revert changes to .gitmodules

* [dep] Update jslib

Co-authored-by: Oscar Hinton <oscar@oscarhinton.com>
Co-authored-by: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com>
Co-authored-by: Vincent Salucci <vincesalucci21@gmail.com>
Co-authored-by: addison <addisonbeck1@gmail.com>
Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
Co-authored-by: Matt Gibson <mgibson@bitwarden.com>
Co-authored-by: dwbit <98768076+dwbit@users.noreply.github.com>
Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>
2022-05-09 08:21:52 -04:00
Oscar Hinton
bc054236ad [EC-189] Resolve password reprompt not appearing on linkable cipher (#1643) 2022-05-06 11:29:45 +02:00
Vince Grassia
1c31d090a3 DEVOPS-758 - Move Web deploy from GitHub Pages to CloudFlare Pages (#1627) 2022-05-03 12:21:27 -04:00
dwbit
f8d942c02c Update localization description for 'random' (#1633)
Adding description string for 'random'
2022-05-03 09:02:40 -04:00
Kyle Spearrin
248938ca00 Forwarded email providers to username generator (#1628)
* forwarded emails

* firefox relay

* remove firefox relay

* update jslib ref

* remove dupe logService
2022-05-02 10:32:15 -04:00
Oscar Hinton
06d95bb224 [PS-381] Fix locale being empty when not configuring a language (#1631) 2022-05-02 15:32:44 +02:00
Thomas Rittson
446f2027b4 [PS-74] Fix user authentication state checks (#1632)
* Update to use new authStatus method

* Delete unused services and import

* update jslib
2022-04-30 10:51:33 +10:00
Federico Maccaroni
1f0d496f21 PS-79 Updated two-factor component to to align to jslib change to send the deviceId on 2fa email resend code (#1624) 2022-04-29 13:05:05 -03:00
Thomas Rittson
2b03162bfd [EC-154] [BEEEP] Remove factory providers in Angular DI (#1609)
* use InjectionTokens

* Use InitService
2022-04-29 09:45:47 +02:00
github-actions[bot]
f586359610 Bumped version to 2.28.1 (#1629)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-04-28 11:50:04 -07:00
Matt Gibson
96641cf195 [PS-301] Load OssModule from BitwardenLicense (#1626) 2022-04-28 07:39:59 -05:00
dwbit
572758c598 Add description for reports message (#1600)
Add "Vault Health Reports can be used to evaluate the security of your Bitwarden Personal or Organization Vault" description to the source string, "Identify and close security gaps in your online accounts by clicking the reports below."
2022-04-27 17:06:50 -04:00
dwbit
df7db8ad07 Add description for the A-Z & a-z items (#1615) 2022-04-27 17:06:16 -04:00
dwbit
0439d37c14 Contribution Documentation edits (#1599)
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'
2022-04-27 17:05:30 -04:00
Matt Gibson
97f38aa654 Fix login sponsorship redirect (#1620) 2022-04-27 08:09:38 -05:00
Oscar Hinton
0444b78ad1 [EC-143] [BEEEP] Allow linking to ciphers (#1579)
Co-authored-by: Matt Gibson <mgibson@bitwarden.com>
2022-04-25 15:41:44 +02:00
Daniel James Smith
705251fbe2 Add aria attributes to password gen options (#1611) 2022-04-22 17:58:42 +02:00
Oscar Hinton
27853481d8 Add reorganization notice (#1610) 2022-04-22 14:25:02 +02:00
Thomas Rittson
c0511f25ca [PS-211] [PS-212] Make Generator page accessible (#1607)
* Fix grouping of radiobutton inputs

* Add role=radiogroup

* Add aria-labelledBy to radio button groups
2022-04-21 09:51:00 -04:00
Oscar Hinton
1be62ac222 [EC-161] Bump braintree (#1606) 2022-04-20 20:51:33 +02:00
github-actions[bot]
8304104a7a Bumped version to 2.28.0 (#1603)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-04-19 14:14:40 -07:00
Thomas Rittson
56808a7dbb Update jslib (#1602)
* Update jslib

* Update name of UserVerificationComponent
2022-04-19 09:49:59 +02:00
Joseph Flinn
609c13faf4 Bumping the pinned commit for the download-artifact to bypass the github api issue (#1598) 2022-04-14 16:05:20 -07:00
github-actions[bot]
62b20a5c6d Autosync the updated translations (#1590)
Co-authored-by: github-actions <>
2022-04-08 11:45:22 +02:00
Matt Gibson
d56bf1211e Add descriptions to vague messages (#1586)
* Add descriptions to vague messages

* Fix typo
2022-04-07 08:20:38 -05:00
Daniel James Smith
8831f96fc2 [EC-142] Fix error during import of 1pux containing new email field format (#1585)
* Pull in changes made on https://github.com/bitwarden/jslib/pull/758

* Update package-lock.json
2022-04-06 19:10:44 +02:00
217 changed files with 7535 additions and 7338 deletions

View File

@@ -21,10 +21,6 @@
<!--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)

View File

@@ -11,6 +11,7 @@ on:
branches-ignore:
- "l10n_master"
- "gh-pages"
- "deploy"
paths-ignore:
- '.github/workflows/**'

View File

@@ -19,8 +19,8 @@ jobs:
name: Setup
runs-on: ubuntu-20.04
outputs:
release_version: ${{ steps.version.outputs.package }}
tag_version: ${{ steps.version.outputs.tag }}
release_version: ${{ steps.version.outputs.version }}
tag_version: ${{ steps.version.outputs.version }}
branch_name: ${{ steps.branch.outputs.branch_name }}
steps:
- name: Branch check
@@ -38,20 +38,11 @@ jobs:
- name: Check Release Version
id: version
run: |
version=$( jq -r ".version" package.json)
previous_release_tag_version=$(
curl -sL https://api.github.com/repos/$GITHUB_REPOSITORY/releases/latest | jq -r ".tag_name"
)
if [ "v$version" == "$previous_release_tag_version" ] && \
[ "${{ github.event.inputs.release_type }}" == "Initial Release" ]; then
echo "[!] Already released v$version. Please bump version to continue"
exit 1
fi
echo "::set-output name=package::$version"
echo "::set-output name=tag::v$version"
uses: bitwarden/gh-actions/release-version-check@ea9fab01d76940267b4147cc1c4542431246b9f6
with:
release-type: ${{ github.event.inputs.release_type }}
project-type: ts
file: package.json
- name: Get branch name
id: branch
@@ -158,7 +149,7 @@ jobs:
ghpages-deploy:
name: Deploy Web Vault
name: Deploy Web Vault to GitHub Pages
runs-on: ubuntu-20.04
needs:
- setup
@@ -172,10 +163,10 @@ jobs:
with:
ref: gh-pages
- name: Create deploy branch
- name: Create gh-pages-deploy branch
run: |
git switch -c deploy-$_TAG_VERSION
git push -u origin deploy-$_TAG_VERSION
git switch -c gh-pages-deploy-$_TAG_VERSION
git push -u origin gh-pages-deploy-$_TAG_VERSION
- name: Checkout Repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
@@ -188,7 +179,7 @@ jobs:
git config --global url."https://".insteadOf ssh://
- name: Download latest cloud asset
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
with:
workflow: build.yml
workflow_conclusion: success
@@ -204,24 +195,88 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
target_branch: deploy-${{ needs.setup.outputs.tag_version }}
target_branch: gh-pages-deploy-${{ needs.setup.outputs.tag_version }}
build_dir: build
keep_history: true
commit_message: "Staging deploy ${{ needs.setup.outputs.release_version }}"
dry_run: ${{ github.event.inputs.release_type == 'Dry Run' }}
- name: Create Deploy PR
- name: Create GitHub Pages Deploy PR
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
env:
PR_BRANCH: deploy-${{ env._TAG_VERSION }}
PR_BRANCH: gh-pages-deploy-${{ env._TAG_VERSION }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr create --title "Deploy $_RELEASE_VERSION" \
gh pr create --title "Deploy $_RELEASE_VERSION to GitHub Pages" \
--body "Deploying $_RELEASE_VERSION" \
--base gh-pages \
--head "$PR_BRANCH"
cfpages-deploy:
name: Deploy Web Vault to CloudFlare Pages branch
runs-on: ubuntu-20.04
needs:
- setup
- self-host
env:
_RELEASE_VERSION: ${{ needs.setup.outputs.release_version }}
_TAG_VERSION: ${{ needs.setup.outputs.tag_version }}
steps:
- name: Checkout Repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
- name: Download latest cloud asset
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
with:
workflow: build.yml
workflow_conclusion: success
branch: ${{ needs.setup.outputs.branch_name }}
artifacts: web-*-cloud-COMMERCIAL.zip
# This should result in a build directory in the current working directory
- name: Unzip build asset
run: unzip web-*-cloud-COMMERCIAL.zip
- name: Checkout Repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
with:
ref: deploy
path: deployment
- name: Setup git config
run: |
git config --global user.name = "GitHub Action Bot"
git config --global user.email = "<>"
git config --global url."https://github.com/".insteadOf ssh://git@github.com/
git config --global url."https://".insteadOf ssh://
- name: Deploy CloudFlare Pages
run: |
rm -rf ./*
cp -R ../build/* .
working-directory: deployment
- name: Create cf-pages-deploy branch
run: |
git switch -c cf-pages-deploy-$_TAG_VERSION
git add .
git commit -m "Staging deploy ${{ needs.setup.outputs.release_version }}"
git push -u origin cf-pages-deploy-$_TAG_VERSION
working-directory: deployment
- name: Create CloudFlare Pages Deploy PR
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
env:
PR_BRANCH: cf-pages-deploy-${{ env._TAG_VERSION }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr create --title "Deploy $_RELEASE_VERSION to CloudFlare Pages" \
--body "Deploying $_RELEASE_VERSION" \
--base deploy \
--head "$PR_BRANCH"
release:
name: Create GitHub Release
runs-on: ubuntu-20.04
@@ -229,6 +284,7 @@ jobs:
- setup
- self-host
- ghpages-deploy
- cfpages-deploy
steps:
- name: Download latest build artifacts
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
@@ -271,5 +327,8 @@ jobs:
- name: Checkout repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # 2.4.0
- name: Remove deploy branch
run: git push origin --delete deploy-$_TAG_VERSION
- name: Remove gh-pages-deploy branch
run: git push origin --delete gh-pages-deploy-$_TAG_VERSION
- name: Remove cf-pages-deploy branch
run: git push origin --delete cf-pages-deploy-$_TAG_VERSION

View File

@@ -10,7 +10,7 @@ Here is how you can get involved:
- **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
- **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
@@ -31,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 web vault into another language (or make a translation correction), please register an account at Crowdin and join our project here: https://crowdin.com/project/bitwarden-web
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

@@ -1,3 +1,9 @@
> **Repository Reorganization in Progress**
>
> We are currently migrating some projects over to a mono repository. For existing PR's we will be providing documentation on how to move/migrate them. To minimize the overhead we are actively reviewing open PRs. If possible please ensure any pending comments are resolved as soon as possible.
>
> New pull requests created during this transition period may not get addressed —if needed, please create a new PR after the reorganization is complete.
<p align="center">
<img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/web-vault-macbook.png" alt="" width="600" height="358" />
</p>

View File

@@ -1,4 +1,5 @@
import { DragDropModule } from "@angular/cdk/drag-drop";
import { OverlayModule } from "@angular/cdk/overlay";
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
@@ -8,6 +9,7 @@ import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { JslibModule } from "jslib-angular/jslib.module";
import { OssRoutingModule } from "src/app/oss-routing.module";
import { OssModule } from "src/app/oss.module";
import { ServicesModule } from "src/app/services/services.module";
import { WildcardRoutingModule } from "src/app/wildcard-routing.module";
@@ -19,6 +21,8 @@ import { MaximumVaultTimeoutPolicyComponent } from "./policies/maximum-vault-tim
@NgModule({
imports: [
OverlayModule,
OssModule,
JslibModule,
BrowserAnimationsModule,
FormsModule,

View File

@@ -1,13 +1,13 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { AuthGuard } from "jslib-angular/guards/auth.guard";
import { Permissions } from "jslib-common/enums/permissions";
import { OrganizationLayoutComponent } from "src/app/layouts/organization-layout.component";
import { PermissionsGuard } from "src/app/organizations/guards/permissions.guard";
import { OrganizationLayoutComponent } from "src/app/organizations/layouts/organization-layout.component";
import { ManageComponent } from "src/app/organizations/manage/manage.component";
import { OrganizationGuardService } from "src/app/services/organization-guard.service";
import { OrganizationTypeGuardService } from "src/app/services/organization-type-guard.service";
import { NavigationPermissionsService } from "src/app/organizations/services/navigation-permissions.service";
import { SsoComponent } from "./manage/sso.component";
@@ -15,25 +15,16 @@ const routes: Routes = [
{
path: "organizations/:organizationId",
component: OrganizationLayoutComponent,
canActivate: [AuthGuardService, OrganizationGuardService],
canActivate: [AuthGuard, PermissionsGuard],
children: [
{
path: "manage",
component: ManageComponent,
canActivate: [OrganizationTypeGuardService],
canActivate: [PermissionsGuard],
data: {
permissions: [
Permissions.CreateNewCollections,
Permissions.EditAnyCollection,
Permissions.DeleteAnyCollection,
Permissions.EditAssignedCollections,
Permissions.DeleteAssignedCollections,
Permissions.AccessEventLogs,
Permissions.ManageGroups,
Permissions.ManageUsers,
Permissions.ManagePolicies,
Permissions.ManageSso,
],
permissions: NavigationPermissionsService.getPermissions("manage").concat(
Permissions.ManageSso
),
},
children: [
{

View File

@@ -5,7 +5,7 @@ import { ProviderService } from "jslib-common/abstractions/provider.service";
import { Permissions } from "jslib-common/enums/permissions";
@Injectable()
export class ProviderTypeGuardService implements CanActivate {
export class PermissionsGuard implements CanActivate {
constructor(private providerService: ProviderService, private router: Router) {}
async canActivate(route: ActivatedRouteSnapshot) {

View File

@@ -6,7 +6,7 @@ import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.se
import { ProviderService } from "jslib-common/abstractions/provider.service";
@Injectable()
export class ProviderGuardService implements CanActivate {
export class ProviderGuard implements CanActivate {
constructor(
private router: Router,
private platformUtilsService: PlatformUtilsService,

View File

@@ -24,7 +24,11 @@
<p>{{ "joinProviderDesc" | i18n }}</p>
<hr />
<div class="d-flex">
<a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
<a
routerLink="/login"
[queryParams]="{ email: email }"
class="btn btn-primary btn-block"
>
{{ "logIn" | i18n }}
</a>
<a

View File

@@ -1,7 +1,7 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { AuthGuard } from "jslib-angular/guards/auth.guard";
import { Permissions } from "jslib-common/enums/permissions";
import { FrontendLayoutComponent } from "src/app/layouts/frontend-layout.component";
@@ -9,13 +9,13 @@ import { ProvidersComponent } from "src/app/providers/providers.component";
import { ClientsComponent } from "./clients/clients.component";
import { CreateOrganizationComponent } from "./clients/create-organization.component";
import { PermissionsGuard } from "./guards/provider-type.guard";
import { ProviderGuard } from "./guards/provider.guard";
import { AcceptProviderComponent } from "./manage/accept-provider.component";
import { EventsComponent } from "./manage/events.component";
import { ManageComponent } from "./manage/manage.component";
import { PeopleComponent } from "./manage/people.component";
import { ProvidersLayoutComponent } from "./providers-layout.component";
import { ProviderGuardService } from "./services/provider-guard.service";
import { ProviderTypeGuardService } from "./services/provider-type-guard.service";
import { AccountComponent } from "./settings/account.component";
import { SettingsComponent } from "./settings/settings.component";
import { SetupProviderComponent } from "./setup/setup-provider.component";
@@ -24,7 +24,7 @@ import { SetupComponent } from "./setup/setup.component";
const routes: Routes = [
{
path: "",
canActivate: [AuthGuardService],
canActivate: [AuthGuard],
component: ProvidersComponent,
},
{
@@ -45,7 +45,7 @@ const routes: Routes = [
},
{
path: "",
canActivate: [AuthGuardService],
canActivate: [AuthGuard],
children: [
{
path: "setup",
@@ -54,7 +54,7 @@ const routes: Routes = [
{
path: ":providerId",
component: ProvidersLayoutComponent,
canActivate: [ProviderGuardService],
canActivate: [ProviderGuard],
children: [
{ path: "", pathMatch: "full", redirectTo: "clients" },
{ path: "clients/create", component: CreateOrganizationComponent },
@@ -71,7 +71,7 @@ const routes: Routes = [
{
path: "people",
component: PeopleComponent,
canActivate: [ProviderTypeGuardService],
canActivate: [PermissionsGuard],
data: {
titleId: "people",
permissions: [Permissions.ManageUsers],
@@ -80,7 +80,7 @@ const routes: Routes = [
{
path: "events",
component: EventsComponent,
canActivate: [ProviderTypeGuardService],
canActivate: [PermissionsGuard],
data: {
titleId: "eventLogs",
permissions: [Permissions.AccessEventLogs],
@@ -100,7 +100,7 @@ const routes: Routes = [
{
path: "account",
component: AccountComponent,
canActivate: [ProviderTypeGuardService],
canActivate: [PermissionsGuard],
data: {
titleId: "myProvider",
permissions: [Permissions.ManageProvider],

View File

@@ -10,6 +10,8 @@ import { OssModule } from "src/app/oss.module";
import { AddOrganizationComponent } from "./clients/add-organization.component";
import { ClientsComponent } from "./clients/clients.component";
import { CreateOrganizationComponent } from "./clients/create-organization.component";
import { PermissionsGuard } from "./guards/provider-type.guard";
import { ProviderGuard } from "./guards/provider.guard";
import { AcceptProviderComponent } from "./manage/accept-provider.component";
import { BulkConfirmComponent } from "./manage/bulk/bulk-confirm.component";
import { BulkRemoveComponent } from "./manage/bulk/bulk-remove.component";
@@ -19,8 +21,6 @@ import { PeopleComponent } from "./manage/people.component";
import { UserAddEditComponent } from "./manage/user-add-edit.component";
import { ProvidersLayoutComponent } from "./providers-layout.component";
import { ProvidersRoutingModule } from "./providers-routing.module";
import { ProviderGuardService } from "./services/provider-guard.service";
import { ProviderTypeGuardService } from "./services/provider-type-guard.service";
import { WebProviderService } from "./services/webProvider.service";
import { AccountComponent } from "./settings/account.component";
import { SettingsComponent } from "./settings/settings.component";
@@ -46,7 +46,7 @@ import { SetupComponent } from "./setup/setup.component";
SetupProviderComponent,
UserAddEditComponent,
],
providers: [WebProviderService, ProviderGuardService, ProviderTypeGuardService],
providers: [WebProviderService, ProviderGuard, PermissionsGuard],
})
export class ProvidersModule {
constructor(modalService: ModalService, componentFactoryResolver: ComponentFactoryResolver) {

View File

@@ -20,7 +20,11 @@
<p>{{ "setupProviderLoginDesc" | i18n }}</p>
<hr />
<div class="d-flex">
<a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
<a
routerLink="/login"
[queryParams]="{ email: email }"
class="btn btn-primary btn-block"
>
{{ "logIn" | i18n }}
</a>
</div>

2
jslib

Submodule jslib updated: e595c0548e...1cc4bed671

6495
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@bitwarden/web-vault",
"version": "2.28.0",
"version": "2022.05.0",
"license": "GPL-3.0",
"repository": "https://github.com/bitwarden/web",
"scripts": {
@@ -90,7 +90,7 @@
"@bitwarden/jslib-angular": "file:jslib/angular",
"@bitwarden/jslib-common": "file:jslib/common",
"bootstrap": "4.6.0",
"braintree-web-drop-in": "1.30.1",
"braintree-web-drop-in": "1.33.1",
"browser-hrtime": "^1.1.8",
"core-js": "^3.11.0",
"date-input-polyfill": "^2.14.0",
@@ -98,6 +98,7 @@
"jszip": "^3.7.1",
"ngx-infinite-scroll": "^10.0.1",
"ngx-toastr": "14.1.4",
"node-forge": "^1.3.1",
"popper.js": "1.16.1",
"qrious": "4.0.2",
"rxjs": "^7.4.0",

View File

@@ -23,7 +23,11 @@
<p>{{ "acceptEmergencyAccess" | i18n }}</p>
<hr />
<div class="d-flex">
<a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
<a
routerLink="/login"
[queryParams]="{ email: email }"
class="btn btn-primary btn-block"
>
{{ "logIn" | i18n }}
</a>
<a

View File

@@ -24,7 +24,11 @@
<p>{{ "joinOrganizationDesc" | i18n }}</p>
<hr />
<div class="d-flex">
<a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
<a
routerLink="/login"
[queryParams]="{ email: email }"
class="btn btn-primary btn-block"
>
{{ "logIn" | i18n }}
</a>
<a

View File

@@ -33,7 +33,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@@ -55,10 +55,10 @@ export class LockComponent extends BaseLockComponent {
await super.ngOnInit();
this.onSuccessfulSubmit = async () => {
const previousUrl = this.routerService.getPreviousUrl();
if (previousUrl !== "/" && previousUrl.indexOf("lock") === -1) {
if (previousUrl && previousUrl !== "/" && previousUrl.indexOf("lock") === -1) {
this.successRoute = previousUrl;
}
this.router.navigate([this.successRoute]);
this.router.navigateByUrl(this.successRoute);
};
}
}

View File

@@ -20,6 +20,7 @@ import { ListResponse } from "jslib-common/models/response/listResponse";
import { PolicyResponse } from "jslib-common/models/response/policyResponse";
import { StateService } from "../../abstractions/state.service";
import { RouterService } from "../services/router.service";
@Component({
selector: "app-login",
@@ -44,7 +45,8 @@ export class LoginComponent extends BaseLoginComponent {
logService: LogService,
ngZone: NgZone,
protected stateService: StateService,
private messagingService: MessagingService
private messagingService: MessagingService,
private routerService: RouterService
) {
super(
authService,
@@ -70,21 +72,20 @@ export class LoginComponent extends BaseLoginComponent {
this.email = qParams.email;
}
if (qParams.premium != null) {
this.stateService.setLoginRedirect({ route: "/settings/premium" });
this.routerService.setPreviousUrl("/settings/premium");
} else if (qParams.org != null) {
this.stateService.setLoginRedirect({
route: "/settings/create-organization",
qParams: { plan: qParams.org },
const route = this.router.createUrlTree(["create-organization"], {
queryParams: { plan: qParams.org },
});
this.routerService.setPreviousUrl(route.toString());
}
// Are they coming from an email for sponsoring a families organization
if (qParams.sponsorshipToken != null) {
// After logging in redirect them to setup the families sponsorship
this.stateService.setLoginRedirect({
route: "/setup/families-for-enterprise",
qParams: { token: qParams.sponsorshipToken },
const route = this.router.createUrlTree(["setup/families-for-enterprise"], {
queryParams: { token: qParams.sponsorshipToken },
});
this.routerService.setPreviousUrl(route.toString());
}
await super.ngOnInit();
this.rememberEmail = await this.stateService.getRememberEmail();
@@ -145,10 +146,9 @@ export class LoginComponent extends BaseLoginComponent {
}
}
const loginRedirect = await this.stateService.getLoginRedirect();
if (loginRedirect != null) {
this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams });
await this.stateService.setLoginRedirect(null);
const previousUrl = this.routerService.getPreviousUrl();
if (previousUrl) {
this.router.navigateByUrl(previousUrl);
} else {
this.router.navigate([this.successRoute]);
}

View File

@@ -33,7 +33,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@@ -65,7 +65,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@@ -258,7 +258,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@@ -18,6 +18,8 @@ import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPa
import { Policy } from "jslib-common/models/domain/policy";
import { ReferenceEventRequest } from "jslib-common/models/request/referenceEventRequest";
import { RouterService } from "../services/router.service";
@Component({
selector: "app-register",
templateUrl: "register.component.html",
@@ -41,7 +43,8 @@ export class RegisterComponent extends BaseRegisterComponent {
passwordGenerationService: PasswordGenerationService,
private policyService: PolicyService,
environmentService: EnvironmentService,
logService: LogService
logService: LogService,
private routerService: RouterService
) {
super(
authService,
@@ -64,14 +67,14 @@ export class RegisterComponent extends BaseRegisterComponent {
this.email = qParams.email;
}
if (qParams.premium != null) {
this.stateService.setLoginRedirect({ route: "/settings/premium" });
this.routerService.setPreviousUrl("/settings/premium");
} else if (qParams.org != null) {
this.showCreateOrgMessage = true;
this.referenceData.flow = qParams.org;
this.stateService.setLoginRedirect({
route: "/settings/create-organization",
qParams: { plan: qParams.org },
const route = this.router.createUrlTree(["create-organization"], {
queryParams: { plan: qParams.org },
});
this.routerService.setPreviousUrl(route.toString());
}
if (qParams.layout != null) {
this.layout = this.referenceData.layout = qParams.layout;
@@ -88,10 +91,10 @@ export class RegisterComponent extends BaseRegisterComponent {
// Are they coming from an email for sponsoring a families organization
if (qParams.sponsorshipToken != null) {
// After logging in redirect them to setup the families sponsorship
this.stateService.setLoginRedirect({
route: "/setup/families-for-enterprise",
qParams: { token: qParams.sponsorshipToken },
const route = this.router.createUrlTree(["setup/families-for-enterprise"], {
queryParams: { plan: qParams.sponsorshipToken },
});
this.routerService.setPreviousUrl(route.toString());
}
if (this.referenceData.id === "") {
this.referenceData.id = null;

View File

@@ -41,7 +41,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@@ -138,7 +138,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@@ -4,6 +4,7 @@ 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";
@@ -13,6 +14,8 @@ import { StateService } from "jslib-common/abstractions/state.service";
import { TwoFactorService } from "jslib-common/abstractions/twoFactor.service";
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
import { RouterService } from "../services/router.service";
import { TwoFactorOptionsComponent } from "./two-factor-options.component";
@Component({
@@ -34,7 +37,9 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
private modalService: ModalService,
route: ActivatedRoute,
logService: LogService,
twoFactorService: TwoFactorService
twoFactorService: TwoFactorService,
appIdService: AppIdService,
private routerService: RouterService
) {
super(
authService,
@@ -47,7 +52,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
stateService,
route,
logService,
twoFactorService
twoFactorService,
appIdService
);
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
}
@@ -70,10 +76,9 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
}
async goAfterLogIn() {
const loginRedirect = await this.stateService.getLoginRedirect();
if (loginRedirect != null) {
this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams });
await this.stateService.setLoginRedirect(null);
const previousUrl = this.routerService.getPreviousUrl();
if (previousUrl) {
this.router.navigateByUrl(previousUrl);
} else {
this.router.navigate([this.successRoute], {
queryParams: {

View File

@@ -23,7 +23,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@@ -91,11 +91,17 @@ export class AppComponent implements OnDestroy, OnInit {
this.ngZone.run(async () => {
switch (message.command) {
case "loggedIn":
this.notificationsService.updateConnection(false);
break;
case "loggedOut":
this.routerService.setPreviousUrl(null);
this.notificationsService.updateConnection(false);
break;
case "unlocked":
this.notificationsService.updateConnection(false);
break;
case "authBlocked":
this.routerService.setPreviousUrl(message.url);
this.router.navigate(["/"]);
break;
case "logout":
@@ -109,7 +115,7 @@ export class AppComponent implements OnDestroy, OnInit {
this.router.navigate(["lock"]);
break;
case "lockedUrl":
window.setTimeout(() => this.routerService.setPreviousUrl(message.url), 500);
this.routerService.setPreviousUrl(message.url);
break;
case "syncStarted":
break;

View File

@@ -30,7 +30,6 @@ export abstract class BaseAcceptComponent implements OnInit {
ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
await this.stateService.setLoginRedirect(null);
let error = this.requiredParameters.some((e) => qParams?.[e] == null || qParams[e] === "");
let errorMessage: string = null;
if (!error) {
@@ -44,11 +43,6 @@ export abstract class BaseAcceptComponent implements OnInit {
errorMessage = e.message;
}
} else {
await this.stateService.setLoginRedirect({
route: this.getRedirectRoute(),
qParams: qParams,
});
this.email = qParams.email;
await this.unauthedHandler(qParams);
}
@@ -66,10 +60,4 @@ export abstract class BaseAcceptComponent implements OnInit {
this.loading = false;
});
}
getRedirectRoute() {
const urlTree = this.router.parseUrl(this.router.url);
urlTree.queryParams = {};
return urlTree.toString();
}
}

View File

@@ -122,17 +122,20 @@ export abstract class BaseEventsComponent {
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
const eventInfo = await this.eventService.getEventInfo(r);
const user = this.getUserName(r, userId);
const userName = user != null ? user.name : this.i18nService.t("unknown");
return new EventView({
message: eventInfo.message,
humanReadableMessage: eventInfo.humanReadableMessage,
appIcon: eventInfo.appIcon,
appName: eventInfo.appName,
userId: userId,
userName: user != null ? user.name : this.i18nService.t("unknown"),
userName: r.installationId != null ? `Installation: ${r.installationId}` : userName,
userEmail: user != null ? user.email : "",
date: r.date,
ip: r.ipAddress,
type: r.type,
installationId: r.installationId,
});
})
);

View File

@@ -0,0 +1,68 @@
<div *ngIf="loaded && activeOrganization != null" class="tw-flex">
<button
class="tw-flex tw-items-center tw-bg-background-alt tw-border-none"
type="button"
id="pickerButton"
[appA11yTitle]="'organizationPicker' | i18n"
[bitMenuTriggerFor]="orgPickerMenu"
>
<app-avatar
[data]="activeOrganization.name"
size="45"
[circle]="true"
[dynamic]="true"
></app-avatar>
<div class="tw-flex">
<div class="org-name tw-ml-3">
<span>{{ activeOrganization.name }}</span>
<small class="tw-text-muted">{{ "organization" | i18n }}</small>
</div>
<div class="tw-ml-3">
<i class="bwi bwi-angle-down tw-text-main" aria-hidden="true"></i>
</div>
</div>
</button>
<div>
<div
class="tw-ml-3 tw-border tw-border-solid tw-rounded tw-border-danger-500 tw-text-danger"
*ngIf="!activeOrganization.enabled"
>
<div class="tw-py-2 tw-px-5">
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
{{ "organizationIsDisabled" | i18n }}
</div>
</div>
<div
class="tw-ml-3 tw-border tw-border-solid tw-rounded tw-border-info-500 tw-text-info"
*ngIf="activeOrganization.isProviderUser"
>
<div class="tw-py-2 tw-px-5">
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
{{ "accessingUsingProvider" | i18n: activeOrganization.providerName }}
</div>
</div>
</div>
<bit-menu #orgPickerMenu>
<ul aria-labelledby="pickerButton" class="tw-p-0 tw-m-0">
<li *ngFor="let org of organizations" class="tw-list-none tw-flex tw-flex-col" role="none">
<a bit-menu-item [routerLink]="['/organizations', org.id]">
<i
class="bwi bwi-check mr-2"
[ngClass]="org.id === activeOrganization.id ? 'visible' : 'invisible'"
>
<span class="tw-sr-only">{{ "currentOrganization" | i18n }}</span>
</i>
{{ org.name }}
</a>
</li>
<bit-menu-divider></bit-menu-divider>
<li class="tw-list-none" role="none">
<a bit-menu-item routerLink="/create-organization">
<i class="bwi bwi-plus mr-2"></i>
{{ "newOrganization" | i18n }}</a
>
</li>
</ul>
</bit-menu>
</div>

View File

@@ -0,0 +1,34 @@
import { Component, Input, OnInit } from "@angular/core";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { Utils } from "jslib-common/misc/utils";
import { Organization } from "jslib-common/models/domain/organization";
import { NavigationPermissionsService } from "../organizations/services/navigation-permissions.service";
@Component({
selector: "app-organization-switcher",
templateUrl: "organization-switcher.component.html",
})
export class OrganizationSwitcherComponent implements OnInit {
constructor(private organizationService: OrganizationService, private i18nService: I18nService) {}
@Input() activeOrganization: Organization = null;
organizations: Organization[] = [];
loaded = false;
async ngOnInit() {
await this.load();
}
async load() {
const orgs = await this.organizationService.getAll();
this.organizations = orgs
.filter((org) => NavigationPermissionsService.canAccessAdmin(org))
.sort(Utils.getSortFunction(this.i18nService, "name"));
this.loaded = true;
}
}

View File

@@ -0,0 +1,22 @@
import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { AuthenticationStatus } from "jslib-common/enums/authenticationStatus";
@Injectable()
export class HomeGuard implements CanActivate {
constructor(private router: Router, private authService: AuthService) {}
async canActivate(route: ActivatedRouteSnapshot) {
const authStatus = await this.authService.getAuthStatus();
if (authStatus === AuthenticationStatus.LoggedOut) {
return this.router.createUrlTree(["/login"], { queryParams: route.queryParams });
}
if (authStatus === AuthenticationStatus.Locked) {
return this.router.createUrlTree(["/lock"], { queryParams: route.queryParams });
}
return this.router.createUrlTree(["/vault"], { queryParams: route.queryParams });
}
}

View File

@@ -6,11 +6,22 @@
<div class="collapse navbar-collapse">
<ul class="navbar-nav">
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/vault">{{ "myVault" | i18n }}</a>
<a class="nav-link" routerLink="/vault">{{ "vaults" | i18n }}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/sends">{{ "send" | i18n }}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/tools">{{ "tools" | i18n }}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/reports">{{ "reports" | i18n }}</a>
</li>
<li *ngIf="organizations.length >= 1" class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/organizations', organizations[0].id]">{{
"organizations" | i18n
}}</a>
</li>
<ng-container *ngIf="providers.length >= 1">
<li class="nav-item" routerLinkActive="active" *ngIf="providers.length == 1">
<a class="nav-link" [routerLink]="['/providers', providers[0].id]">{{
@@ -21,76 +32,62 @@
<a class="nav-link" routerLink="/providers">{{ "provider" | i18n }}</a>
</li>
</ng-container>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/tools">{{ "tools" | i18n }}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/reports">{{ "reports" | i18n }}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/settings">{{ "settings" | i18n }}</a>
</li>
</ul>
</div>
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
<li class="nav-item dropdown">
<a
class="nav-item nav-link dropdown-toggle"
href="#"
id="nav-profile"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
<li>
<button
[bitMenuTriggerFor]="accountMenu"
class="tw-border-0 tw-bg-transparent tw-text-alt2 tw-opacity-70 hover:tw-opacity-90"
>
<i class="bwi bwi-user-circle bwi-lg" aria-hidden="true"></i>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="nav-profile">
<div class="dropdown-item-text d-flex align-items-center" *ngIf="name" appStopProp>
<app-avatar
[data]="name"
[email]="email"
size="25"
fontSize="14"
[circle]="true"
></app-avatar>
<div class="ml-2 overflow-hidden">
<span>{{ "loggedInAs" | i18n }}</span>
<small class="text-muted">{{ name }}</small>
<i class="bwi bwi-caret-down bwi-sm" aria-hidden="true"></i>
</button>
<bit-menu class="dropdown-menu" #accountMenu>
<div class="tw-max-w-[300px] tw-min-w-[200px] tw-flex tw-flex-col">
<div
class="tw-flex tw-items-center tw-leading-tight tw-text-info tw-py-1 tw-px-4"
*ngIf="name"
appStopProp
>
<app-avatar
[data]="name"
[email]="email"
size="25"
fontSize="14"
[circle]="true"
></app-avatar>
<div class="tw-ml-2 tw-block tw-overflow-hidden tw-whitespace-nowrap">
<span>{{ "loggedInAs" | i18n }}</span>
<small class="tw-text-muted tw-block tw-overflow-hidden tw-whitespace-nowrap">{{
name
}}</small>
</div>
</div>
<bit-menu-divider></bit-menu-divider>
<a bit-menu-item routerLink="/settings/account">
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
{{ "accountSettings" | i18n }}
</a>
<a bit-menu-item href="https://bitwarden.com/help/" target="_blank" rel="noopener">
<i class="bwi bwi-fw bwi-question-circle" aria-hidden="true"></i>
{{ "getHelp" | i18n }}
</a>
<a bit-menu-item href="https://bitwarden.com/download/" target="_blank" rel="noopener">
<i class="bwi bwi-fw bwi-download" aria-hidden="true"></i>
{{ "getApps" | i18n }}
</a>
<bit-menu-divider></bit-menu-divider>
<button bit-menu-item type="button" (click)="lock()">
<i class="bwi bwi-fw bwi-lock" aria-hidden="true"></i>
{{ "lockNow" | i18n }}
</button>
<button bit-menu-item type="button" (click)="logOut()">
<i class="bwi bwi-fw bwi-sign-out" aria-hidden="true"></i>
{{ "logOut" | i18n }}
</button>
</div>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" routerLink="/settings/account">
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
{{ "myAccount" | i18n }}
</a>
<a
class="dropdown-item"
href="https://bitwarden.com/help/"
target="_blank"
rel="noopener"
>
<i class="bwi bwi-fw bwi-question-circle" aria-hidden="true"></i>
{{ "getHelp" | i18n }}
</a>
<a
class="dropdown-item"
href="https://bitwarden.com/download/"
target="_blank"
rel="noopener"
>
<i class="bwi bwi-fw bwi-download" aria-hidden="true"></i>
{{ "getApps" | i18n }}
</a>
<div class="dropdown-divider"></div>
<button type="button" class="dropdown-item" (click)="lock()">
<i class="bwi bwi-fw bwi-lock" aria-hidden="true"></i>
{{ "lockNow" | i18n }}
</button>
<button type="button" class="dropdown-item" (click)="logOut()">
<i class="bwi bwi-fw bwi-sign-out" aria-hidden="true"></i>
{{ "logOut" | i18n }}
</button>
</div>
</bit-menu>
</li>
</ul>
</div>

View File

@@ -1,12 +1,19 @@
import { Component, OnInit } from "@angular/core";
import { Component, NgZone, OnInit } from "@angular/core";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ProviderService } from "jslib-common/abstractions/provider.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { TokenService } from "jslib-common/abstractions/token.service";
import { Utils } from "jslib-common/misc/utils";
import { Organization } from "jslib-common/models/domain/organization";
import { Provider } from "jslib-common/models/domain/provider";
import { NavigationPermissionsService as OrgNavigationPermissionsService } from "../organizations/services/navigation-permissions.service";
@Component({
selector: "app-navbar",
templateUrl: "navbar.component.html",
@@ -16,13 +23,18 @@ export class NavbarComponent implements OnInit {
name: string;
email: string;
providers: Provider[] = [];
organizations: Organization[] = [];
constructor(
private messagingService: MessagingService,
private platformUtilsService: PlatformUtilsService,
private tokenService: TokenService,
private providerService: ProviderService,
private syncService: SyncService
private syncService: SyncService,
private organizationService: OrganizationService,
private i18nService: I18nService,
private broadcasterService: BroadcasterService,
private ngZone: NgZone
) {
this.selfHosted = this.platformUtilsService.isSelfHost();
}
@@ -34,11 +46,32 @@ export class NavbarComponent implements OnInit {
this.name = this.email;
}
// Ensure provides are loaded
// Ensure providers and organizations are loaded
if ((await this.syncService.getLastSync()) == null) {
await this.syncService.fullSync(false);
}
this.providers = await this.providerService.getAll();
this.organizations = await this.buildOrganizations();
this.broadcasterService.subscribe(this.constructor.name, async (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case "organizationCreated":
if (this.organizations.length < 1) {
this.organizations = await this.buildOrganizations();
}
break;
}
});
});
}
async buildOrganizations() {
const allOrgs = await this.organizationService.getAll();
return allOrgs
.filter((org) => OrgNavigationPermissionsService.canAccessAdmin(org))
.sort(Utils.getSortFunction(this.i18nService, "name"));
}
lock() {

View File

@@ -0,0 +1,496 @@
import { NgModule } from "@angular/core";
import { UserVerificationComponent } from "jslib-angular/components/user-verification.component";
import { AcceptEmergencyComponent } from "../accounts/accept-emergency.component";
import { AcceptOrganizationComponent } from "../accounts/accept-organization.component";
import { HintComponent } from "../accounts/hint.component";
import { LockComponent } from "../accounts/lock.component";
import { LoginComponent } from "../accounts/login.component";
import { RecoverDeleteComponent } from "../accounts/recover-delete.component";
import { RecoverTwoFactorComponent } from "../accounts/recover-two-factor.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 { TwoFactorOptionsComponent } from "../accounts/two-factor-options.component";
import { TwoFactorComponent } from "../accounts/two-factor.component";
import { UpdatePasswordComponent } from "../accounts/update-password.component";
import { UpdateTempPasswordComponent } from "../accounts/update-temp-password.component";
import { VerifyEmailTokenComponent } from "../accounts/verify-email-token.component";
import { VerifyRecoverDeleteComponent } from "../accounts/verify-recover-delete.component";
import { NestedCheckboxComponent } from "../components/nested-checkbox.component";
import { OrganizationSwitcherComponent } from "../components/organization-switcher.component";
import { PasswordRepromptComponent } from "../components/password-reprompt.component";
import { PasswordStrengthComponent } from "../components/password-strength.component";
import { PremiumBadgeComponent } from "../components/premium-badge.component";
import { FooterComponent } from "../layouts/footer.component";
import { FrontendLayoutComponent } from "../layouts/frontend-layout.component";
import { NavbarComponent } from "../layouts/navbar.component";
import { UserLayoutComponent } from "../layouts/user-layout.component";
import { OrganizationLayoutComponent } from "../organizations/layouts/organization-layout.component";
import { BulkConfirmComponent as OrgBulkConfirmComponent } from "../organizations/manage/bulk/bulk-confirm.component";
import { BulkRemoveComponent as OrgBulkRemoveComponent } from "../organizations/manage/bulk/bulk-remove.component";
import { BulkStatusComponent as OrgBulkStatusComponent } from "../organizations/manage/bulk/bulk-status.component";
import { CollectionAddEditComponent as OrgCollectionAddEditComponent } from "../organizations/manage/collection-add-edit.component";
import { CollectionsComponent as OrgManageCollectionsComponent } from "../organizations/manage/collections.component";
import { EntityEventsComponent as OrgEntityEventsComponent } from "../organizations/manage/entity-events.component";
import { EntityUsersComponent as OrgEntityUsersComponent } from "../organizations/manage/entity-users.component";
import { EventsComponent as OrgEventsComponent } from "../organizations/manage/events.component";
import { GroupAddEditComponent as OrgGroupAddEditComponent } from "../organizations/manage/group-add-edit.component";
import { GroupsComponent as OrgGroupsComponent } from "../organizations/manage/groups.component";
import { ManageComponent as OrgManageComponent } from "../organizations/manage/manage.component";
import { PeopleComponent as OrgPeopleComponent } from "../organizations/manage/people.component";
import { PoliciesComponent as OrgPoliciesComponent } from "../organizations/manage/policies.component";
import { PolicyEditComponent as OrgPolicyEditComponent } from "../organizations/manage/policy-edit.component";
import { ResetPasswordComponent as OrgResetPasswordComponent } from "../organizations/manage/reset-password.component";
import { UserAddEditComponent as OrgUserAddEditComponent } from "../organizations/manage/user-add-edit.component";
import { UserConfirmComponent as OrgUserConfirmComponent } from "../organizations/manage/user-confirm.component";
import { UserGroupsComponent as OrgUserGroupsComponent } from "../organizations/manage/user-groups.component";
import { DisableSendPolicyComponent } from "../organizations/policies/disable-send.component";
import { MasterPasswordPolicyComponent } from "../organizations/policies/master-password.component";
import { PasswordGeneratorPolicyComponent } from "../organizations/policies/password-generator.component";
import { PersonalOwnershipPolicyComponent } from "../organizations/policies/personal-ownership.component";
import { RequireSsoPolicyComponent } from "../organizations/policies/require-sso.component";
import { ResetPasswordPolicyComponent } from "../organizations/policies/reset-password.component";
import { SendOptionsPolicyComponent } from "../organizations/policies/send-options.component";
import { SingleOrgPolicyComponent } from "../organizations/policies/single-org.component";
import { TwoFactorAuthenticationPolicyComponent } from "../organizations/policies/two-factor-authentication.component";
import { AccountComponent as OrgAccountComponent } from "../organizations/settings/account.component";
import { AdjustSubscription } from "../organizations/settings/adjust-subscription.component";
import { BillingSyncApiKeyComponent } from "../organizations/settings/billing-sync-api-key.component";
import { ChangePlanComponent } from "../organizations/settings/change-plan.component";
import { DeleteOrganizationComponent } from "../organizations/settings/delete-organization.component";
import { DownloadLicenseComponent } from "../organizations/settings/download-license.component";
import { ImageSubscriptionHiddenComponent as OrgSubscriptionHiddenComponent } from "../organizations/settings/image-subscription-hidden.component";
import { OrganizationBillingComponent } from "../organizations/settings/organization-billing.component";
import { OrganizationSubscriptionComponent } from "../organizations/settings/organization-subscription.component";
import { SettingsComponent as OrgSettingComponent } from "../organizations/settings/settings.component";
import { TwoFactorSetupComponent as OrgTwoFactorSetupComponent } from "../organizations/settings/two-factor-setup.component";
import { AcceptFamilySponsorshipComponent } from "../organizations/sponsorships/accept-family-sponsorship.component";
import { FamiliesForEnterpriseSetupComponent } from "../organizations/sponsorships/families-for-enterprise-setup.component";
import { ExportComponent as OrgExportComponent } from "../organizations/tools/export.component";
import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "../organizations/tools/exposed-passwords-report.component";
import { ImportComponent as OrgImportComponent } from "../organizations/tools/import.component";
import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent } from "../organizations/tools/inactive-two-factor-report.component";
import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } from "../organizations/tools/reused-passwords-report.component";
import { ToolsComponent as OrgToolsComponent } from "../organizations/tools/tools.component";
import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "../organizations/tools/unsecured-websites-report.component";
import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "../organizations/tools/weak-passwords-report.component";
import { AddEditComponent as OrgAddEditComponent } from "../organizations/vault/add-edit.component";
import { AttachmentsComponent as OrgAttachmentsComponent } from "../organizations/vault/attachments.component";
import { CiphersComponent as OrgCiphersComponent } from "../organizations/vault/ciphers.component";
import { CollectionsComponent as OrgCollectionsComponent } from "../organizations/vault/collections.component";
import { ProvidersComponent } from "../providers/providers.component";
import { BreachReportComponent } from "../reports/breach-report.component";
import { ExposedPasswordsReportComponent } from "../reports/exposed-passwords-report.component";
import { InactiveTwoFactorReportComponent } from "../reports/inactive-two-factor-report.component";
import { ReportCardComponent } from "../reports/report-card.component";
import { ReportListComponent } from "../reports/report-list.component";
import { ReportsComponent } from "../reports/reports.component";
import { ReusedPasswordsReportComponent } from "../reports/reused-passwords-report.component";
import { UnsecuredWebsitesReportComponent } from "../reports/unsecured-websites-report.component";
import { WeakPasswordsReportComponent } from "../reports/weak-passwords-report.component";
import { AccessComponent } from "../send/access.component";
import { AddEditComponent as SendAddEditComponent } from "../send/add-edit.component";
import { EffluxDatesComponent as SendEffluxDatesComponent } from "../send/efflux-dates.component";
import { SendComponent } from "../send/send.component";
import { AccountComponent } from "../settings/account.component";
import { AddCreditComponent } from "../settings/add-credit.component";
import { AdjustPaymentComponent } from "../settings/adjust-payment.component";
import { AdjustStorageComponent } from "../settings/adjust-storage.component";
import { ApiKeyComponent } from "../settings/api-key.component";
import { BillingSyncKeyComponent } from "../settings/billing-sync-key.component";
import { ChangeEmailComponent } from "../settings/change-email.component";
import { ChangeKdfComponent } from "../settings/change-kdf.component";
import { ChangePasswordComponent } from "../settings/change-password.component";
import { CreateOrganizationComponent } from "../settings/create-organization.component";
import { DeauthorizeSessionsComponent } from "../settings/deauthorize-sessions.component";
import { DeleteAccountComponent } from "../settings/delete-account.component";
import { DomainRulesComponent } from "../settings/domain-rules.component";
import { EmergencyAccessAddEditComponent } from "../settings/emergency-access-add-edit.component";
import { EmergencyAccessAttachmentsComponent } from "../settings/emergency-access-attachments.component";
import { EmergencyAccessConfirmComponent } from "../settings/emergency-access-confirm.component";
import { EmergencyAccessTakeoverComponent } from "../settings/emergency-access-takeover.component";
import { EmergencyAccessViewComponent } from "../settings/emergency-access-view.component";
import { EmergencyAccessComponent } from "../settings/emergency-access.component";
import { EmergencyAddEditComponent } from "../settings/emergency-add-edit.component";
import { OrganizationPlansComponent } from "../settings/organization-plans.component";
import { PaymentMethodComponent } from "../settings/payment-method.component";
import { PaymentComponent } from "../settings/payment.component";
import { PreferencesComponent } from "../settings/preferences.component";
import { PremiumComponent } from "../settings/premium.component";
import { ProfileComponent } from "../settings/profile.component";
import { PurgeVaultComponent } from "../settings/purge-vault.component";
import { SecurityKeysComponent } from "../settings/security-keys.component";
import { SecurityComponent } from "../settings/security.component";
import { SettingsComponent } from "../settings/settings.component";
import { SponsoredFamiliesComponent } from "../settings/sponsored-families.component";
import { SponsoringOrgRowComponent } from "../settings/sponsoring-org-row.component";
import { SubscriptionComponent } from "../settings/subscription.component";
import { TaxInfoComponent } from "../settings/tax-info.component";
import { TwoFactorAuthenticatorComponent } from "../settings/two-factor-authenticator.component";
import { TwoFactorDuoComponent } from "../settings/two-factor-duo.component";
import { TwoFactorEmailComponent } from "../settings/two-factor-email.component";
import { TwoFactorRecoveryComponent } from "../settings/two-factor-recovery.component";
import { TwoFactorSetupComponent } from "../settings/two-factor-setup.component";
import { TwoFactorVerifyComponent } from "../settings/two-factor-verify.component";
import { TwoFactorWebAuthnComponent } from "../settings/two-factor-webauthn.component";
import { TwoFactorYubiKeyComponent } from "../settings/two-factor-yubikey.component";
import { UpdateKeyComponent } from "../settings/update-key.component";
import { UpdateLicenseComponent } from "../settings/update-license.component";
import { UserBillingHistoryComponent } from "../settings/user-billing-history.component";
import { UserSubscriptionComponent } from "../settings/user-subscription.component";
import { VaultTimeoutInputComponent } from "../settings/vault-timeout-input.component";
import { VerifyEmailComponent } from "../settings/verify-email.component";
import { ExportComponent } from "../tools/export.component";
import { GeneratorComponent } from "../tools/generator.component";
import { ImportComponent } from "../tools/import.component";
import { PasswordGeneratorHistoryComponent } from "../tools/password-generator-history.component";
import { ToolsComponent } from "../tools/tools.component";
import { AddEditCustomFieldsComponent } from "../vault/add-edit-custom-fields.component";
import { AddEditComponent } from "../vault/add-edit.component";
import { AttachmentsComponent } from "../vault/attachments.component";
import { BulkActionsComponent } from "../vault/bulk-actions.component";
import { BulkDeleteComponent } from "../vault/bulk-delete.component";
import { BulkMoveComponent } from "../vault/bulk-move.component";
import { BulkRestoreComponent } from "../vault/bulk-restore.component";
import { BulkShareComponent } from "../vault/bulk-share.component";
import { CiphersComponent } from "../vault/ciphers.component";
import { CollectionsComponent } from "../vault/collections.component";
import { FolderAddEditComponent } from "../vault/folder-add-edit.component";
import { ShareComponent } from "../vault/share.component";
import { PipesModule } from "./pipes/pipes.module";
import { SharedModule } from "./shared.module";
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
import { OrganizationBadgeModule } from "./vault/modules/organization-badge/organization-badge.module";
// Please do not add to this list of declarations - we should refactor these into modules when doing so makes sense until there are none left.
// If you are building new functionality, please create or extend a feature module instead.
@NgModule({
imports: [SharedModule, VaultFilterModule, OrganizationBadgeModule, PipesModule],
declarations: [
PremiumBadgeComponent,
AcceptEmergencyComponent,
AcceptFamilySponsorshipComponent,
AcceptOrganizationComponent,
AccessComponent,
AccountComponent,
AddCreditComponent,
AddEditComponent,
AddEditCustomFieldsComponent,
AddEditCustomFieldsComponent,
AdjustPaymentComponent,
AdjustStorageComponent,
AdjustSubscription,
ApiKeyComponent,
AttachmentsComponent,
BillingSyncApiKeyComponent,
BillingSyncKeyComponent,
BreachReportComponent,
BulkActionsComponent,
BulkDeleteComponent,
BulkMoveComponent,
BulkRestoreComponent,
BulkShareComponent,
ChangeEmailComponent,
ChangeKdfComponent,
ChangePasswordComponent,
ChangePlanComponent,
CiphersComponent,
CollectionsComponent,
CreateOrganizationComponent,
DeauthorizeSessionsComponent,
DeleteAccountComponent,
DeleteOrganizationComponent,
DisableSendPolicyComponent,
DomainRulesComponent,
DownloadLicenseComponent,
EmergencyAccessAddEditComponent,
EmergencyAccessAttachmentsComponent,
EmergencyAccessComponent,
EmergencyAccessConfirmComponent,
EmergencyAccessTakeoverComponent,
EmergencyAccessViewComponent,
EmergencyAddEditComponent,
ExportComponent,
ExposedPasswordsReportComponent,
FamiliesForEnterpriseSetupComponent,
FolderAddEditComponent,
FooterComponent,
FrontendLayoutComponent,
HintComponent,
ImportComponent,
InactiveTwoFactorReportComponent,
LockComponent,
LoginComponent,
MasterPasswordPolicyComponent,
NavbarComponent,
NestedCheckboxComponent,
OrganizationSwitcherComponent,
OrgAccountComponent,
OrgAddEditComponent,
OrganizationBillingComponent,
OrganizationLayoutComponent,
OrganizationPlansComponent,
OrganizationSubscriptionComponent,
OrgAttachmentsComponent,
OrgBulkConfirmComponent,
OrgBulkRemoveComponent,
OrgBulkStatusComponent,
OrgCiphersComponent,
OrgCollectionAddEditComponent,
OrgCollectionsComponent,
OrgEntityEventsComponent,
OrgEntityUsersComponent,
OrgEventsComponent,
OrgExportComponent,
OrgExposedPasswordsReportComponent,
OrgGroupAddEditComponent,
OrgGroupsComponent,
OrgImportComponent,
OrgInactiveTwoFactorReportComponent,
OrgManageCollectionsComponent,
OrgManageComponent,
OrgPeopleComponent,
OrgPoliciesComponent,
OrgPolicyEditComponent,
OrgResetPasswordComponent,
OrgReusedPasswordsReportComponent,
OrgSettingComponent,
OrgToolsComponent,
OrgTwoFactorSetupComponent,
OrgSubscriptionHiddenComponent,
OrgUnsecuredWebsitesReportComponent,
OrgUserAddEditComponent,
OrgUserConfirmComponent,
OrgUserGroupsComponent,
OrgWeakPasswordsReportComponent,
GeneratorComponent,
PasswordGeneratorHistoryComponent,
PasswordGeneratorPolicyComponent,
PasswordRepromptComponent,
PasswordStrengthComponent,
PaymentComponent,
PaymentMethodComponent,
PersonalOwnershipPolicyComponent,
PreferencesComponent,
PremiumBadgeComponent,
PremiumComponent,
ProfileComponent,
ProvidersComponent,
PurgeVaultComponent,
RecoverDeleteComponent,
RecoverTwoFactorComponent,
RegisterComponent,
RemovePasswordComponent,
ReportCardComponent,
ReportListComponent,
ReportsComponent,
RequireSsoPolicyComponent,
ResetPasswordPolicyComponent,
ReusedPasswordsReportComponent,
SecurityComponent,
SecurityKeysComponent,
SendAddEditComponent,
SendComponent,
SendEffluxDatesComponent,
SendOptionsPolicyComponent,
SetPasswordComponent,
SettingsComponent,
ShareComponent,
SingleOrgPolicyComponent,
SponsoredFamiliesComponent,
SponsoringOrgRowComponent,
SsoComponent,
SubscriptionComponent,
TaxInfoComponent,
ToolsComponent,
TwoFactorAuthenticationPolicyComponent,
TwoFactorAuthenticatorComponent,
TwoFactorComponent,
TwoFactorDuoComponent,
TwoFactorEmailComponent,
TwoFactorOptionsComponent,
TwoFactorRecoveryComponent,
TwoFactorSetupComponent,
TwoFactorVerifyComponent,
TwoFactorWebAuthnComponent,
TwoFactorYubiKeyComponent,
UnsecuredWebsitesReportComponent,
UpdateKeyComponent,
UpdateLicenseComponent,
UpdatePasswordComponent,
UpdateTempPasswordComponent,
UserBillingHistoryComponent,
UserLayoutComponent,
UserSubscriptionComponent,
UserVerificationComponent,
VaultTimeoutInputComponent,
VerifyEmailComponent,
VerifyEmailTokenComponent,
VerifyRecoverDeleteComponent,
WeakPasswordsReportComponent,
],
exports: [
PremiumBadgeComponent,
AcceptEmergencyComponent,
AcceptOrganizationComponent,
AccessComponent,
AccountComponent,
AddCreditComponent,
AddEditComponent,
AddEditCustomFieldsComponent,
AddEditCustomFieldsComponent,
AdjustPaymentComponent,
AdjustStorageComponent,
AdjustSubscription,
ApiKeyComponent,
AttachmentsComponent,
BreachReportComponent,
BulkActionsComponent,
BulkDeleteComponent,
BulkMoveComponent,
BulkRestoreComponent,
BulkShareComponent,
ChangeEmailComponent,
ChangeKdfComponent,
ChangePasswordComponent,
ChangePlanComponent,
CiphersComponent,
CollectionsComponent,
CreateOrganizationComponent,
DeauthorizeSessionsComponent,
DeleteAccountComponent,
DeleteOrganizationComponent,
DisableSendPolicyComponent,
DomainRulesComponent,
DownloadLicenseComponent,
EmergencyAccessAddEditComponent,
EmergencyAccessAttachmentsComponent,
EmergencyAccessComponent,
EmergencyAccessConfirmComponent,
EmergencyAccessTakeoverComponent,
EmergencyAccessViewComponent,
EmergencyAddEditComponent,
ExportComponent,
ExposedPasswordsReportComponent,
FamiliesForEnterpriseSetupComponent,
FolderAddEditComponent,
FooterComponent,
FrontendLayoutComponent,
HintComponent,
ImportComponent,
InactiveTwoFactorReportComponent,
LockComponent,
LoginComponent,
MasterPasswordPolicyComponent,
NavbarComponent,
NestedCheckboxComponent,
OrganizationSwitcherComponent,
OrgAccountComponent,
OrgAddEditComponent,
OrganizationBillingComponent,
OrganizationLayoutComponent,
OrganizationPlansComponent,
OrganizationSubscriptionComponent,
OrgAttachmentsComponent,
OrgBulkConfirmComponent,
OrgBulkRemoveComponent,
OrgBulkStatusComponent,
OrgCiphersComponent,
OrgCollectionAddEditComponent,
OrgCollectionsComponent,
OrgEntityEventsComponent,
OrgEntityUsersComponent,
OrgEventsComponent,
OrgExportComponent,
OrgExposedPasswordsReportComponent,
OrgGroupAddEditComponent,
OrgGroupsComponent,
OrgImportComponent,
OrgInactiveTwoFactorReportComponent,
OrgManageCollectionsComponent,
OrgManageComponent,
OrgPeopleComponent,
OrgPoliciesComponent,
OrgPolicyEditComponent,
OrgResetPasswordComponent,
OrgReusedPasswordsReportComponent,
OrgSettingComponent,
OrgToolsComponent,
OrgTwoFactorSetupComponent,
OrgUnsecuredWebsitesReportComponent,
OrgUserAddEditComponent,
OrgUserConfirmComponent,
OrgUserGroupsComponent,
OrgWeakPasswordsReportComponent,
GeneratorComponent,
PasswordGeneratorHistoryComponent,
PasswordGeneratorPolicyComponent,
PasswordRepromptComponent,
PasswordStrengthComponent,
PaymentComponent,
PaymentMethodComponent,
PersonalOwnershipPolicyComponent,
PreferencesComponent,
PremiumBadgeComponent,
PremiumComponent,
ProfileComponent,
ProvidersComponent,
PurgeVaultComponent,
RecoverDeleteComponent,
RecoverTwoFactorComponent,
RegisterComponent,
RemovePasswordComponent,
ReportCardComponent,
ReportListComponent,
ReportsComponent,
RequireSsoPolicyComponent,
ResetPasswordPolicyComponent,
ReusedPasswordsReportComponent,
SecurityComponent,
SecurityKeysComponent,
SendAddEditComponent,
SendComponent,
SendEffluxDatesComponent,
SendOptionsPolicyComponent,
SetPasswordComponent,
SettingsComponent,
ShareComponent,
SingleOrgPolicyComponent,
SponsoredFamiliesComponent,
SponsoringOrgRowComponent,
SsoComponent,
SubscriptionComponent,
TaxInfoComponent,
ToolsComponent,
TwoFactorAuthenticationPolicyComponent,
TwoFactorAuthenticatorComponent,
TwoFactorComponent,
TwoFactorDuoComponent,
TwoFactorEmailComponent,
TwoFactorOptionsComponent,
TwoFactorRecoveryComponent,
TwoFactorSetupComponent,
TwoFactorVerifyComponent,
TwoFactorWebAuthnComponent,
TwoFactorYubiKeyComponent,
UnsecuredWebsitesReportComponent,
UpdateKeyComponent,
UpdateLicenseComponent,
UpdatePasswordComponent,
UpdateTempPasswordComponent,
UserBillingHistoryComponent,
UserLayoutComponent,
UserSubscriptionComponent,
UserVerificationComponent,
VaultTimeoutInputComponent,
VerifyEmailComponent,
VerifyEmailTokenComponent,
VerifyRecoverDeleteComponent,
WeakPasswordsReportComponent,
],
})
export class LooseComponentsModule {}

View File

@@ -0,0 +1,14 @@
import { Pipe, PipeTransform } from "@angular/core";
import { Organization } from "jslib-common/models/domain/organization";
@Pipe({
name: "orgNameFromId",
pure: true,
})
export class GetOrgNameFromIdPipe implements PipeTransform {
transform(value: string, organizations: Organization[]) {
const orgName = organizations.find((o) => o.id === value)?.name;
return orgName;
}
}

View File

@@ -0,0 +1,10 @@
import { NgModule } from "@angular/core";
import { GetOrgNameFromIdPipe } from "./get-organization-name.pipe";
@NgModule({
imports: [],
declarations: [GetOrgNameFromIdPipe],
exports: [GetOrgNameFromIdPipe],
})
export class PipesModule {}

View File

@@ -0,0 +1,149 @@
import { DragDropModule } from "@angular/cdk/drag-drop";
import { DatePipe, registerLocaleData, CommonModule } from "@angular/common";
import localeAf from "@angular/common/locales/af";
import localeAz from "@angular/common/locales/az";
import localeBe from "@angular/common/locales/be";
import localeBg from "@angular/common/locales/bg";
import localeBn from "@angular/common/locales/bn";
import localeBs from "@angular/common/locales/bs";
import localeCa from "@angular/common/locales/ca";
import localeCs from "@angular/common/locales/cs";
import localeDa from "@angular/common/locales/da";
import localeDe from "@angular/common/locales/de";
import localeEl from "@angular/common/locales/el";
import localeEnGb from "@angular/common/locales/en-GB";
import localeEnIn from "@angular/common/locales/en-IN";
import localeEo from "@angular/common/locales/eo";
import localeEs from "@angular/common/locales/es";
import localeEt from "@angular/common/locales/et";
import localeFi from "@angular/common/locales/fi";
import localeFil from "@angular/common/locales/fil";
import localeFr from "@angular/common/locales/fr";
import localeHe from "@angular/common/locales/he";
import localeHi from "@angular/common/locales/hi";
import localeHr from "@angular/common/locales/hr";
import localeHu from "@angular/common/locales/hu";
import localeId from "@angular/common/locales/id";
import localeIt from "@angular/common/locales/it";
import localeJa from "@angular/common/locales/ja";
import localeKa from "@angular/common/locales/ka";
import localeKm from "@angular/common/locales/km";
import localeKn from "@angular/common/locales/kn";
import localeKo from "@angular/common/locales/ko";
import localeLv from "@angular/common/locales/lv";
import localeMl from "@angular/common/locales/ml";
import localeNb from "@angular/common/locales/nb";
import localeNl from "@angular/common/locales/nl";
import localeNn from "@angular/common/locales/nn";
import localePl from "@angular/common/locales/pl";
import localePtBr from "@angular/common/locales/pt";
import localePtPt from "@angular/common/locales/pt-PT";
import localeRo from "@angular/common/locales/ro";
import localeRu from "@angular/common/locales/ru";
import localeSi from "@angular/common/locales/si";
import localeSk from "@angular/common/locales/sk";
import localeSl from "@angular/common/locales/sl";
import localeSr from "@angular/common/locales/sr";
import localeSv from "@angular/common/locales/sv";
import localeTr from "@angular/common/locales/tr";
import localeUk from "@angular/common/locales/uk";
import localeVi from "@angular/common/locales/vi";
import localeZhCn from "@angular/common/locales/zh-Hans";
import localeZhTw from "@angular/common/locales/zh-Hant";
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { RouterModule } from "@angular/router";
import { BadgeModule, ButtonModule, CalloutModule, MenuModule } from "@bitwarden/components";
import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { ToastrModule } from "ngx-toastr";
import { JslibModule } from "jslib-angular/jslib.module";
registerLocaleData(localeAf, "af");
registerLocaleData(localeAz, "az");
registerLocaleData(localeBe, "be");
registerLocaleData(localeBg, "bg");
registerLocaleData(localeBn, "bn");
registerLocaleData(localeBs, "bs");
registerLocaleData(localeCa, "ca");
registerLocaleData(localeCs, "cs");
registerLocaleData(localeDa, "da");
registerLocaleData(localeDe, "de");
registerLocaleData(localeEl, "el");
registerLocaleData(localeEnGb, "en-GB");
registerLocaleData(localeEnIn, "en-IN");
registerLocaleData(localeEo, "eo");
registerLocaleData(localeEs, "es");
registerLocaleData(localeEt, "et");
registerLocaleData(localeFi, "fi");
registerLocaleData(localeFil, "fil");
registerLocaleData(localeFr, "fr");
registerLocaleData(localeHe, "he");
registerLocaleData(localeHi, "hi");
registerLocaleData(localeHr, "hr");
registerLocaleData(localeHu, "hu");
registerLocaleData(localeId, "id");
registerLocaleData(localeIt, "it");
registerLocaleData(localeJa, "ja");
registerLocaleData(localeKa, "ka");
registerLocaleData(localeKm, "km");
registerLocaleData(localeKn, "kn");
registerLocaleData(localeKo, "ko");
registerLocaleData(localeLv, "lv");
registerLocaleData(localeMl, "ml");
registerLocaleData(localeNb, "nb");
registerLocaleData(localeNl, "nl");
registerLocaleData(localeNn, "nn");
registerLocaleData(localePl, "pl");
registerLocaleData(localePtBr, "pt-BR");
registerLocaleData(localePtPt, "pt-PT");
registerLocaleData(localeRo, "ro");
registerLocaleData(localeRu, "ru");
registerLocaleData(localeSi, "si");
registerLocaleData(localeSk, "sk");
registerLocaleData(localeSl, "sl");
registerLocaleData(localeSr, "sr");
registerLocaleData(localeSv, "sv");
registerLocaleData(localeTr, "tr");
registerLocaleData(localeUk, "uk");
registerLocaleData(localeVi, "vi");
registerLocaleData(localeZhCn, "zh-CN");
registerLocaleData(localeZhTw, "zh-TW");
@NgModule({
imports: [
CommonModule,
DragDropModule,
FormsModule,
InfiniteScrollModule,
JslibModule,
ReactiveFormsModule,
RouterModule,
BadgeModule,
ButtonModule,
CalloutModule,
ToastrModule,
BadgeModule,
ButtonModule,
MenuModule,
],
exports: [
CommonModule,
DragDropModule,
FormsModule,
InfiniteScrollModule,
JslibModule,
ReactiveFormsModule,
RouterModule,
BadgeModule,
ButtonModule,
CalloutModule,
ToastrModule,
BadgeModule,
ButtonModule,
MenuModule,
],
providers: [DatePipe],
bootstrap: [],
})
export class SharedModule {}

View File

@@ -0,0 +1,74 @@
<ng-container *ngIf="show">
<div class="filter-heading">
<button
(click)="toggleCollapse(collectionsGrouping)"
[attr.aria-expanded]="!isCollapsed(collectionsGrouping)"
aria-controls="collection-filters"
title="{{ 'toggleCollapse' | i18n }}"
class="toggle-button"
>
<i
class="bwi bwi-fw"
[ngClass]="{
'bwi-angle-right': isCollapsed(collectionsGrouping),
'bwi-angle-down': !isCollapsed(collectionsGrouping)
}"
aria-hidden="true"
></i>
</button>
<h3 class="filter-title">&nbsp;{{ collectionsGrouping.name | i18n }}</h3>
</div>
<ul id="collection-filters" *ngIf="!isCollapsed(collectionsGrouping)" class="filter-options">
<ng-template #recursiveCollections let-collections>
<li
*ngFor="let c of collections"
[ngClass]="{
active: c.node.id === activeFilter.selectedCollectionId
}"
class="filter-option"
>
<span class="filter-buttons">
<button
class="toggle-button"
*ngIf="c.children.length"
(click)="collapse(c.node)"
title="{{ 'toggleCollapse' | i18n }}"
[attr.aria-expanded]="!isCollapsed(c.node)"
[attr.aria-controls]="c.node.name + '_children'"
>
<i
class="bwi bwi-fw"
[ngClass]="{
'bwi-angle-right': isCollapsed(c.node),
'bwi-angle-down': !isCollapsed(c.node)
}"
aria-hidden="true"
></i>
</button>
<button class="filter-button" (click)="applyFilter(c.node)">
<i
*ngIf="c.children.length === 0"
class="bwi bwi-collection bwi-fw"
aria-hidden="true"
></i
>&nbsp;{{ c.node.name }}
</button>
</span>
<ul
[id]="c.node.name + '_children'"
class="nested-filter-options"
*ngIf="c.children.length && !isCollapsed(c.node)"
>
<ng-container
*ngTemplateOutlet="recursiveCollections; context: { $implicit: c.children }"
>
</ng-container>
</ul>
</li>
</ng-template>
<ng-container
*ngTemplateOutlet="recursiveCollections; context: { $implicit: nestedCollections }"
>
</ng-container>
</ul>
</ng-container>

View File

@@ -0,0 +1,9 @@
import { Component } from "@angular/core";
import { CollectionFilterComponent as BaseCollectionFilterComponent } from "jslib-angular/modules/vault-filter/components/collection-filter.component";
@Component({
selector: "app-collection-filter",
templateUrl: "collection-filter.component.html",
})
export class CollectionFilterComponent extends BaseCollectionFilterComponent {}

View File

@@ -0,0 +1,82 @@
<ng-container *ngIf="!hide">
<div class="filter-heading">
<button
class="toggle-button"
(click)="toggleCollapse(foldersGrouping)"
[attr.aria-expanded]="!isCollapsed(foldersGrouping)"
aria-controls="folder-filters"
title="{{ 'toggleCollapse' | i18n }}"
>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed(foldersGrouping),
'bwi-angle-down': !isCollapsed(foldersGrouping)
}"
></i>
</button>
<h3 class="filter-title">&nbsp;{{ "folders" | i18n }}</h3>
<button
class="text-muted ml-auto add-button"
(click)="addFolder()"
appA11yTitle="{{ 'addFolder' | i18n }}"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
</button>
</div>
<ul id="folder-filters" *ngIf="!isCollapsed(foldersGrouping)" class="filter-options">
<ng-template #recursiveFolders let-folders>
<li
*ngFor="let f of folders"
[ngClass]="{
active: f.node.id === activeFilter.selectedFolderId && activeFilter.selectedFolder
}"
class="filter-option"
>
<span class="filter-buttons">
<button
*ngIf="f.children.length"
title="{{ 'toggleCollapse' | i18n }}"
(click)="toggleCollapse(f.node)"
[attr.aria-expanded]="!isCollapsed(f.node)"
[attr.aria-controls]="f.node.name + '_children'"
class="toggle-button"
>
<i
class="bwi bwi-fw"
[ngClass]="{
'bwi-angle-right': isCollapsed(f.node),
'bwi-angle-down': !isCollapsed(f.node)
}"
aria-hidden="true"
></i>
</button>
<button class="filter-button" (click)="applyFilter(f.node)">
<i *ngIf="f.children.length === 0" class="bwi bwi-fw bwi-folder" aria-hidden="true"></i
>&nbsp;{{ f.node.name }}
</button>
<button
class="edit-button"
(click)="editFolder(f.node)"
appA11yTitle="{{ 'editFolder' | i18n }}"
*ngIf="f.node.id"
>
<i class="bwi bwi-pencil bwi-fw" aria-hidden="true"></i>
</button>
</span>
<ul
[id]="f.node.name + '_children'"
class="nested-filter-options"
*ngIf="f.children.length && !isCollapsed(f.node)"
>
<ng-container *ngTemplateOutlet="recursiveFolders; context: { $implicit: f.children }">
</ng-container>
</ul>
</li>
</ng-template>
<ng-container
*ngTemplateOutlet="recursiveFolders; context: { $implicit: nestedFolders }"
></ng-container>
</ul>
</ng-container>

View File

@@ -0,0 +1,9 @@
import { Component } from "@angular/core";
import { FolderFilterComponent as BaseFolderFilterComponent } from "jslib-angular/modules/vault-filter/components/folder-filter.component";
@Component({
selector: "app-folder-filter",
templateUrl: "folder-filter.component.html",
})
export class FolderFilterComponent extends BaseFolderFilterComponent {}

View File

@@ -0,0 +1,155 @@
<ng-container *ngIf="!hide">
<ng-container [ngSwitch]="displayMode">
<ng-container *ngSwitchCase="'noOrganizations'">
<ul class="filter-options">
<li class="filter-option active">
<span class="filter-buttons">
<button class="filter-button">
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
{{ "myVault" | i18n }}
</button>
</span>
</li>
<li class="filter-option">
<span class="filter-buttons">
<a href="#" routerLink="/create-organization" class="filter-button">
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
&nbsp;{{ "newOrganization" | i18n }}
</a>
</span>
</li>
</ul>
</ng-container>
<ng-container *ngSwitchCase="'personalOwnershipPolicy'">
<div class="filter-heading">
<button
(click)="toggleCollapse()"
title="{{ 'toggleCollapse' | i18n }}"
class="toggle-button"
[attr.aria-expanded]="!isCollapsed"
aria-controls="organization-filters"
>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed,
'bwi-angle-down': !isCollapsed
}"
></i>
</button>
<button
class="filter-button"
(click)="clearFilter()"
[ngClass]="{ active: !hasActiveFilter }"
>
&nbsp;{{ organizationGrouping.name | i18n }}
</button>
</div>
<ul id="organization-filters" *ngIf="!isCollapsed" class="filter-options">
<li
class="filter-option"
*ngFor="let organization of organizations"
[ngClass]="{ active: organization.id === activeFilter.selectedOrganizationId }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyOrganizationFilter(organization)">
<i class="bwi bwi-fw bwi-business" aria-hidden="true"></i>
{{ organization.name }}
</button>
<ng-container *ngIf="organization.id === activeFilter.selectedOrganizationId">
<button [bitMenuTriggerFor]="orgMenu" class="org-options ml-auto">
<i class="bwi bwi-ellipsis-v" aria-hidden="true"></i>
</button>
<bit-menu class="filter-organization-options" #orgMenu>
<app-organization-options [organization]="organization"></app-organization-options>
</bit-menu>
</ng-container>
</span>
</li>
<li class="filter-option">
<span class="filter-buttons">
<a href="#" routerLink="/create-organization" class="filter-button">
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
&nbsp;{{ "newOrganization" | i18n }}
</a>
</span>
</li>
</ul>
</ng-container>
<ng-container *ngSwitchCase="'singleOrganizationAndPersonalOwnershipPolicies'">
<div class="filter-heading">
<button class="filter-button active">
<i class="bwi bwi-fw bwi-business" aria-hidden="true"></i>
{{ organizations[0].name }}
</button>
</div>
</ng-container>
<ng-container *ngSwitchDefault>
<div class="filter-heading">
<button
class="toggle-button"
title="{{ 'toggleCollapse' | i18n }}"
(click)="toggleCollapse()"
[attr.aria-expanded]="!isCollapsed"
aria-controls="organization-filters"
>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed,
'bwi-angle-down': !isCollapsed
}"
></i>
</button>
<button
class="filter-button"
(click)="clearFilter()"
[ngClass]="{ active: !hasActiveFilter }"
>
&nbsp;{{ organizationGrouping.name | i18n }}
</button>
</div>
<ul id="organization-filters" *ngIf="!isCollapsed" class="filter-options">
<li class="filter-option" [ngClass]="{ active: activeFilter.myVaultOnly }">
<span class="filter-buttons">
<button class="filter-button" (click)="applyMyVaultFilter()">
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
{{ "myVault" | i18n }}
</button>
</span>
</li>
<li
class="filter-option"
*ngFor="let organization of organizations"
[ngClass]="{ active: organization.id === activeFilter.selectedOrganizationId }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyOrganizationFilter(organization)">
<i class="bwi bwi-fw bwi-business" aria-hidden="true"></i>
{{ organization.name }}
</button>
<ng-container>
<button [bitMenuTriggerFor]="orgMenu" class="org-options ml-auto">
<i class="bwi bwi-ellipsis-v" aria-hidden="true"></i>
</button>
<bit-menu class="filter-organization-options" #orgMenu>
<app-organization-options [organization]="organization"></app-organization-options>
</bit-menu>
</ng-container>
</span>
</li>
<li class="filter-option" *ngIf="!(displayMode === 'singleOrganizationPolicy')">
<span class="filter-buttons">
<a href="#" routerLink="/create-organization" class="filter-button">
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
&nbsp;{{ "newOrganization" | i18n }}
</a>
</span>
</li>
</ul>
</ng-container>
</ng-container>
<hr />
</ng-container>

View File

@@ -0,0 +1,11 @@
import { Component } from "@angular/core";
import { OrganizationFilterComponent as BaseOrganizationFilterComponent } from "jslib-angular/modules/vault-filter/components/organization-filter.component";
@Component({
selector: "app-organization-filter",
templateUrl: "organization-filter.component.html",
})
export class OrganizationFilterComponent extends BaseOrganizationFilterComponent {
displayText = "allVaults";
}

View File

@@ -0,0 +1,43 @@
<ng-container *ngIf="!loaded">
<i
class="bwi bwi-spinner bwi-spin text-muted tw-m-2"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container>
<div *ngIf="loaded" class="tw-max-w-[300px] tw-min-w-[200px] tw-flex tw-flex-col">
<button
*ngIf="allowEnrollmentChanges(organization) && !organization.resetPasswordEnrolled"
class="dropdown-item"
(click)="toggleResetPasswordEnrollment(organization)"
>
<i class="bwi bwi-fw bwi-key" aria-hidden="true"></i>
{{ "enrollPasswordReset" | i18n }}
</button>
<button
*ngIf="allowEnrollmentChanges(organization) && organization.resetPasswordEnrolled"
class="dropdown-item"
(click)="toggleResetPasswordEnrollment(organization)"
>
<i class="bwi bwi-fw bwi-undo" aria-hidden="true"></i>
{{ "withdrawPasswordReset" | i18n }}
</button>
<ng-container *ngIf="organization.useSso && organization.identifier">
<button
*ngIf="organization.ssoBound; else linkSso"
class="dropdown-item"
(click)="unlinkSso(organization)"
>
<i class="bwi bwi-fw bwi-chain-broken" aria-hidden="true"></i>
{{ "unlinkSso" | i18n }}
</button>
<ng-template #linkSso>
<app-link-sso [organization]="organization"> </app-link-sso>
</ng-template>
</ng-container>
<button class="dropdown-item text-danger" (click)="leave(organization)">
<i class="bwi bwi-fw bwi-sign-out" aria-hidden="true"></i>
{{ "leave" | i18n }}
</button>
</div>

View File

@@ -1,10 +1,9 @@
import { Component, Input, OnInit } from "@angular/core";
import { Component, Input } 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 { OrganizationService } from "jslib-common/abstractions/organization.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";
@@ -15,19 +14,17 @@ import { Policy } from "jslib-common/models/domain/policy";
import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest";
@Component({
selector: "app-organizations",
templateUrl: "organizations.component.html",
selector: "app-organization-options",
templateUrl: "organization-options.component.html",
})
export class OrganizationsComponent implements OnInit {
@Input() vault = false;
organizations: Organization[];
export class OrganizationOptionsComponent {
actionPromise: Promise<any>;
policies: Policy[];
loaded = false;
actionPromise: Promise<any>;
@Input() organization: Organization;
constructor(
private organizationService: OrganizationService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private apiService: ApiService,
@@ -38,16 +35,10 @@ export class OrganizationsComponent implements OnInit {
) {}
async ngOnInit() {
if (!this.vault) {
await this.syncService.fullSync(true);
await this.load();
}
await this.load();
}
async load() {
const orgs = await this.organizationService.getAll();
orgs.sort(Utils.getSortFunction(this.i18nService, "name"));
this.organizations = orgs;
this.policies = await this.policyService.getAll(PolicyType.ResetPassword);
this.loaded = true;
}
@@ -91,6 +82,7 @@ export class OrganizationsComponent implements OnInit {
this.platformUtilsService.showToast("success", null, "Unlinked SSO");
await this.load();
} catch (e) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message);
this.logService.error(e);
}
}
@@ -115,6 +107,7 @@ export class OrganizationsComponent implements OnInit {
this.platformUtilsService.showToast("success", null, this.i18nService.t("leftOrganization"));
await this.load();
} catch (e) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message);
this.logService.error(e);
}
}
@@ -182,6 +175,7 @@ export class OrganizationsComponent implements OnInit {
this.platformUtilsService.showToast("success", null, this.i18nService.t(toastStringRef));
await this.load();
} catch (e) {
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message);
this.logService.error(e);
}
}

View File

@@ -0,0 +1,33 @@
<ng-container *ngIf="show">
<ul class="filter-options">
<li class="filter-option" [ngClass]="{ active: activeFilter.status === 'all' }">
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter('all')">
<i class="bwi bwi-fw bwi-filter" aria-hidden="true"></i>&nbsp;{{ "allItems" | i18n }}
</button>
</span>
</li>
<li
*ngIf="!hideFavorites"
class="filter-option"
[ngClass]="{ active: activeFilter.status === 'favorites' }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter('favorites')">
<i class="bwi bwi-fw bwi-star" aria-hidden="true"></i>&nbsp;{{ "favorites" | i18n }}
</button>
</span>
</li>
<li
*ngIf="!hideTrash"
class="filter-option"
[ngClass]="{ active: activeFilter.status === 'trash' }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter('trash')">
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>&nbsp;{{ "trash" | i18n }}
</button>
</span>
</li>
</ul>
</ng-container>

View File

@@ -0,0 +1,9 @@
import { Component } from "@angular/core";
import { StatusFilterComponent as BaseStatusFilterComponent } from "jslib-angular/modules/vault-filter/components/status-filter.component";
@Component({
selector: "app-status-filter",
templateUrl: "status-filter.component.html",
})
export class StatusFilterComponent extends BaseStatusFilterComponent {}

View File

@@ -0,0 +1,60 @@
<div class="filter-heading">
<button
class="toggle-button"
[attr.aria-expanded]="!isCollapsed"
aria-controls="type-filters"
(click)="toggleCollapse()"
title="{{ 'toggleCollapse' | i18n }}"
>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed,
'bwi-angle-down': !isCollapsed
}"
></i>
</button>
<h3>&nbsp;{{ "types" | i18n }}</h3>
</div>
<ul id="type-filters" *ngIf="!isCollapsed" class="filter-options">
<li
class="filter-option"
[ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.Login }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter(cipherTypeEnum.Login)">
<i class="bwi bwi-fw bwi-globe" aria-hidden="true"></i>&nbsp;{{ "typeLogin" | i18n }}
</button>
</span>
</li>
<li class="filter-option" [ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.Card }">
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter(cipherTypeEnum.Card)">
<i class="bwi bwi-fw bwi-credit-card" aria-hidden="true"></i>&nbsp;{{ "typeCard" | i18n }}
</button>
</span>
</li>
<li
class="filter-option"
[ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.Identity }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter(cipherTypeEnum.Identity)">
<i class="bwi bwi-fw bwi-id-card" aria-hidden="true"></i>&nbsp;{{ "typeIdentity" | i18n }}
</button>
</span>
</li>
<li
class="filter-option"
[ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.SecureNote }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter(cipherTypeEnum.SecureNote)">
<i class="bwi bwi-fw bwi-sticky-note" aria-hidden="true"></i>&nbsp;{{
"typeSecureNote" | i18n
}}
</button>
</span>
</li>
</ul>

View File

@@ -0,0 +1,9 @@
import { Component } from "@angular/core";
import { TypeFilterComponent as BaseTypeFilterComponent } from "jslib-angular/modules/vault-filter/components/type-filter.component";
@Component({
selector: "app-type-filter",
templateUrl: "type-filter.component.html",
})
export class TypeFilterComponent extends BaseTypeFilterComponent {}

View File

@@ -0,0 +1,80 @@
<div class="card vault-filters">
<div class="container loading-spinner" *ngIf="!isLoaded">
<i class="bwi bwi-spinner bwi-spin bwi-3x" aria-hidden="true"></i>
</div>
<div *ngIf="isLoaded">
<div class="card-header d-flex">
{{ "filters" | i18n }}
<a
class="ml-auto"
href="https://bitwarden.com/help/searching-vault/"
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
<div class="card-body">
<input
type="search"
placeholder="{{ (searchPlaceholder | i18n) || ('searchVault' | i18n) }}"
id="search"
class="form-control"
[(ngModel)]="searchText"
(input)="searchTextChanged()"
autocomplete="off"
appAutofocus
/>
<app-organization-filter
*ngIf="showOrgFilter"
[hide]="hideOrganizations"
[activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes"
[organizations]="organizations"
[activePersonalOwnershipPolicy]="activePersonalOwnershipPolicy"
[activeSingleOrganizationPolicy]="activeSingleOrganizationPolicy"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)"
></app-organization-filter>
<div class="filter">
<app-status-filter
[hideFavorites]="!showFavorites"
[hideTrash]="hideTrash"
[activeFilter]="activeFilter"
(onFilterChange)="applyFilter($event)"
></app-status-filter>
</div>
<div class="filter">
<app-type-filter
[activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)"
></app-type-filter>
</div>
<div class="filter">
<app-folder-filter
[hide]="!showFolders"
[activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes"
[folderNodes]="folders"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)"
(onAddFolder)="addFolder()"
(onEditFolder)="editFolder($event)"
></app-folder-filter>
</div>
<div class="filter">
<app-collection-filter
[hide]="hideCollections"
[activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes"
[collectionNodes]="collections"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)"
></app-collection-filter>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,44 @@
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { VaultFilterComponent as BaseVaultFilterComponent } from "jslib-angular/modules/vault-filter/vault-filter.component";
import { VaultFilterService } from "jslib-angular/modules/vault-filter/vault-filter.service";
import { Organization } from "jslib-common/models/domain/organization";
@Component({
selector: "app-vault-filter",
templateUrl: "vault-filter.component.html",
})
export class VaultFilterComponent extends BaseVaultFilterComponent {
@Input() showOrgFilter = true;
@Input() showFolders = true;
@Input() showFavorites = true;
@Output() onSearchTextChanged = new EventEmitter<string>();
searchPlaceholder: string;
searchText = "";
organization: Organization;
constructor(vaultFilterService: VaultFilterService) {
super(vaultFilterService);
}
searchTextChanged() {
this.onSearchTextChanged.emit(this.searchText);
}
// This method exists because the vault component gets its data mixed up during the initial sync on first login. It looks for data before the sync is complete.
// It should be removed as soon as doing so makes sense.
async reloadOrganizations() {
this.organizations = await this.vaultFilterService.buildOrganizations();
this.activePersonalOwnershipPolicy =
await this.vaultFilterService.checkForPersonalOwnershipPolicy();
this.activeSingleOrganizationPolicy =
await this.vaultFilterService.checkForSingleOrganizationPolicy();
}
async initCollections() {
return await this.vaultFilterService.buildCollections(this.organization?.id);
}
}

View File

@@ -0,0 +1,50 @@
import { NgModule } from "@angular/core";
import { VaultFilterService } from "jslib-angular/modules/vault-filter/vault-filter.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { FolderService } from "jslib-common/abstractions/folder.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SharedModule } from "../shared.module";
import { CollectionFilterComponent } from "./components/collection-filter.component";
import { FolderFilterComponent } from "./components/folder-filter.component";
import { LinkSsoComponent } from "./components/link-sso.component";
import { OrganizationFilterComponent } from "./components/organization-filter.component";
import { OrganizationOptionsComponent } from "./components/organization-options.component";
import { StatusFilterComponent } from "./components/status-filter.component";
import { TypeFilterComponent } from "./components/type-filter.component";
import { VaultFilterComponent } from "./vault-filter.component";
@NgModule({
imports: [SharedModule],
declarations: [
VaultFilterComponent,
CollectionFilterComponent,
FolderFilterComponent,
OrganizationFilterComponent,
OrganizationOptionsComponent,
StatusFilterComponent,
TypeFilterComponent,
LinkSsoComponent,
],
exports: [VaultFilterComponent],
providers: [
{
provide: VaultFilterService,
useClass: VaultFilterService,
deps: [
StateService,
OrganizationService,
FolderService,
CipherService,
CollectionService,
PolicyService,
],
},
],
})
export class VaultFilterModule {}

View File

@@ -0,0 +1,3 @@
import { VaultFilterService as BaseVaultFilterService } from "jslib-angular/modules/vault-filter/vault-filter.service";
export class VaultFilterService extends BaseVaultFilterService {}

View File

@@ -0,0 +1,16 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { IndividualVaultComponent } from "./individual-vault.component";
const routes: Routes = [
{
path: "",
component: IndividualVaultComponent,
data: { titleId: "vaults" },
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class IndividualVaultRoutingModule {}

View File

@@ -1,23 +1,25 @@
<div class="container page-content">
<div class="row">
<div class="col-3">
<app-vault-groupings
(onAllClicked)="clearGroupingFilters()"
(onFavoritesClicked)="filterFavorites()"
(onCipherTypeClicked)="filterCipherType($event)"
(onFolderClicked)="filterFolder($event.id)"
(onAddFolder)="addFolder()"
(onEditFolder)="editFolder($event.id)"
(onCollectionClicked)="filterCollection($event.id)"
(onSearchTextChanged)="filterSearchText($event)"
(onTrashClicked)="filterDeleted()"
>
</app-vault-groupings>
<div class="groupings">
<div class="content">
<div class="inner-content">
<app-vault-filter
#vaultFilter
[activeFilter]="activeFilter"
(onFilterChange)="applyVaultFilter($event)"
(onAddFolder)="addFolder()"
(onEditFolder)="editFolder($event.id)"
(onSearchTextChanged)="filterSearchText($event)"
></app-vault-filter>
</div>
</div>
</div>
</div>
<div class="col-6">
<div [ngClass]="{ 'col-6': isShowingCards, 'col-9': !isShowingCards }">
<div class="page-header d-flex">
<h1>
{{ "myVault" | i18n }}
{{ "vaultItems" | i18n }}
<small #actionSpinner [appApiAction]="ciphersComponent.actionPromise">
<ng-container *ngIf="actionSpinner.loading">
<i
@@ -30,19 +32,26 @@
</small>
</h1>
<div class="ml-auto d-flex">
<app-vault-bulk-actions [ciphersComponent]="ciphersComponent" [deleted]="deleted">
<app-vault-bulk-actions
[ciphersComponent]="ciphersComponent"
[deleted]="activeFilter.status === 'trash'"
>
</app-vault-bulk-actions>
<button
type="button"
class="btn btn-outline-primary btn-sm"
(click)="addCipher()"
*ngIf="!deleted"
*ngIf="activeFilter.status !== 'trash'"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>{{ "addItem" | i18n }}
</button>
</div>
</div>
<app-callout type="warning" *ngIf="deleted" icon="bwi-exclamation-triangle">
<app-callout
type="warning"
*ngIf="activeFilter.status === 'trash'"
icon="bwi-exclamation-triangle"
>
{{ trashCleanupWarning }}
</app-callout>
<app-vault-ciphers
@@ -52,6 +61,7 @@
(onShareClicked)="shareCipher($event)"
(onCollectionsClicked)="editCipherCollections($event)"
(onCloneClicked)="cloneCipher($event)"
(onOrganzationBadgeClicked)="applyOrganizationFilter($event)"
>
</app-vault-ciphers>
</div>
@@ -92,45 +102,14 @@
</div>
<div class="card-body">
<p>{{ "premiumUpgradeUnlockFeatures" | i18n }}</p>
<a class="btn btn-block btn-outline-secondary" routerLink="/settings/premium">
<a
class="btn btn-block btn-outline-secondary"
routerLink="/settings/subscription/premium"
>
{{ "goPremium" | i18n }}
</a>
</div>
</div>
<div class="card mb-4">
<div class="card-header d-flex">
{{ "organizations" | i18n }}
<a
class="ml-auto"
href="https://bitwarden.com/help/about-organizations/"
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
<div class="card-body">
<app-organizations [vault]="true"></app-organizations>
</div>
</div>
<div class="card mt-4" *ngIf="showProviders">
<div class="card-header d-flex">
{{ "providers" | i18n }}
<a
class="ml-auto"
href="https://bitwarden.com/help/providers/"
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
<div class="card-body">
<app-providers vault="true"></app-providers>
</div>
</div>
</div>
</div>
</div>

View File

@@ -10,42 +10,41 @@ import {
import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
import { VaultFilter } from "jslib-angular/modules/vault-filter/models/vault-filter.model";
import { ModalService } from "jslib-angular/services/modal.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ProviderService } from "jslib-common/abstractions/provider.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { TokenService } from "jslib-common/abstractions/token.service";
import { CipherType } from "jslib-common/enums/cipherType";
import { CipherView } from "jslib-common/models/view/cipherView";
import { OrganizationsComponent } from "../settings/organizations.component";
import { UpdateKeyComponent } from "../settings/update-key.component";
import { AddEditComponent } from "./add-edit.component";
import { AttachmentsComponent } from "./attachments.component";
import { CiphersComponent } from "./ciphers.component";
import { CollectionsComponent } from "./collections.component";
import { FolderAddEditComponent } from "./folder-add-edit.component";
import { GroupingsComponent } from "./groupings.component";
import { ShareComponent } from "./share.component";
import { UpdateKeyComponent } from "../../../../settings/update-key.component";
import { AddEditComponent } from "../../../../vault/add-edit.component";
import { AttachmentsComponent } from "../../../../vault/attachments.component";
import { CiphersComponent } from "../../../../vault/ciphers.component";
import { CollectionsComponent } from "../../../../vault/collections.component";
import { FolderAddEditComponent } from "../../../../vault/folder-add-edit.component";
import { ShareComponent } from "../../../../vault/share.component";
import { VaultFilterComponent } from "../../../vault-filter/vault-filter.component";
import { VaultService } from "../../vault.service";
const BroadcasterSubscriptionId = "VaultComponent";
@Component({
selector: "app-vault",
templateUrl: "vault.component.html",
templateUrl: "individual-vault.component.html",
})
export class VaultComponent implements OnInit, OnDestroy {
@ViewChild(GroupingsComponent, { static: true }) groupingsComponent: GroupingsComponent;
export class IndividualVaultComponent implements OnInit, OnDestroy {
@ViewChild("vaultFilter", { static: true }) filterComponent: VaultFilterComponent;
@ViewChild(CiphersComponent, { static: true }) ciphersComponent: CiphersComponent;
@ViewChild(OrganizationsComponent, { static: true })
organizationsComponent: OrganizationsComponent;
@ViewChild("attachments", { read: ViewContainerRef, static: true })
attachmentsModalRef: ViewContainerRef;
@ViewChild("folderAddEdit", { read: ViewContainerRef, static: true })
@@ -62,13 +61,15 @@ export class VaultComponent implements OnInit, OnDestroy {
type: CipherType = null;
folderId: string = null;
collectionId: string = null;
organizationId: string = null;
myVaultOnly = false;
showVerifyEmail = false;
showBrowserOutdated = false;
showUpdateKey = false;
showPremiumCallout = false;
showProviders = false;
deleted = false;
trashCleanupWarning: string = null;
activeFilter: VaultFilter = new VaultFilter();
constructor(
private syncService: SyncService,
@@ -85,7 +86,9 @@ export class VaultComponent implements OnInit, OnDestroy {
private ngZone: NgZone,
private stateService: StateService,
private organizationService: OrganizationService,
private providerService: ProviderService
private vaultService: VaultService,
private cipherService: CipherService,
private passwordRepromptService: PasswordRepromptService
) {}
async ngOnInit() {
@@ -99,42 +102,42 @@ export class VaultComponent implements OnInit, OnDestroy {
this.route.queryParams.pipe(first()).subscribe(async (params) => {
await this.syncService.fullSync(false);
const canAccessPremium = await this.stateService.getCanAccessPremium();
this.showPremiumCallout =
!this.showVerifyEmail && !canAccessPremium && !this.platformUtilsService.isSelfHost();
this.showProviders = (await this.providerService.getAll()).length > 0;
await Promise.all([this.groupingsComponent.load(), this.organizationsComponent.load()]);
this.filterComponent.reloadCollectionsAndFolders(this.activeFilter);
this.filterComponent.reloadOrganizations();
this.showUpdateKey = !(await this.cryptoService.hasEncKey());
if (params == null) {
this.groupingsComponent.selectedAll = true;
await this.ciphersComponent.reload();
} else {
if (params.deleted) {
this.groupingsComponent.selectedTrash = true;
await this.filterDeleted();
} else if (params.favorites) {
this.groupingsComponent.selectedFavorites = true;
await this.filterFavorites();
} else if (params.type) {
const t = parseInt(params.type, null);
this.groupingsComponent.selectedType = t;
await this.filterCipherType(t);
} else if (params.folderId) {
this.groupingsComponent.selectedFolder = true;
this.groupingsComponent.selectedFolderId = params.folderId;
await this.filterFolder(params.folderId);
} else if (params.collectionId) {
this.groupingsComponent.selectedCollectionId = params.collectionId;
await this.filterCollection(params.collectionId);
} else {
this.groupingsComponent.selectedAll = true;
await this.ciphersComponent.reload();
if (params.cipherId) {
const cipherView = new CipherView();
cipherView.id = params.cipherId;
if (params.action === "clone") {
await this.cloneCipher(cipherView);
} else if (params.action === "edit") {
await this.editCipher(cipherView);
}
}
await this.ciphersComponent.reload();
this.route.queryParams.subscribe(async (params) => {
if (params.cipherId) {
if ((await this.cipherService.get(params.cipherId)) != null) {
this.editCipherId(params.cipherId);
} else {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("unknownCipher")
);
this.router.navigate([], {
queryParams: { cipherId: null },
queryParamsHandling: "merge",
});
}
}
});
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
@@ -142,8 +145,8 @@ export class VaultComponent implements OnInit, OnDestroy {
case "syncCompleted":
if (message.successfully) {
await Promise.all([
this.groupingsComponent.load(),
this.organizationsComponent.load(),
this.filterComponent.reloadCollectionsAndFolders(this.activeFilter),
this.filterComponent.reloadOrganizations(),
this.ciphersComponent.load(this.ciphersComponent.filter),
]);
this.changeDetectorRef.detectChanges();
@@ -155,72 +158,78 @@ export class VaultComponent implements OnInit, OnDestroy {
});
}
get isShowingCards() {
return (
this.showBrowserOutdated ||
this.showPremiumCallout ||
this.showUpdateKey ||
this.showVerifyEmail
);
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async clearGroupingFilters() {
this.ciphersComponent.showAddNew = true;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchVault");
await this.ciphersComponent.reload();
this.clearFilters();
this.go();
}
async filterFavorites() {
this.ciphersComponent.showAddNew = true;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchFavorites");
await this.ciphersComponent.reload((c) => c.favorite);
this.clearFilters();
this.favorites = true;
this.go();
}
async filterDeleted() {
this.ciphersComponent.showAddNew = false;
this.ciphersComponent.deleted = true;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchTrash");
await this.ciphersComponent.reload(null, true);
this.clearFilters();
this.deleted = true;
this.go();
}
async filterCipherType(type: CipherType) {
this.ciphersComponent.showAddNew = true;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchType");
await this.ciphersComponent.reload((c) => c.type === type);
this.clearFilters();
this.type = type;
this.go();
}
async filterFolder(folderId: string) {
this.ciphersComponent.showAddNew = true;
folderId = folderId === "none" ? null : folderId;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchFolder");
await this.ciphersComponent.reload((c) => c.folderId === folderId);
this.clearFilters();
this.folderId = folderId == null ? "none" : folderId;
this.go();
}
async filterCollection(collectionId: string) {
this.ciphersComponent.showAddNew = true;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchCollection");
await this.ciphersComponent.reload(
(c) => c.collectionIds != null && c.collectionIds.indexOf(collectionId) > -1
async applyVaultFilter(vaultFilter: VaultFilter) {
this.ciphersComponent.showAddNew = vaultFilter.status !== "trash";
this.activeFilter = vaultFilter;
await this.ciphersComponent.reload(this.buildFilter(), vaultFilter.status === "trash");
this.filterComponent.searchPlaceholder = this.vaultService.calculateSearchBarLocalizationString(
this.activeFilter
);
this.clearFilters();
this.collectionId = collectionId;
this.go();
}
async applyOrganizationFilter(orgId: string) {
if (orgId == null) {
this.activeFilter.resetOrganization();
this.activeFilter.myVaultOnly = true;
} else {
this.activeFilter.selectedOrganizationId = orgId;
}
await this.applyVaultFilter(this.activeFilter);
}
filterSearchText(searchText: string) {
this.ciphersComponent.searchText = searchText;
this.ciphersComponent.search(200);
}
private buildFilter(): (cipher: CipherView) => boolean {
return (cipher) => {
let cipherPassesFilter = true;
if (this.activeFilter.status === "favorites" && cipherPassesFilter) {
cipherPassesFilter = cipher.favorite;
}
if (this.activeFilter.status === "trash" && cipherPassesFilter) {
cipherPassesFilter = cipher.isDeleted;
}
if (this.activeFilter.cipherType != null && cipherPassesFilter) {
cipherPassesFilter = cipher.type === this.activeFilter.cipherType;
}
if (
this.activeFilter.selectedFolder &&
this.activeFilter.selectedFolderId != "none" &&
cipherPassesFilter
) {
cipherPassesFilter = cipher.folderId === this.activeFilter.selectedFolderId;
}
if (this.activeFilter.selectedCollectionId != null && cipherPassesFilter) {
cipherPassesFilter =
cipher.collectionIds != null &&
cipher.collectionIds.indexOf(this.activeFilter.selectedCollectionId) > -1;
}
if (this.activeFilter.selectedOrganizationId != null && cipherPassesFilter) {
cipherPassesFilter = cipher.organizationId === this.activeFilter.selectedOrganizationId;
}
if (this.activeFilter.myVaultOnly && cipherPassesFilter) {
cipherPassesFilter = cipher.organizationId === null;
}
return cipherPassesFilter;
};
}
async editCipherAttachments(cipher: CipherView) {
const canAccessPremium = await this.stateService.getCanAccessPremium();
if (cipher.organizationId == null && !canAccessPremium) {
@@ -292,7 +301,7 @@ export class VaultComponent implements OnInit, OnDestroy {
comp.folderId = null;
comp.onSavedFolder.subscribe(async () => {
modal.close();
await this.groupingsComponent.loadFolders();
await this.filterComponent.reloadCollectionsAndFolders(this.activeFilter);
});
}
);
@@ -306,13 +315,11 @@ export class VaultComponent implements OnInit, OnDestroy {
comp.folderId = folderId;
comp.onSavedFolder.subscribe(async () => {
modal.close();
await this.groupingsComponent.loadFolders();
await this.filterComponent.reloadCollectionsAndFolders(this.activeFilter);
});
comp.onDeletedFolder.subscribe(async () => {
modal.close();
await this.groupingsComponent.loadFolders();
await this.filterFolder("none");
this.groupingsComponent.selectedFolderId = null;
await this.filterComponent.reloadCollectionsAndFolders(this.activeFilter);
});
}
);
@@ -322,23 +329,41 @@ export class VaultComponent implements OnInit, OnDestroy {
const component = await this.editCipher(null);
component.type = this.type;
component.folderId = this.folderId === "none" ? null : this.folderId;
if (this.collectionId != null) {
const collection = this.groupingsComponent.collections.filter(
(c) => c.id === this.collectionId
if (this.activeFilter.selectedCollectionId != null) {
const collection = this.filterComponent.collections.fullList.filter(
(c) => c.id === this.activeFilter.selectedCollectionId
);
if (collection.length > 0) {
component.organizationId = collection[0].organizationId;
component.collectionIds = [this.collectionId];
component.collectionIds = [this.activeFilter.selectedCollectionId];
}
}
if (this.activeFilter.selectedFolderId && this.activeFilter.selectedFolder) {
component.folderId = this.activeFilter.selectedFolderId;
}
if (this.activeFilter.selectedOrganizationId) {
component.organizationId = this.activeFilter.selectedOrganizationId;
}
}
async editCipher(cipher: CipherView) {
return this.editCipherId(cipher?.id);
}
async editCipherId(id: string) {
const cipher = await this.cipherService.get(id);
if (cipher != null && cipher.reprompt != 0) {
if (!(await this.passwordRepromptService.showPasswordPrompt())) {
this.go({ cipherId: null });
return;
}
}
const [modal, childComponent] = await this.modalService.openViewRef(
AddEditComponent,
this.cipherAddEditModalRef,
(comp) => {
comp.cipherId = cipher == null ? null : cipher.id;
comp.cipherId = id;
comp.onSavedCipher.subscribe(async () => {
modal.close();
await this.ciphersComponent.refresh();
@@ -354,6 +379,10 @@ export class VaultComponent implements OnInit, OnDestroy {
}
);
modal.onClosedPromise().then(() => {
this.go({ cipherId: null });
});
return childComponent;
}
@@ -366,14 +395,6 @@ export class VaultComponent implements OnInit, OnDestroy {
await this.modalService.openViewRef(UpdateKeyComponent, this.updateKeyModalRef);
}
private clearFilters() {
this.folderId = null;
this.collectionId = null;
this.favorites = false;
this.type = null;
this.deleted = false;
}
private go(queryParams: any = null) {
if (queryParams == null) {
queryParams = {
@@ -388,6 +409,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.router.navigate([], {
relativeTo: this.route,
queryParams: queryParams,
queryParamsHandling: "merge",
replaceUrl: true,
});
}

View File

@@ -0,0 +1,13 @@
import { NgModule } from "@angular/core";
import { VaultModule } from "../../vault.module";
import { IndividualVaultRoutingModule } from "./individual-vault-routing.module";
import { IndividualVaultComponent } from "./individual-vault.component";
@NgModule({
imports: [VaultModule, IndividualVaultRoutingModule],
declarations: [IndividualVaultComponent],
exports: [IndividualVaultComponent],
})
export class IndividualVaultModule {}

View File

@@ -0,0 +1,12 @@
import { NgModule } from "@angular/core";
import { SharedModule } from "../../../shared.module";
import { OrganizationNameBadgeComponent } from "./organization-name-badge.component";
@NgModule({
imports: [SharedModule],
declarations: [OrganizationNameBadgeComponent],
exports: [OrganizationNameBadgeComponent],
})
export class OrganizationBadgeModule {}

View File

@@ -0,0 +1,9 @@
<button
bit-badge
[style.color]="textColor"
[style.background-color]="color"
appA11yTitle="{{ organizationName }}"
(click)="emitOnOrganizationClicked()"
>
{{ organizationName | ellipsis: 13 }}
</button>

View File

@@ -0,0 +1,59 @@
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { I18nService } from "jslib-common/abstractions/i18n.service";
@Component({
selector: "app-org-badge",
templateUrl: "organization-name-badge.component.html",
})
export class OrganizationNameBadgeComponent implements OnInit {
@Input() organizationName: string;
@Input() profileName: string;
@Output() onOrganizationClicked = new EventEmitter<string>();
color: string;
textColor: string;
constructor(private i18nService: I18nService) {}
ngOnInit(): void {
if (this.organizationName == null || this.organizationName === "") {
this.organizationName = this.i18nService.t("me");
this.color = this.stringToColor(this.profileName.toUpperCase());
}
if (this.color == null) {
this.color = this.stringToColor(this.organizationName.toUpperCase());
}
this.textColor = this.pickTextColorBasedOnBgColor();
}
// This value currently isn't stored anywhere, only calculated in the app-avatar component
// Once we are allowing org colors to be changed and saved, change this out
private stringToColor(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
let color = "#";
for (let i = 0; i < 3; i++) {
const value = (hash >> (i * 8)) & 0xff;
color += ("00" + value.toString(16)).substr(-2);
}
return color;
}
// There are a few ways to calculate text color for contrast, this one seems to fit accessibility guidelines best.
// https://stackoverflow.com/a/3943023/6869691
private pickTextColorBasedOnBgColor() {
const color = this.color.charAt(0) === "#" ? this.color.substring(1, 7) : this.color;
const r = parseInt(color.substring(0, 2), 16); // hexToR
const g = parseInt(color.substring(2, 4), 16); // hexToG
const b = parseInt(color.substring(4, 6), 16); // hexToB
return r * 0.299 + g * 0.587 + b * 0.114 > 186 ? "black !important" : "white !important";
}
emitOnOrganizationClicked() {
this.onOrganizationClicked.emit();
}
}

View File

@@ -0,0 +1,16 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { OrganizationVaultComponent } from "./organization-vault.component";
const routes: Routes = [
{
path: "",
component: OrganizationVaultComponent,
data: { titleId: "vaults" },
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class OrganizationVaultRoutingModule {}

View File

@@ -1,22 +1,26 @@
<div class="container page-content">
<div class="row">
<div class="col-3">
<app-org-vault-groupings
[showFolders]="false"
[showFavorites]="false"
[showTrash]="true"
(onAllClicked)="clearGroupingFilters()"
(onCipherTypeClicked)="filterCipherType($event)"
(onCollectionClicked)="filterCollection($event.id)"
(onSearchTextChanged)="filterSearchText($event)"
(onTrashClicked)="filterDeleted()"
>
</app-org-vault-groupings>
<div class="groupings">
<div class="content">
<div class="inner-content">
<app-vault-filter
#vaultFilter
[showFolders]="false"
[showFavorites]="false"
[activeFilter]="activeFilter"
[showOrgFilter]="false"
(onFilterChange)="applyVaultFilter($event)"
(onSearchTextChanged)="filterSearchText($event)"
></app-vault-filter>
</div>
</div>
</div>
</div>
<div class="col-9">
<div class="page-header d-flex">
<h1>
{{ "vault" | i18n }}
{{ "vaultItems" | i18n }}
<small #actionSpinner [appApiAction]="ciphersComponent.actionPromise">
<ng-container *ngIf="actionSpinner.loading">
<i

View File

@@ -10,33 +10,36 @@ import {
import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
import { VaultFilter } from "jslib-angular/modules/vault-filter/models/vault-filter.model";
import { ModalService } from "jslib-angular/services/modal.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { CipherType } from "jslib-common/enums/cipherType";
import { Organization } from "jslib-common/models/domain/organization";
import { CipherView } from "jslib-common/models/view/cipherView";
import { EntityEventsComponent } from "../manage/entity-events.component";
import { AddEditComponent } from "./add-edit.component";
import { AttachmentsComponent } from "./attachments.component";
import { CiphersComponent } from "./ciphers.component";
import { CollectionsComponent } from "./collections.component";
import { GroupingsComponent } from "./groupings.component";
import { EntityEventsComponent } from "../../../../organizations/manage/entity-events.component";
import { AddEditComponent } from "../../../../organizations/vault/add-edit.component";
import { AttachmentsComponent } from "../../../../organizations/vault/attachments.component";
import { CiphersComponent } from "../../../../organizations/vault/ciphers.component";
import { CollectionsComponent } from "../../../../organizations/vault/collections.component";
import { VaultFilterComponent } from "../../../vault-filter/vault-filter.component";
import { VaultService } from "../../vault.service";
const BroadcasterSubscriptionId = "OrgVaultComponent";
@Component({
selector: "app-org-vault",
templateUrl: "vault.component.html",
templateUrl: "organization-vault.component.html",
})
export class VaultComponent implements OnInit, OnDestroy {
@ViewChild(GroupingsComponent, { static: true }) groupingsComponent: GroupingsComponent;
export class OrganizationVaultComponent implements OnInit, OnDestroy {
@ViewChild("vaultFilter", { static: true }) vaultFilterComponent: VaultFilterComponent;
@ViewChild(CiphersComponent, { static: true }) ciphersComponent: CiphersComponent;
@ViewChild("attachments", { read: ViewContainerRef, static: true })
attachmentsModalRef: ViewContainerRef;
@@ -52,6 +55,7 @@ export class VaultComponent implements OnInit, OnDestroy {
type: CipherType = null;
deleted = false;
trashCleanupWarning: string = null;
activeFilter: VaultFilter = new VaultFilter();
constructor(
private route: ActivatedRoute,
@@ -64,7 +68,10 @@ export class VaultComponent implements OnInit, OnDestroy {
private messagingService: MessagingService,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
private platformUtilsService: PlatformUtilsService
private platformUtilsService: PlatformUtilsService,
private vaultService: VaultService,
private cipherService: CipherService,
private passwordRepromptService: PasswordRepromptService
) {}
ngOnInit() {
@@ -73,13 +80,13 @@ export class VaultComponent implements OnInit, OnDestroy {
? "trashCleanupWarningSelfHosted"
: "trashCleanupWarning"
);
this.route.parent.params.pipe(first()).subscribe(async (params) => {
this.route.parent.params.subscribe(async (params: any) => {
this.organization = await this.organizationService.get(params.organizationId);
this.groupingsComponent.organization = this.organization;
this.vaultFilterComponent.organization = this.organization;
this.ciphersComponent.organization = this.organization;
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
this.ciphersComponent.searchText = this.groupingsComponent.searchText = qParams.search;
this.ciphersComponent.searchText = this.vaultFilterComponent.searchText = qParams.search;
if (!this.organization.canViewAllCollections) {
await this.syncService.fullSync(false);
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
@@ -88,7 +95,11 @@ export class VaultComponent implements OnInit, OnDestroy {
case "syncCompleted":
if (message.successfully) {
await Promise.all([
this.groupingsComponent.load(),
this.vaultFilterComponent.reloadCollectionsAndFolders(
new VaultFilter({
selectedOrganizationId: this.organization.id,
} as Partial<VaultFilter>)
),
this.ciphersComponent.refresh(),
]);
this.changeDetectorRef.detectChanges();
@@ -98,27 +109,10 @@ export class VaultComponent implements OnInit, OnDestroy {
});
});
}
await this.groupingsComponent.load();
if (qParams == null) {
this.groupingsComponent.selectedAll = true;
await this.ciphersComponent.reload();
} else {
if (qParams.deleted) {
this.groupingsComponent.selectedTrash = true;
await this.filterDeleted(true);
} else if (qParams.type) {
const t = parseInt(qParams.type, null);
this.groupingsComponent.selectedType = t;
await this.filterCipherType(t, true);
} else if (qParams.collectionId) {
this.groupingsComponent.selectedCollectionId = qParams.collectionId;
await this.filterCollection(qParams.collectionId, true);
} else {
this.groupingsComponent.selectedAll = true;
await this.ciphersComponent.reload();
}
}
await this.vaultFilterComponent.reloadCollectionsAndFolders(
new VaultFilter({ selectedOrganizationId: this.organization.id } as Partial<VaultFilter>)
);
await this.ciphersComponent.reload();
if (qParams.viewEvents != null) {
const cipher = this.ciphersComponent.ciphers.filter((c) => c.id === qParams.viewEvents);
@@ -126,6 +120,28 @@ export class VaultComponent implements OnInit, OnDestroy {
this.viewEvents(cipher[0]);
}
}
this.route.queryParams.subscribe(async (params) => {
if (params.cipherId) {
if (
// Handle users with implicit collection access since they use the admin endpoint
this.organization.canEditAnyCollection ||
(await this.cipherService.get(params.cipherId)) != null
) {
this.editCipherId(params.cipherId);
} else {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("unknownCipher")
);
this.router.navigate([], {
queryParams: { cipherId: null },
queryParamsHandling: "merge",
});
}
}
});
});
});
}
@@ -134,63 +150,47 @@ export class VaultComponent implements OnInit, OnDestroy {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async clearGroupingFilters() {
this.ciphersComponent.showAddNew = true;
this.ciphersComponent.deleted = false;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchVault");
await this.ciphersComponent.applyFilter();
this.clearFilters();
async applyVaultFilter(vaultFilter: VaultFilter) {
this.ciphersComponent.showAddNew = vaultFilter.status !== "trash";
this.activeFilter = vaultFilter;
await this.ciphersComponent.reload(this.buildFilter(), vaultFilter.status === "trash");
this.vaultFilterComponent.searchPlaceholder =
this.vaultService.calculateSearchBarLocalizationString(this.activeFilter);
this.go();
}
async filterCipherType(type: CipherType, load = false) {
this.ciphersComponent.showAddNew = true;
this.ciphersComponent.deleted = false;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchType");
const filter = (c: CipherView) => c.type === type;
if (load) {
await this.ciphersComponent.reload(filter);
} else {
await this.ciphersComponent.applyFilter(filter);
}
this.clearFilters();
this.type = type;
this.go();
}
async filterCollection(collectionId: string, load = false) {
this.ciphersComponent.showAddNew = true;
this.ciphersComponent.deleted = false;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchCollection");
const filter = (c: CipherView) => {
if (collectionId === "unassigned") {
return c.collectionIds == null || c.collectionIds.length === 0;
} else {
return c.collectionIds != null && c.collectionIds.indexOf(collectionId) > -1;
private buildFilter(): (cipher: CipherView) => boolean {
return (cipher) => {
let cipherPassesFilter = true;
if (this.activeFilter.status === "favorites" && cipherPassesFilter) {
cipherPassesFilter = cipher.favorite;
}
if (this.activeFilter.status === "trash" && cipherPassesFilter) {
cipherPassesFilter = cipher.isDeleted;
}
if (this.activeFilter.cipherType != null && cipherPassesFilter) {
cipherPassesFilter = cipher.type === this.activeFilter.cipherType;
}
if (
this.activeFilter.selectedFolder != null &&
this.activeFilter.selectedFolderId != "none" &&
cipherPassesFilter
) {
cipherPassesFilter = cipher.folderId === this.activeFilter.selectedFolderId;
}
if (this.activeFilter.selectedCollectionId != null && cipherPassesFilter) {
cipherPassesFilter =
cipher.collectionIds != null &&
cipher.collectionIds.indexOf(this.activeFilter.selectedCollectionId) > -1;
}
if (this.activeFilter.selectedOrganizationId != null && cipherPassesFilter) {
cipherPassesFilter = cipher.organizationId === this.activeFilter.selectedOrganizationId;
}
if (this.activeFilter.myVaultOnly && cipherPassesFilter) {
cipherPassesFilter = cipher.organizationId === null;
}
return cipherPassesFilter;
};
if (load) {
await this.ciphersComponent.reload(filter);
} else {
await this.ciphersComponent.applyFilter(filter);
}
this.clearFilters();
this.collectionId = collectionId;
this.go();
}
async filterDeleted(load = false) {
this.ciphersComponent.showAddNew = false;
this.ciphersComponent.deleted = true;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchTrash");
if (load) {
await this.ciphersComponent.reload(null, true);
} else {
await this.ciphersComponent.applyFilter(null);
}
this.clearFilters();
this.deleted = true;
this.go();
}
filterSearchText(searchText: string) {
@@ -232,7 +232,9 @@ export class VaultComponent implements OnInit, OnDestroy {
(comp) => {
if (this.organization.canEditAnyCollection) {
comp.collectionIds = cipher.collectionIds;
comp.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
comp.collections = this.vaultFilterComponent.collections.fullList.filter(
(c) => !c.readOnly
);
}
comp.organization = this.organization;
comp.cipherId = cipher.id;
@@ -249,7 +251,9 @@ export class VaultComponent implements OnInit, OnDestroy {
component.organizationId = this.organization.id;
component.type = this.type;
if (this.organization.canEditAnyCollection) {
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
component.collections = this.vaultFilterComponent.collections.fullList.filter(
(c) => !c.readOnly
);
}
if (this.collectionId != null) {
component.collectionIds = [this.collectionId];
@@ -257,12 +261,24 @@ export class VaultComponent implements OnInit, OnDestroy {
}
async editCipher(cipher: CipherView) {
return this.editCipherId(cipher?.id);
}
async editCipherId(cipherId: string) {
const cipher = await this.cipherService.get(cipherId);
if (cipher != null && cipher.reprompt != 0) {
if (!(await this.passwordRepromptService.showPasswordPrompt())) {
this.go({ cipherId: null });
return;
}
}
const [modal, childComponent] = await this.modalService.openViewRef(
AddEditComponent,
this.cipherAddEditModalRef,
(comp) => {
comp.organization = this.organization;
comp.cipherId = cipher == null ? null : cipher.id;
comp.cipherId = cipherId;
comp.onSavedCipher.subscribe(async () => {
modal.close();
await this.ciphersComponent.refresh();
@@ -278,6 +294,10 @@ export class VaultComponent implements OnInit, OnDestroy {
}
);
modal.onClosedPromise().then(() => {
this.go({ cipherId: null });
});
return childComponent;
}
@@ -286,7 +306,9 @@ export class VaultComponent implements OnInit, OnDestroy {
component.cloneMode = true;
component.organizationId = this.organization.id;
if (this.organization.canEditAnyCollection) {
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
component.collections = this.vaultFilterComponent.collections.fullList.filter(
(c) => !c.readOnly
);
}
// Regardless of Admin state, the collection Ids need to passed manually as they are not assigned value
// in the add-edit componenet
@@ -321,6 +343,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.router.navigate([], {
relativeTo: this.route,
queryParams: queryParams,
queryParamsHandling: "merge",
replaceUrl: true,
});
}

View File

@@ -0,0 +1,13 @@
import { NgModule } from "@angular/core";
import { VaultModule } from "../../vault.module";
import { OrganizationVaultRoutingModule } from "./organization-vault-routing.module";
import { OrganizationVaultComponent } from "./organization-vault.component";
@NgModule({
imports: [VaultModule, OrganizationVaultRoutingModule],
declarations: [OrganizationVaultComponent],
exports: [OrganizationVaultComponent],
})
export class OrganizationVaultModule {}

View File

@@ -0,0 +1,19 @@
import { NgModule } from "@angular/core";
import { LooseComponentsModule } from "../loose-components.module";
import { SharedModule } from "../shared.module";
import { VaultFilterModule } from "../vault-filter/vault-filter.module";
import { VaultService } from "./vault.service";
@NgModule({
imports: [SharedModule, VaultFilterModule, LooseComponentsModule],
exports: [SharedModule, VaultFilterModule, LooseComponentsModule],
providers: [
{
provide: VaultService,
useClass: VaultService,
},
],
})
export class VaultModule {}

View File

@@ -0,0 +1,29 @@
import { VaultFilter } from "jslib-angular/modules/vault-filter/models/vault-filter.model";
export class VaultService {
calculateSearchBarLocalizationString(vaultFilter: VaultFilter): string {
if (vaultFilter.status === "favorites") {
return "searchFavorites";
}
if (vaultFilter.status === "trash") {
return "searchTrash";
}
if (vaultFilter.cipherType != null) {
return "searchType";
}
if (vaultFilter.selectedFolderId != null && vaultFilter.selectedFolderId != "none") {
return "searchFolder";
}
if (vaultFilter.selectedCollectionId != null) {
return "searchCollection";
}
if (vaultFilter.selectedOrganizationId != null) {
return "searchOrganization";
}
if (vaultFilter.myVaultOnly) {
return "searchMyVault";
}
return "searchVault";
}
}

View File

@@ -0,0 +1,58 @@
import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { Permissions } from "jslib-common/enums/permissions";
@Injectable()
export class PermissionsGuard implements CanActivate {
constructor(
private router: Router,
private organizationService: OrganizationService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private syncService: SyncService
) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
// TODO: We need to fix this issue once and for all.
if ((await this.syncService.getLastSync()) == null) {
await this.syncService.fullSync(false);
}
const org = await this.organizationService.get(route.params.organizationId);
if (org == null) {
return this.router.createUrlTree(["/"]);
}
if (!org.isOwner && !org.enabled) {
this.platformUtilsService.showToast(
"error",
null,
this.i18nService.t("organizationIsDisabled")
);
return this.router.createUrlTree(["/"]);
}
const permissions = route.data == null ? [] : (route.data.permissions as Permissions[]);
if (permissions != null && !org.hasAnyPermission(permissions)) {
// Handle linkable ciphers for organizations the user only has view access to
// https://bitwarden.atlassian.net/browse/EC-203
if (state.root.queryParamMap.has("cipherId")) {
return this.router.createUrlTree(["/vault"], {
queryParams: {
cipherId: state.root.queryParamMap.get("cipherId"),
},
});
}
this.platformUtilsService.showToast("error", null, this.i18nService.t("accessDenied"));
return this.router.createUrlTree(["/"]);
}
return true;
}
}

View File

@@ -2,32 +2,11 @@
<div class="org-nav" *ngIf="organization">
<div class="container d-flex">
<div class="d-flex flex-column">
<div class="my-auto d-flex align-items-center pl-1">
<app-avatar [data]="organization.name" size="45" [circle]="true"></app-avatar>
<div class="org-name ml-3">
<span>{{ organization.name }}</span>
<small class="text-muted">{{ "organization" | i18n }}</small>
</div>
<div
class="ml-3 card border-danger text-danger bg-transparent"
*ngIf="!organization.enabled"
>
<div class="card-body py-2">
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
{{ "organizationIsDisabled" | i18n }}
</div>
</div>
<div
class="ml-3 card border-info text-info bg-transparent"
*ngIf="organization.isProviderUser"
>
<div class="card-body py-2">
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
{{ "accessingUsingProvider" | i18n: organization.providerName }}
</div>
</div>
</div>
<ul class="nav nav-tabs" *ngIf="showMenuBar">
<app-organization-switcher
class="my-auto pl-1"
[activeOrganization]="organization"
></app-organization-switcher>
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link" routerLink="vault" routerLinkActive="active">
<i class="bwi bwi-lock" aria-hidden="true"></i>
@@ -46,7 +25,7 @@
{{ "tools" | i18n }}
</a>
</li>
<li class="nav-item" *ngIf="organization.isOwner">
<li class="nav-item" *ngIf="showSettingsTab">
<a class="nav-link" routerLink="settings" routerLinkActive="active">
<i class="bwi bwi-cogs" aria-hidden="true"></i>
{{ "settings" | i18n }}

View File

@@ -5,6 +5,8 @@ import { BroadcasterService } from "jslib-common/abstractions/broadcaster.servic
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { Organization } from "jslib-common/models/domain/organization";
import { NavigationPermissionsService } from "../services/navigation-permissions.service";
const BroadcasterSubscriptionId = "OrganizationLayoutComponent";
@Component({
@@ -25,7 +27,7 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
ngOnInit() {
document.body.classList.remove("layout_frontend");
this.route.params.subscribe(async (params) => {
this.route.params.subscribe(async (params: any) => {
this.organizationId = params.organizationId;
await this.load();
});
@@ -48,23 +50,16 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
this.organization = await this.organizationService.get(this.organizationId);
}
get showMenuBar() {
return this.showManageTab || this.showToolsTab || this.organization.isOwner;
}
get showManageTab(): boolean {
return (
this.organization.canManageUsers ||
this.organization.canViewAllCollections ||
this.organization.canViewAssignedCollections ||
this.organization.canManageGroups ||
this.organization.canManagePolicies ||
this.organization.canAccessEventLogs
);
return NavigationPermissionsService.canAccessManage(this.organization);
}
get showToolsTab(): boolean {
return this.organization.canAccessImportExport || this.organization.canAccessReports;
return NavigationPermissionsService.canAccessTools(this.organization);
}
get showSettingsTab(): boolean {
return NavigationPermissionsService.canAccessSettings(this.organization);
}
get toolsRoute(): string {

View File

@@ -0,0 +1,222 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuard } from "jslib-angular/guards/auth.guard";
import { Permissions } from "jslib-common/enums/permissions";
import { PermissionsGuard } from "./guards/permissions.guard";
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
import { CollectionsComponent } from "./manage/collections.component";
import { EventsComponent } from "./manage/events.component";
import { GroupsComponent } from "./manage/groups.component";
import { ManageComponent } from "./manage/manage.component";
import { PeopleComponent } from "./manage/people.component";
import { PoliciesComponent } from "./manage/policies.component";
import { NavigationPermissionsService } from "./services/navigation-permissions.service";
import { AccountComponent } from "./settings/account.component";
import { OrganizationBillingComponent } from "./settings/organization-billing.component";
import { OrganizationSubscriptionComponent } from "./settings/organization-subscription.component";
import { SettingsComponent } from "./settings/settings.component";
import { TwoFactorSetupComponent } from "./settings/two-factor-setup.component";
import { ExportComponent } from "./tools/export.component";
import { ExposedPasswordsReportComponent } from "./tools/exposed-passwords-report.component";
import { ImportComponent } from "./tools/import.component";
import { InactiveTwoFactorReportComponent } from "./tools/inactive-two-factor-report.component";
import { ReusedPasswordsReportComponent } from "./tools/reused-passwords-report.component";
import { ToolsComponent } from "./tools/tools.component";
import { UnsecuredWebsitesReportComponent } from "./tools/unsecured-websites-report.component";
import { WeakPasswordsReportComponent } from "./tools/weak-passwords-report.component";
const routes: Routes = [
{
path: ":organizationId",
component: OrganizationLayoutComponent,
canActivate: [AuthGuard, PermissionsGuard],
data: {
permissions: NavigationPermissionsService.getPermissions("admin"),
},
children: [
{ path: "", pathMatch: "full", redirectTo: "vault" },
{
path: "vault",
loadChildren: async () =>
(await import("../modules/vault/modules/organization-vault/organization-vault.module"))
.OrganizationVaultModule,
},
{
path: "tools",
component: ToolsComponent,
canActivate: [PermissionsGuard],
data: { permissions: NavigationPermissionsService.getPermissions("tools") },
children: [
{
path: "",
pathMatch: "full",
redirectTo: "import",
},
{
path: "import",
component: ImportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "importData",
permissions: [Permissions.AccessImportExport],
},
},
{
path: "export",
component: ExportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "exportVault",
permissions: [Permissions.AccessImportExport],
},
},
{
path: "exposed-passwords-report",
component: ExposedPasswordsReportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "exposedPasswordsReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "inactive-two-factor-report",
component: InactiveTwoFactorReportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "inactive2faReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "reused-passwords-report",
component: ReusedPasswordsReportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "reusedPasswordsReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "unsecured-websites-report",
component: UnsecuredWebsitesReportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "unsecuredWebsitesReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "weak-passwords-report",
component: WeakPasswordsReportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "weakPasswordsReport",
permissions: [Permissions.AccessReports],
},
},
],
},
{
path: "manage",
component: ManageComponent,
canActivate: [PermissionsGuard],
data: {
permissions: NavigationPermissionsService.getPermissions("manage"),
},
children: [
{
path: "",
pathMatch: "full",
redirectTo: "people",
},
{
path: "collections",
component: CollectionsComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "collections",
permissions: [
Permissions.CreateNewCollections,
Permissions.EditAnyCollection,
Permissions.DeleteAnyCollection,
Permissions.EditAssignedCollections,
Permissions.DeleteAssignedCollections,
],
},
},
{
path: "events",
component: EventsComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "eventLogs",
permissions: [Permissions.AccessEventLogs],
},
},
{
path: "groups",
component: GroupsComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "groups",
permissions: [Permissions.ManageGroups],
},
},
{
path: "people",
component: PeopleComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "people",
permissions: [Permissions.ManageUsers, Permissions.ManageUsersPassword],
},
},
{
path: "policies",
component: PoliciesComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "policies",
permissions: [Permissions.ManagePolicies],
},
},
],
},
{
path: "settings",
component: SettingsComponent,
canActivate: [PermissionsGuard],
data: { permissions: NavigationPermissionsService.getPermissions("settings") },
children: [
{ path: "", pathMatch: "full", redirectTo: "account" },
{ path: "account", component: AccountComponent, data: { titleId: "myOrganization" } },
{
path: "two-factor",
component: TwoFactorSetupComponent,
data: { titleId: "twoStepLogin" },
},
{
path: "billing",
component: OrganizationBillingComponent,
canActivate: [PermissionsGuard],
data: { titleId: "billing", permissions: [Permissions.ManageBilling] },
},
{
path: "subscription",
component: OrganizationSubscriptionComponent,
data: { titleId: "subscription" },
},
],
},
],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class OrganizationsRoutingModule {}

View File

@@ -0,0 +1,48 @@
import { Permissions } from "jslib-common/enums/permissions";
import { Organization } from "jslib-common/models/domain/organization";
const permissions = {
manage: [
Permissions.CreateNewCollections,
Permissions.EditAnyCollection,
Permissions.DeleteAnyCollection,
Permissions.EditAssignedCollections,
Permissions.DeleteAssignedCollections,
Permissions.AccessEventLogs,
Permissions.ManageGroups,
Permissions.ManageUsers,
Permissions.ManagePolicies,
],
tools: [Permissions.AccessImportExport, Permissions.AccessReports],
settings: [Permissions.ManageOrganization],
};
export class NavigationPermissionsService {
static getPermissions(route: keyof typeof permissions | "admin") {
if (route === "admin") {
return Object.values(permissions).reduce((previous, current) => previous.concat(current), []);
}
return permissions[route];
}
static canAccessAdmin(organization: Organization): boolean {
return (
this.canAccessTools(organization) ||
this.canAccessSettings(organization) ||
this.canAccessManage(organization)
);
}
static canAccessTools(organization: Organization): boolean {
return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("tools"));
}
static canAccessSettings(organization: Organization): boolean {
return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("settings"));
}
static canAccessManage(organization: Organization): boolean {
return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("manage"));
}
}

View File

@@ -37,7 +37,7 @@
type="text"
name="BillingEmail"
[(ngModel)]="org.billingEmail"
[disabled]="selfHosted"
[disabled]="selfHosted || !canManageBilling"
/>
</div>
<div class="form-group">
@@ -48,7 +48,7 @@
type="text"
name="BusinessName"
[(ngModel)]="org.businessName"
[disabled]="selfHosted"
[disabled]="selfHosted || !canManageBilling"
/>
</div>
<div class="form-group">

View File

@@ -6,6 +6,7 @@ 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 { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { OrganizationKeysRequest } from "jslib-common/models/request/organizationKeysRequest";
@@ -34,6 +35,7 @@ export class AccountComponent {
@ViewChild(TaxInfoComponent) taxInfo: TaxInfoComponent;
selfHosted = false;
canManageBilling = true;
loading = true;
canUseApi = false;
org: OrganizationResponse;
@@ -51,13 +53,18 @@ export class AccountComponent {
private platformUtilsService: PlatformUtilsService,
private cryptoService: CryptoService,
private logService: LogService,
private router: Router
private router: Router,
private organizationService: OrganizationService
) {}
async ngOnInit() {
this.selfHosted = this.platformUtilsService.isSelfHost();
this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
this.canManageBilling = (
await this.organizationService.get(this.organizationId)
).canManageBilling;
try {
this.org = await this.apiService.getOrganization(this.organizationId);
this.canUseApi = this.org.useApi;

View File

@@ -0,0 +1,117 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="billingSyncApiKeyTitle">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form
class="modal-content"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
>
<div class="modal-header">
<h2 class="modal-title" id="billingSyncApiKeyTitle">
{{ (hasBillingToken ? "viewBillingSyncToken" : "generateBillingSyncToken") | i18n }}
</h2>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<app-user-verification
[(ngModel)]="masterPassword"
ngDefaultControl
name="secret"
*ngIf="!clientSecret"
>
</app-user-verification>
<ng-container *ngIf="clientSecret && showRotateScreen">
<p>{{ "rotateBillingSyncTokenTitle" | i18n }}</p>
<app-callout type="warning">
{{ "rotateBillingSyncTokenWarning" | i18n }}
</app-callout>
</ng-container>
<div *ngIf="clientSecret && !showRotateScreen">
<p>{{ "copyPasteBillingSync" | i18n }}</p>
<label for="clientSecret">Billing Sync Key</label>
<div class="input-group">
<input
id="clientSecret"
class="form-control text-monospace"
type="text"
[(ngModel)]="clientSecret"
name="clientSecret"
disabled
/>
<div class="input-group-append">
<button
type="button"
class="btn btn-outline-secondary"
(click)="copy()"
[appA11yTitle]="'copy' | i18n"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
<div class="small text-muted mt-2" *ngIf="showLastSyncText">
<b class="font-weight-semibold">{{ "lastSync" | i18n }}:</b>
{{ lastSyncDate | date: "medium" }}
</div>
<div class="small text-danger mt-2" *ngIf="showAwaitingSyncText">
<i class="bwi bwi-error"></i>
{{
(daysBetween === 1 ? "awaitingSyncSingular" : "awaitingSyncPlural")
| i18n: daysBetween
}}
</div>
</div>
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary btn-submit"
[disabled]="form.loading"
*ngIf="!clientSecret || showRotateScreen"
>
<i
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
*ngIf="form.loading"
></i>
<span>
{{ submitButtonText }}
</span>
</button>
<button
type="button"
class="btn btn-outline-secondary"
data-dismiss="modal"
*ngIf="!showRotateScreen"
>
{{ "close" | i18n }}
</button>
<button
type="button"
class="btn btn-outline-secondary"
*ngIf="showRotateScreen"
(click)="cancelRotate()"
>
{{ "cancel" | i18n }}
</button>
<button
type="button"
class="btn btn-outline-secondary"
*ngIf="clientSecret && !showRotateScreen"
(click)="rotateToken()"
>
{{ "rotateToken" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@@ -0,0 +1,108 @@
import { Component } from "@angular/core";
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { UserVerificationService } from "jslib-common/abstractions/userVerification.service";
import { OrganizationApiKeyType } from "jslib-common/enums/organizationApiKeyType";
import { OrganizationApiKeyRequest } from "jslib-common/models/request/organizationApiKeyRequest";
import { ApiKeyResponse } from "jslib-common/models/response/apiKeyResponse";
import { Verification } from "jslib-common/types/verification";
@Component({
selector: "app-billing-sync-api-key",
templateUrl: "billing-sync-api-key.component.html",
})
export class BillingSyncApiKeyComponent {
organizationId: string;
hasBillingToken: boolean;
showRotateScreen: boolean;
masterPassword: Verification;
formPromise: Promise<ApiKeyResponse>;
clientSecret?: string;
keyRevisionDate?: Date;
lastSyncDate?: Date = null;
constructor(
private userVerificationService: UserVerificationService,
private apiService: ApiService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService
) {}
copy() {
this.platformUtilsService.copyToClipboard(this.clientSecret);
}
async submit() {
if (this.showRotateScreen) {
this.formPromise = this.userVerificationService
.buildRequest(this.masterPassword, OrganizationApiKeyRequest)
.then((request) => {
request.type = OrganizationApiKeyType.BillingSync;
return this.apiService.postOrganizationRotateApiKey(this.organizationId, request);
});
const response = await this.formPromise;
await this.load(response);
this.showRotateScreen = false;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("billingSyncApiKeyRotated")
);
} else {
this.formPromise = this.userVerificationService
.buildRequest(this.masterPassword, OrganizationApiKeyRequest)
.then((request) => {
request.type = OrganizationApiKeyType.BillingSync;
return this.apiService.postOrganizationApiKey(this.organizationId, request);
});
const response = await this.formPromise;
await this.load(response);
}
}
async load(response: ApiKeyResponse) {
this.clientSecret = response.apiKey;
this.keyRevisionDate = response.revisionDate;
this.hasBillingToken = true;
const syncStatus = await this.apiService.getSponsorshipSyncStatus(this.organizationId);
this.lastSyncDate = syncStatus.lastSyncDate;
}
cancelRotate() {
this.showRotateScreen = false;
}
rotateToken() {
this.showRotateScreen = true;
}
private dayDiff(date1: Date, date2: Date): number {
const diffTime = Math.abs(date2.getTime() - date1.getTime());
return Math.round(diffTime / (1000 * 60 * 60 * 24));
}
get submitButtonText(): string {
if (this.showRotateScreen) {
return this.i18nService.t("rotateToken");
}
return this.i18nService.t(this.hasBillingToken ? "continue" : "generateToken");
}
get showLastSyncText(): boolean {
// If the keyRevisionDate is later than the lastSyncDate we need to show
// a warning that they need to put the billing sync key in their self hosted install
return this.lastSyncDate && this.lastSyncDate > this.keyRevisionDate;
}
get showAwaitingSyncText(): boolean {
return this.lastSyncDate && this.lastSyncDate <= this.keyRevisionDate;
}
get daysBetween(): number {
return this.dayDiff(this.keyRevisionDate, new Date());
}
}

View File

@@ -44,8 +44,8 @@
</ng-container>
</ng-template>
</p>
<app-verify-master-password [(ngModel)]="masterPassword" ngDefaultControl name="secret">
</app-verify-master-password>
<app-user-verification [(ngModel)]="masterPassword" ngDefaultControl name="secret">
</app-user-verification>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-danger btn-submit" [disabled]="form.loading">

View File

@@ -0,0 +1,20 @@
<svg width="216" height="231" viewBox="0 0 216 231" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M133.356 85.6618C133.136 85.43 132.871 85.2457 132.577 85.1198C132.283 84.9939 131.968 84.93 131.648 84.9318H87.8482C87.5289 84.93 87.2113 84.9939 86.9175 85.1198C86.6237 85.2457 86.359 85.43 86.14 85.6618C85.9083 85.8808 85.7239 86.1473 85.598 86.4411C85.4721 86.7349 85.4082 87.0506 85.41 87.37V116.57C85.4192 118.793 85.8499 120.994 86.6802 123.056C87.4705 125.091 88.5326 127.011 89.8375 128.761C91.1789 130.515 92.6808 132.137 94.3233 133.612C95.8472 135.01 97.4532 136.318 99.1304 137.528C100.59 138.565 102.123 139.547 103.729 140.474C105.335 141.401 106.469 142.027 107.131 142.354C107.799 142.682 108.339 142.941 108.741 143.113C109.055 143.264 109.4 143.339 109.748 143.332C110.091 143.337 110.431 143.257 110.737 143.102C111.146 142.923 111.679 142.671 112.354 142.343C113.03 142.014 114.179 141.386 115.756 140.463C117.333 139.539 118.884 138.554 120.355 137.517C122.034 136.306 123.642 134.999 125.169 133.601C126.814 132.128 128.316 130.504 129.655 128.75C130.958 126.998 132.021 125.08 132.813 123.045C133.645 120.983 134.075 118.782 134.083 116.559V87.3591C134.085 87.0415 134.021 86.7276 133.895 86.4356C133.769 86.1436 133.586 85.8808 133.356 85.6618ZM127.71 116.836C127.71 127.421 109.748 136.514 109.748 136.514V91.1879H127.71V116.836Z" fill="var(--color-secondary-700)"/>
<path d="M24.6216 122.3C24.7144 123.4 25.6819 124.217 26.7825 124.125C27.8832 124.032 28.7002 123.064 28.6074 121.964L24.6216 122.3ZM151.501 45.7445C152.57 45.4679 153.213 44.3768 152.936 43.3074L148.429 25.8809C148.152 24.8115 147.061 24.1688 145.992 24.4454C144.922 24.722 144.28 25.8131 144.556 26.8825L148.563 42.3728L133.073 46.3793C132.003 46.6559 131.361 47.747 131.637 48.8164C131.914 49.8858 133.005 50.5285 134.074 50.2519L151.501 45.7445ZM28.6074 121.964C26.6788 99.0874 34.4658 75.5543 51.9661 58.054L49.1377 55.2256C30.7695 73.5938 22.5982 98.2999 24.6216 122.3L28.6074 121.964ZM51.9661 58.054C78.5404 31.4797 119.036 27.3026 149.985 45.5315L152.015 42.0849C119.534 22.9534 77.0327 27.3306 49.1377 55.2256L51.9661 58.054Z" fill="var(--color-secondary-700)"/>
<path d="M67.4747 185.062C66.4089 185.352 65.7801 186.451 66.0701 187.517L70.797 204.885C71.0871 205.951 72.1862 206.58 73.252 206.29C74.3179 205.999 74.9467 204.9 74.6567 203.834L70.455 188.396L85.8934 184.194C86.9592 183.904 87.5881 182.805 87.298 181.739C87.008 180.674 85.9088 180.045 84.843 180.335L67.4747 185.062ZM192.478 100.283C192.286 99.1952 191.248 98.4697 190.16 98.6625C189.072 98.8552 188.347 99.8931 188.54 100.981L192.478 100.283ZM166.138 172.225C139.813 198.55 99.8271 202.897 68.9932 185.255L67.0068 188.727C99.3669 207.242 141.333 202.687 168.966 175.054L166.138 172.225ZM188.54 100.981C192.985 126.07 185.516 152.847 166.138 172.225L168.966 175.054C189.307 154.713 197.142 126.603 192.478 100.283L188.54 100.981Z" fill="var(--color-secondary-700)"/>
<path d="M37.726 108.132C39.283 92.1931 46.1655 76.6765 58.3734 64.4686C75.2893 47.5527 98.5583 40.8618 120.5 44.396" stroke="var(--color-secondary-700)" stroke-width="2" stroke-linecap="round"/>
<path d="M180.595 119.132C179.653 136.119 172.693 152.834 159.717 165.811C144.44 181.088 123.982 188.025 104 186.623" stroke="var(--color-secondary-700)" stroke-width="2" stroke-linecap="round"/>
<rect x="153.5" y="56.1317" width="49" height="34" rx="2.5" stroke="var(--color-secondary-700)" stroke-width="3"/>
<path d="M153.5 63.6317V63.6317C153.5 66.9454 156.186 69.6317 159.5 69.6317H172.509M202.5 63.6317V63.6317C202.5 66.9454 199.814 69.6317 196.5 69.6317H183.491" stroke="var(--color-secondary-700)" stroke-width="3"/>
<mask id="path-8-inside-1_1000_26057" fill="white">
<rect x="172" y="65.6317" width="12" height="9" rx="1.25"/>
</mask>
<rect x="172" y="65.6317" width="12" height="9" rx="1.25" stroke="var(--color-secondary-700)" stroke-width="6" mask="url(#path-8-inside-1_1000_26057)"/>
<path d="M187 54.6317C187 54.6317 187 53.6317 187 52.6317C187 51.6317 186.053 50.6317 185.105 50.6317C184.158 50.6317 171.842 50.6317 170.895 50.6317C169.947 50.6317 169 51.6317 169 52.6317C169 53.6317 169 54.6317 169 54.6317" stroke="var(--color-secondary-700)" stroke-width="3"/>
<circle cx="48" cy="141" r="10.5" fill="var(--color-background)" stroke="var(--color-secondary-700)" stroke-width="3"/>
<path d="M64.9935 168.5H64.9126H64.8318H64.7512H64.6708H64.5905H64.5104H64.4304H64.3506H64.2709H64.1914H64.1121H64.0329H63.9538H63.8749H63.7962H63.7176H63.6392H63.5609H63.4828H63.4048H63.3269H63.2492H63.1717H63.0943H63.017H62.9399H62.8629H62.7861H62.7094H62.6329H62.5565H62.4802H62.4041H62.3281H62.2523H62.1766H62.101H62.0256H61.9503H61.8751H61.8001H61.7252H61.6504H61.5758H61.5013H61.4269H61.3527H61.2786H61.2046H61.1308H61.0571H60.9835H60.91H60.8367H60.7635H60.6904H60.6175H60.5446H60.4719H60.3993H60.3269H60.2545H60.1823H60.1102H60.0382H59.9664H59.8946H59.823H59.7515H59.6801H59.6088H59.5376H59.4666H59.3956H59.3248H59.2541H59.1835H59.113H59.0426H58.9724H58.9022H58.8322H58.7622H58.6924H58.6226H58.553H58.4835H58.4141H58.3448H58.2756H58.2065H58.1375H58.0686H57.9998H57.9311H57.8625H57.794H57.7256H57.6572H57.589H57.5209H57.4529H57.385H57.3172H57.2494H57.1818H57.1142H57.0468H56.9794H56.9121H56.8449H56.7779H56.7108H56.6439H56.5771H56.5103H56.4437H56.3771H56.3106H56.2442H56.1779H56.1117H56.0455H55.9794H55.9134H55.8475H55.7817H55.7159H55.6502H55.5846H55.5191H55.4537H55.3883H55.323H55.2578H55.1926H55.1275H55.0625H54.9976H54.9328H54.868H54.8032H54.7386H54.674H54.6095H54.5451H54.4807H54.4164H54.3521H54.2879H54.2238H54.1598H54.0958H54.0318H53.968H53.9042H53.8404H53.7767H53.7131H53.6495H53.586H53.5226H53.4592H53.3958H53.3325H53.2693H53.2061H53.143H53.0799H53.0169H52.9539H52.891H52.8281H52.7653H52.7025H52.6398H52.5771H52.5145H52.4519H52.3894H52.3269H52.2645H52.202H52.1397H52.0774H52.0151H51.9528H51.8907H51.8285H51.7664H51.7043H51.6423H51.5803H51.5183H51.4564H51.3945H51.3326H51.2708H51.209H51.1472H51.0855H51.0238H50.9621H50.9005H50.8389H50.7773H50.7157H50.6542H50.5927H50.5312H50.4698H50.4084H50.347H50.2856H50.2243H50.1629H50.1016H50.0404H49.9791H49.9178H49.8566H49.7954H49.7342H49.6731H49.6119H49.5508H49.4896H49.4285H49.3674H49.3064H49.2453H49.1842H49.1232H49.0622H49.0011H48.9401H48.8791H48.8181H48.7571H48.6962H48.6352H48.5742H48.5133H48.4523H48.3913H48.3304H48.2694H48.2085H48.1475H48.0866H48.0257H47.9647H47.9038H47.8428H47.7819H47.7209H47.6599H47.599H47.538H47.477H47.416H47.3551H47.2941H47.2331H47.172H47.111H47.05H46.9889H46.9279H46.8668H46.8057H46.7446H46.6835H46.6224H46.5612H46.5001H46.4389H46.3777H46.3165H46.2553H46.194H46.1328H46.0715H46.0102H45.9489H45.8875H45.8261H45.7647H45.7033H45.6418H45.5804H45.5189H45.4573H45.3958H45.3342H45.2726H45.2109H45.1493H45.0876H45.0258H44.9641H44.9023H44.8404H44.7786H44.7166H44.6547H44.5927H44.5307H44.4687H44.4066H44.3445H44.2823H44.2201H44.1578H44.0956H44.0332H43.9709H43.9084H43.846H43.7835H43.7209H43.6583H43.5957H43.533H43.4703H43.4075H43.3447H43.2818H43.2189H43.1559H43.0929H43.0298H42.9666H42.9034H42.8402H42.7769H42.7135H42.6501H42.5867H42.5231H42.4596H42.3959H42.3322H42.2685H42.2046H42.1408H42.0768H42.0128H41.9487H41.8846H41.8204H41.7562H41.6918H41.6274H41.563H41.4985H41.4339H41.3692H41.3045H41.2397H41.1748H41.1098H41.0448H40.9797H40.9146H40.8493H40.784H40.7186H40.6532H40.5876H40.522H40.4563H40.3905H40.3247H40.2588H40.1928H40.1267H40.0605H39.9942H39.9279H39.8615H39.795H39.7284H39.6617H39.5949H39.5281H39.4611H39.3941H39.327H39.2598H39.1925H39.1251H39.0576H38.9901H38.9224H38.8547H38.7868H38.7189H38.6508H38.5827H38.5145H38.4461H38.3777H38.3092H38.2406H38.1719H38.103H38.0341H37.9651H37.896H37.8267H37.7574H37.688H37.6184H37.5488H37.479H37.4092H37.3392H37.2691H37.199H37.1287H37.0583H36.9878H36.9171H36.8464H36.7755H36.7046H36.6335H36.5623H36.491H36.4196H36.3481H36.2764H36.2046H36.1328H36.0607H35.9886H35.9164H35.844H35.7715H35.6989H35.6262H35.5533H35.4803H35.4072H35.334H35.2606H35.1872H35.1135H35.0398H34.9659H34.8919H34.8178H34.7436H34.6692H34.5947H34.52H34.4452H34.3703H34.2952H34.2201H34.1447H34.0693H33.9937H33.9179H33.8421H33.7661H33.6899H33.6136H33.5372H33.4606H33.3839H33.3071H33.2301H33.1529H33.0756H32.9982H32.9206H32.8429H32.765H32.687H32.6088H32.5305H32.452H32.3734H32.2946H32.2157H32.1367H32.0574H31.978H31.8985H31.8188H31.739H31.659H31.5788H31.4985H31.418H31.3374H31.2566H31.1757H31.0946H31.0133H30.9318H30.8503H30.7685H30.6866H30.6045H30.5222H30.4398H30.3572H30.2745H30.1915C30.0489 168.5 29.9693 168.466 29.9164 168.433C29.8553 168.394 29.7779 168.322 29.702 168.192C29.5361 167.906 29.4608 167.48 29.5197 167.111C30.9235 158.322 38.6474 151.574 47.9925 151.574C57.3375 151.574 65.0615 158.322 66.4652 167.111C66.5751 167.799 66.4037 168.054 66.3124 168.149C66.1888 168.278 65.8364 168.5 64.9935 168.5Z" fill="var(--color-background)" stroke="var(--color-secondary-700)" stroke-width="3"/>
<circle cx="20" cy="141" r="10.5" fill="var(--color-background)" stroke="var(--color-secondary-700)" stroke-width="3"/>
<path d="M36.9935 168.5H36.9126H36.8318H36.7512H36.6708H36.5905H36.5104H36.4304H36.3506H36.2709H36.1914H36.1121H36.0329H35.9538H35.8749H35.7962H35.7176H35.6392H35.5609H35.4828H35.4048H35.3269H35.2492H35.1717H35.0943H35.017H34.9399H34.8629H34.7861H34.7094H34.6329H34.5565H34.4802H34.4041H34.3281H34.2523H34.1766H34.101H34.0256H33.9503H33.8751H33.8001H33.7252H33.6504H33.5758H33.5013H33.4269H33.3527H33.2786H33.2046H33.1308H33.0571H32.9835H32.91H32.8367H32.7635H32.6904H32.6175H32.5446H32.4719H32.3993H32.3269H32.2545H32.1823H32.1102H32.0382H31.9664H31.8946H31.823H31.7515H31.6801H31.6088H31.5376H31.4666H31.3956H31.3248H31.2541H31.1835H31.113H31.0426H30.9724H30.9022H30.8322H30.7622H30.6924H30.6226H30.553H30.4835H30.4141H30.3448H30.2756H30.2065H30.1375H30.0686H29.9998H29.9311H29.8625H29.794H29.7256H29.6572H29.589H29.5209H29.4529H29.385H29.3172H29.2494H29.1818H29.1142H29.0468H28.9794H28.9121H28.8449H28.7779H28.7108H28.6439H28.5771H28.5103H28.4437H28.3771H28.3106H28.2442H28.1779H28.1117H28.0455H27.9794H27.9134H27.8475H27.7817H27.7159H27.6502H27.5846H27.5191H27.4537H27.3883H27.323H27.2578H27.1926H27.1275H27.0625H26.9976H26.9328H26.868H26.8032H26.7386H26.674H26.6095H26.5451H26.4807H26.4164H26.3521H26.2879H26.2238H26.1598H26.0958H26.0318H25.968H25.9042H25.8404H25.7767H25.7131H25.6495H25.586H25.5226H25.4592H25.3958H25.3325H25.2693H25.2061H25.143H25.0799H25.0169H24.9539H24.891H24.8281H24.7653H24.7025H24.6398H24.5771H24.5145H24.4519H24.3894H24.3269H24.2645H24.202H24.1397H24.0774H24.0151H23.9528H23.8907H23.8285H23.7664H23.7043H23.6423H23.5803H23.5183H23.4564H23.3945H23.3326H23.2708H23.209H23.1472H23.0855H23.0238H22.9621H22.9005H22.8389H22.7773H22.7157H22.6542H22.5927H22.5312H22.4698H22.4084H22.347H22.2856H22.2243H22.1629H22.1016H22.0404H21.9791H21.9178H21.8566H21.7954H21.7342H21.6731H21.6119H21.5508H21.4896H21.4285H21.3674H21.3064H21.2453H21.1842H21.1232H21.0622H21.0011H20.9401H20.8791H20.8181H20.7571H20.6962H20.6352H20.5742H20.5133H20.4523H20.3913H20.3304H20.2694H20.2085H20.1475H20.0866H20.0257H19.9647H19.9038H19.8428H19.7819H19.7209H19.6599H19.599H19.538H19.477H19.416H19.3551H19.2941H19.2331H19.172H19.111H19.05H18.9889H18.9279H18.8668H18.8057H18.7446H18.6835H18.6224H18.5612H18.5001H18.4389H18.3777H18.3165H18.2553H18.194H18.1328H18.0715H18.0102H17.9489H17.8875H17.8261H17.7647H17.7033H17.6418H17.5804H17.5189H17.4573H17.3958H17.3342H17.2726H17.2109H17.1493H17.0876H17.0258H16.9641H16.9023H16.8404H16.7786H16.7166H16.6547H16.5927H16.5307H16.4687H16.4066H16.3445H16.2823H16.2201H16.1578H16.0956H16.0332H15.9709H15.9084H15.846H15.7835H15.7209H15.6583H15.5957H15.533H15.4703H15.4075H15.3447H15.2818H15.2189H15.1559H15.0929H15.0298H14.9666H14.9034H14.8402H14.7769H14.7135H14.6501H14.5867H14.5231H14.4596H14.3959H14.3322H14.2685H14.2046H14.1408H14.0768H14.0128H13.9487H13.8846H13.8204H13.7562H13.6918H13.6274H13.563H13.4985H13.4339H13.3692H13.3045H13.2397H13.1748H13.1098H13.0448H12.9797H12.9146H12.8493H12.784H12.7186H12.6532H12.5876H12.522H12.4563H12.3905H12.3247H12.2588H12.1928H12.1267H12.0605H11.9942H11.9279H11.8615H11.795H11.7284H11.6617H11.5949H11.5281H11.4611H11.3941H11.327H11.2598H11.1925H11.1251H11.0576H10.9901H10.9224H10.8547H10.7868H10.7189H10.6508H10.5827H10.5145H10.4461H10.3777H10.3092H10.2406H10.1719H10.103H10.0341H9.9651H9.89597H9.82674H9.75741H9.68798H9.61843H9.54879H9.47904H9.40918H9.33921H9.26914H9.19896H9.12867H9.05826H8.98775H8.91713H8.8464H8.77555H8.70459H8.63351H8.56232H8.49102H8.4196H8.34807H8.27641H8.20464H8.13276H8.06075H7.98862H7.91638H7.84401H7.77152H7.69891H7.62617H7.55332H7.48034H7.40723H7.334H7.26064H7.18716H7.11355H7.03981H6.96594H6.89195H6.81782H6.74356H6.66918H6.59466H6.52H6.44522H6.3703H6.29525H6.22006H6.14474H6.06928H5.99368H5.91795H5.84208H5.76607H5.68992H5.61363H5.5372H5.46062H5.38391H5.30705H5.23005H5.15291H5.07562H4.99819H4.92061H4.84288H4.76501H4.68699H4.60882H4.5305H4.45203H4.37342H4.29465H4.21573H4.13665H4.05743H3.97805H3.89851H3.81882H3.73898H3.65898H3.57882H3.49851H3.41804H3.33741H3.25662H3.17566H3.09455H3.01328H2.93185H2.85025H2.76849H2.68657H2.60448H2.52223H2.43981H2.35722H2.27447H2.19155C2.04893 168.5 1.96927 168.466 1.91645 168.433C1.85532 168.394 1.77792 168.322 1.702 168.192C1.53613 167.906 1.46078 167.48 1.51975 167.111C2.92347 158.322 10.6474 151.574 19.9925 151.574C29.3375 151.574 37.0615 158.322 38.4652 167.111C38.5751 167.799 38.4037 168.054 38.3124 168.149C38.1888 168.278 37.8364 168.5 36.9935 168.5Z" fill="var(--color-background)" stroke="var(--color-secondary-700)" stroke-width="3"/>
<circle cx="34" cy="154" r="10.5" fill="var(--color-background)" stroke="var(--color-secondary-700)" stroke-width="3"/>
<path d="M50.9935 181.5H50.9126H50.8318H50.7512H50.6708H50.5905H50.5104H50.4304H50.3506H50.2709H50.1914H50.1121H50.0329H49.9538H49.8749H49.7962H49.7176H49.6392H49.5609H49.4828H49.4048H49.3269H49.2492H49.1717H49.0943H49.017H48.9399H48.8629H48.7861H48.7094H48.6329H48.5565H48.4802H48.4041H48.3281H48.2523H48.1766H48.101H48.0256H47.9503H47.8751H47.8001H47.7252H47.6504H47.5758H47.5013H47.4269H47.3527H47.2786H47.2046H47.1308H47.0571H46.9835H46.91H46.8367H46.7635H46.6904H46.6175H46.5446H46.4719H46.3993H46.3269H46.2545H46.1823H46.1102H46.0382H45.9664H45.8946H45.823H45.7515H45.6801H45.6088H45.5376H45.4666H45.3956H45.3248H45.2541H45.1835H45.113H45.0426H44.9724H44.9022H44.8322H44.7622H44.6924H44.6226H44.553H44.4835H44.4141H44.3448H44.2756H44.2065H44.1375H44.0686H43.9998H43.9311H43.8625H43.794H43.7256H43.6572H43.589H43.5209H43.4529H43.385H43.3172H43.2494H43.1818H43.1142H43.0468H42.9794H42.9121H42.8449H42.7779H42.7108H42.6439H42.5771H42.5103H42.4437H42.3771H42.3106H42.2442H42.1779H42.1117H42.0455H41.9794H41.9134H41.8475H41.7817H41.7159H41.6502H41.5846H41.5191H41.4537H41.3883H41.323H41.2578H41.1926H41.1275H41.0625H40.9976H40.9328H40.868H40.8032H40.7386H40.674H40.6095H40.5451H40.4807H40.4164H40.3521H40.2879H40.2238H40.1598H40.0958H40.0318H39.968H39.9042H39.8404H39.7767H39.7131H39.6495H39.586H39.5226H39.4592H39.3958H39.3325H39.2693H39.2061H39.143H39.0799H39.0169H38.9539H38.891H38.8281H38.7653H38.7025H38.6398H38.5771H38.5145H38.4519H38.3894H38.3269H38.2645H38.202H38.1397H38.0774H38.0151H37.9528H37.8907H37.8285H37.7664H37.7043H37.6423H37.5803H37.5183H37.4564H37.3945H37.3326H37.2708H37.209H37.1472H37.0855H37.0238H36.9621H36.9005H36.8389H36.7773H36.7157H36.6542H36.5927H36.5312H36.4698H36.4084H36.347H36.2856H36.2243H36.1629H36.1016H36.0404H35.9791H35.9178H35.8566H35.7954H35.7342H35.6731H35.6119H35.5508H35.4896H35.4285H35.3674H35.3064H35.2453H35.1842H35.1232H35.0622H35.0011H34.9401H34.8791H34.8181H34.7571H34.6962H34.6352H34.5742H34.5133H34.4523H34.3913H34.3304H34.2694H34.2085H34.1475H34.0866H34.0257H33.9647H33.9038H33.8428H33.7819H33.7209H33.6599H33.599H33.538H33.477H33.416H33.3551H33.2941H33.2331H33.172H33.111H33.05H32.9889H32.9279H32.8668H32.8057H32.7446H32.6835H32.6224H32.5612H32.5001H32.4389H32.3777H32.3165H32.2553H32.194H32.1328H32.0715H32.0102H31.9489H31.8875H31.8261H31.7647H31.7033H31.6418H31.5804H31.5189H31.4573H31.3958H31.3342H31.2726H31.2109H31.1493H31.0876H31.0258H30.9641H30.9023H30.8404H30.7786H30.7166H30.6547H30.5927H30.5307H30.4687H30.4066H30.3445H30.2823H30.2201H30.1578H30.0956H30.0332H29.9709H29.9084H29.846H29.7835H29.7209H29.6583H29.5957H29.533H29.4703H29.4075H29.3447H29.2818H29.2189H29.1559H29.0929H29.0298H28.9666H28.9034H28.8402H28.7769H28.7135H28.6501H28.5867H28.5231H28.4596H28.3959H28.3322H28.2685H28.2046H28.1408H28.0768H28.0128H27.9487H27.8846H27.8204H27.7562H27.6918H27.6274H27.563H27.4985H27.4339H27.3692H27.3045H27.2397H27.1748H27.1098H27.0448H26.9797H26.9146H26.8493H26.784H26.7186H26.6532H26.5876H26.522H26.4563H26.3905H26.3247H26.2588H26.1928H26.1267H26.0605H25.9942H25.9279H25.8615H25.795H25.7284H25.6617H25.5949H25.5281H25.4611H25.3941H25.327H25.2598H25.1925H25.1251H25.0576H24.9901H24.9224H24.8547H24.7868H24.7189H24.6508H24.5827H24.5145H24.4461H24.3777H24.3092H24.2406H24.1719H24.103H24.0341H23.9651H23.896H23.8267H23.7574H23.688H23.6184H23.5488H23.479H23.4092H23.3392H23.2691H23.199H23.1287H23.0583H22.9878H22.9171H22.8464H22.7755H22.7046H22.6335H22.5623H22.491H22.4196H22.3481H22.2764H22.2046H22.1328H22.0607H21.9886H21.9164H21.844H21.7715H21.6989H21.6262H21.5533H21.4803H21.4072H21.334H21.2606H21.1872H21.1135H21.0398H20.9659H20.8919H20.8178H20.7436H20.6692H20.5947H20.52H20.4452H20.3703H20.2952H20.2201H20.1447H20.0693H19.9937H19.9179H19.8421H19.7661H19.6899H19.6136H19.5372H19.4606H19.3839H19.3071H19.2301H19.1529H19.0756H18.9982H18.9206H18.8429H18.765H18.687H18.6088H18.5305H18.452H18.3734H18.2946H18.2157H18.1367H18.0574H17.978H17.8985H17.8188H17.739H17.659H17.5788H17.4985H17.418H17.3374H17.2566H17.1757H17.0946H17.0133H16.9318H16.8503H16.7685H16.6866H16.6045H16.5222H16.4398H16.3572H16.2745H16.1915C16.045 181.5 15.9628 181.465 15.9092 181.432C15.8479 181.394 15.772 181.324 15.6978 181.198C15.5354 180.922 15.4617 180.509 15.5193 180.153C16.9196 171.496 24.6325 164.823 33.9925 164.823C43.3524 164.823 51.0654 171.496 52.4657 180.153C52.574 180.823 52.4052 181.064 52.319 181.152C52.1962 181.279 51.8413 181.5 50.9935 181.5Z" fill="var(--color-background)" stroke="var(--color-secondary-700)" stroke-width="3"/>
</svg>

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,8 @@
import { Component } from "@angular/core";
// Component is used so that the SVG can embed CSS color variables
@Component({
selector: "app-image-org-subscription-hidden",
templateUrl: "image-subscription-hidden.component.svg",
})
export class ImageSubscriptionHiddenComponent {}

View File

@@ -5,23 +5,38 @@ 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 { UserBillingComponent } from "../../settings/user-billing.component";
import { PaymentMethodType } from "jslib-common/enums/paymentMethodType";
import { TransactionType } from "jslib-common/enums/transactionType";
import { VerifyBankRequest } from "jslib-common/models/request/verifyBankRequest";
import { BillingResponse } from "jslib-common/models/response/billingResponse";
@Component({
selector: "app-org-billing",
templateUrl: "../../settings/user-billing.component.html",
templateUrl: "./organization-billing.component.html",
})
export class OrganizationBillingComponent extends UserBillingComponent implements OnInit {
export class OrganizationBillingComponent implements OnInit {
loading = false;
firstLoaded = false;
showAdjustPayment = false;
showAddCredit = false;
billing: BillingResponse;
paymentMethodType = PaymentMethodType;
transactionType = TransactionType;
organizationId: string;
verifyAmount1: number;
verifyAmount2: number;
verifyBankPromise: Promise<any>;
// TODO - Make sure to properly split out the billing/invoice and payment method/account during org admin refresh
constructor(
apiService: ApiService,
i18nService: I18nService,
private apiService: ApiService,
private i18nService: I18nService,
private route: ActivatedRoute,
platformUtilsService: PlatformUtilsService,
logService: LogService
) {
super(apiService, i18nService, platformUtilsService, logService);
}
private platformUtilsService: PlatformUtilsService,
private logService: LogService
) {}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
@@ -30,4 +45,110 @@ export class OrganizationBillingComponent extends UserBillingComponent implement
this.firstLoaded = true;
});
}
async load() {
if (this.loading) {
return;
}
this.loading = true;
if (this.organizationId != null) {
this.billing = await this.apiService.getOrganizationBilling(this.organizationId);
}
this.loading = false;
}
async verifyBank() {
if (this.loading) {
return;
}
try {
const request = new VerifyBankRequest();
request.amount1 = this.verifyAmount1;
request.amount2 = this.verifyAmount2;
this.verifyBankPromise = this.apiService.postOrganizationVerifyBank(
this.organizationId,
request
);
await this.verifyBankPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("verifiedBankAccount")
);
this.load();
} catch (e) {
this.logService.error(e);
}
}
addCredit() {
if (this.paymentSourceInApp) {
this.platformUtilsService.showDialog(
this.i18nService.t("cannotPerformInAppPurchase"),
this.i18nService.t("addCredit"),
null,
null,
"warning"
);
return;
}
this.showAddCredit = true;
}
closeAddCredit(load: boolean) {
this.showAddCredit = false;
if (load) {
this.load();
}
}
changePayment() {
if (this.paymentSourceInApp) {
this.platformUtilsService.showDialog(
this.i18nService.t("cannotPerformInAppPurchase"),
this.i18nService.t("changePaymentMethod"),
null,
null,
"warning"
);
return;
}
this.showAdjustPayment = true;
}
closePayment(load: boolean) {
this.showAdjustPayment = false;
if (load) {
this.load();
}
}
get isCreditBalance() {
return this.billing == null || this.billing.balance <= 0;
}
get creditOrBalance() {
return Math.abs(this.billing != null ? this.billing.balance : 0);
}
get paymentSource() {
return this.billing != null ? this.billing.paymentSource : null;
}
get paymentSourceInApp() {
return (
this.paymentSource != null &&
(this.paymentSource.type === PaymentMethodType.AppleInApp ||
this.paymentSource.type === PaymentMethodType.GoogleInApp)
);
}
get invoices() {
return this.billing != null ? this.billing.invoices : null;
}
get transactions() {
return this.billing != null ? this.billing.transactions : null;
}
}

View File

@@ -16,6 +16,14 @@
<span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container>
<ng-container *ngIf="firstLoaded && !userOrg.canManageBilling">
<div class="tw-flex tw-flex-col tw-items-center tw-text-info">
<app-image-org-subscription-hidden></app-image-org-subscription-hidden>
<p class="tw-font-bold">{{ "billingManagedByProvider" | i18n: this.userOrg.providerName }}</p>
<p>{{ "billingContactProviderForAssistance" | i18n }}</p>
</div>
</ng-container>
<ng-container *ngIf="sub">
<app-callout
type="warning"
@@ -180,10 +188,10 @@
></app-adjust-storage>
</div>
</ng-container>
<h2 class="spaced-header">{{ "additionalOptions" | i18n }}</h2>
<!--Switch to i18n-->
<h2 class="spaced-header">{{ "selfHostingTitle" | i18n }}</h2>
<p class="mb-4">
{{ "additionalOptionsDesc" | i18n }}
{{ "selfHostingEnterpriseOrganizationSectionCopy" | i18n }}
</p>
<div class="d-flex">
<button
@@ -195,6 +203,27 @@
>
{{ "downloadLicense" | i18n }}
</button>
<button
type="button"
class="btn btn-outline-secondary ml-1"
(click)="manageBillingSync()"
*ngIf="canManageBillingSync"
>
{{ (hasBillingSyncToken ? "manageBillingSync" : "setUpBillingSync") | i18n }}
</button>
</div>
<div class="mt-3" *ngIf="showDownloadLicense">
<app-download-license
[organizationId]="organizationId"
(onDownloaded)="closeDownloadLicense()"
(onCanceled)="closeDownloadLicense()"
></app-download-license>
</div>
<h2 class="spaced-header">{{ "additionalOptions" | i18n }}</h2>
<p class="mb-4">
{{ "additionalOptionsDesc" | i18n }}
</p>
<div class="d-flex">
<button
#cancelBtn
type="button"
@@ -208,13 +237,6 @@
<span>{{ "cancelSubscription" | i18n }}</span>
</button>
</div>
<div class="mt-3" *ngIf="showDownloadLicense">
<app-download-license
[organizationId]="organizationId"
(onDownloaded)="closeDownloadLicense()"
(onCanceled)="closeDownloadLicense()"
></app-download-license>
</div>
</ng-container>
<ng-container *ngIf="selfHosted">
<dl>
@@ -261,5 +283,31 @@
></app-update-license>
</div>
</div>
<div *ngIf="showBillingSyncKey">
<h2 class="mt-5">
{{ "billingSync" | i18n }}
</h2>
<p>
{{ "billingSyncDesc" | i18n }}
</p>
<button
type="button"
class="btn btn-outline-secondary"
(click)="manageBillingSyncSelfHosted()"
>
{{ "manageBillingSync" | i18n }}
</button>
<small class="form-text text-muted" *ngIf="billingSyncSetUp">
{{ "lastSync" | i18n }}:
<span *ngIf="userOrg.familySponsorshipLastSyncDate != null">
{{ userOrg.familySponsorshipLastSyncDate | date: "medium" }}
</span>
<span *ngIf="userOrg.familySponsorshipLastSyncDate == null">
{{ "never" | i18n | lowercase }}
</span>
</small>
</div>
</ng-container>
</ng-container>
<ng-template #setupBillingSyncTemplate></ng-template>
<ng-template #rotateBillingSyncKeyTemplate></ng-template>

View File

@@ -1,21 +1,34 @@
import { Component, OnInit } from "@angular/core";
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { ModalRef } from "jslib-angular/components/modal/modal.ref";
import { ModalService } from "jslib-angular/services/modal.service";
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { OrganizationApiKeyType } from "jslib-common/enums/organizationApiKeyType";
import { OrganizationConnectionType } from "jslib-common/enums/organizationConnectionType";
import { PlanType } from "jslib-common/enums/planType";
import { BillingSyncConfigApi } from "jslib-common/models/api/billingSyncConfigApi";
import { Organization } from "jslib-common/models/domain/organization";
import { OrganizationConnectionResponse } from "jslib-common/models/response/organizationConnectionResponse";
import { OrganizationSubscriptionResponse } from "jslib-common/models/response/organizationSubscriptionResponse";
import { BillingSyncKeyComponent } from "src/app/settings/billing-sync-key.component";
import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component";
@Component({
selector: "app-org-subscription",
templateUrl: "organization-subscription.component.html",
})
export class OrganizationSubscriptionComponent implements OnInit {
@ViewChild("setupBillingSyncTemplate", { read: ViewContainerRef, static: true })
setupBillingSyncModalRef: ViewContainerRef;
loading = false;
firstLoaded = false;
organizationId: string;
@@ -25,17 +38,24 @@ export class OrganizationSubscriptionComponent implements OnInit {
adjustStorageAdd = true;
showAdjustStorage = false;
showUpdateLicense = false;
showBillingSyncKey = false;
showDownloadLicense = false;
showChangePlan = false;
sub: OrganizationSubscriptionResponse;
selfHosted = false;
hasBillingSyncToken: boolean;
userOrg: Organization;
existingBillingSyncConnection: OrganizationConnectionResponse<BillingSyncConfigApi>;
removeSponsorshipPromise: Promise<any>;
cancelPromise: Promise<any>;
reinstatePromise: Promise<any>;
@ViewChild("rotateBillingSyncKeyTemplate", { read: ViewContainerRef, static: true })
billingSyncKeyViewContainerRef: ViewContainerRef;
billingSyncKeyRef: [ModalRef, BillingSyncKeyComponent];
constructor(
private apiService: ApiService,
private platformUtilsService: PlatformUtilsService,
@@ -43,7 +63,8 @@ export class OrganizationSubscriptionComponent implements OnInit {
private messagingService: MessagingService,
private route: ActivatedRoute,
private organizationService: OrganizationService,
private logService: LogService
private logService: LogService,
private modalService: ModalService
) {
this.selfHosted = platformUtilsService.isSelfHost();
}
@@ -63,7 +84,28 @@ export class OrganizationSubscriptionComponent implements OnInit {
this.loading = true;
this.userOrg = await this.organizationService.get(this.organizationId);
this.sub = await this.apiService.getOrganizationSubscription(this.organizationId);
if (this.userOrg.canManageBilling) {
this.sub = await this.apiService.getOrganizationSubscription(this.organizationId);
}
const apiKeyResponse = await this.apiService.getOrganizationApiKeyInformation(
this.organizationId
);
this.hasBillingSyncToken = apiKeyResponse.data.some(
(i) => i.keyType === OrganizationApiKeyType.BillingSync
);
if (this.selfHosted) {
this.showBillingSyncKey = await this.apiService.getCloudCommunicationsEnabled();
}
if (this.showBillingSyncKey) {
this.existingBillingSyncConnection = await this.apiService.getOrganizationConnection(
this.organizationId,
OrganizationConnectionType.CloudBillingSync,
BillingSyncConfigApi
);
}
this.loading = false;
}
@@ -134,6 +176,20 @@ export class OrganizationSubscriptionComponent implements OnInit {
this.showDownloadLicense = !this.showDownloadLicense;
}
async manageBillingSync() {
const [ref] = await this.modalService.openViewRef(
BillingSyncApiKeyComponent,
this.setupBillingSyncModalRef,
(comp) => {
comp.organizationId = this.organizationId;
comp.hasBillingToken = this.hasBillingSyncToken;
}
);
ref.onClosed.subscribe(async () => {
await this.load();
});
}
closeDownloadLicense() {
this.showDownloadLicense = false;
}
@@ -196,6 +252,24 @@ export class OrganizationSubscriptionComponent implements OnInit {
}
}
async manageBillingSyncSelfHosted() {
this.billingSyncKeyRef = await this.modalService.openViewRef(
BillingSyncKeyComponent,
this.billingSyncKeyViewContainerRef,
(comp) => {
comp.entityId = this.organizationId;
comp.existingConnectionId = this.existingBillingSyncConnection?.id;
comp.billingSyncKey = this.existingBillingSyncConnection?.config?.billingSyncKey;
comp.setParentConnection = (
connection: OrganizationConnectionResponse<BillingSyncConfigApi>
) => {
this.existingBillingSyncConnection = connection;
this.billingSyncKeyRef[0].close();
};
}
);
}
get isExpired() {
return (
this.sub != null && this.sub.expiration != null && new Date(this.sub.expiration) < new Date()
@@ -262,6 +336,16 @@ export class OrganizationSubscriptionComponent implements OnInit {
);
}
get canManageBillingSync() {
return (
!this.selfHosted &&
(this.sub.planType === PlanType.EnterpriseAnnually ||
this.sub.planType === PlanType.EnterpriseMonthly ||
this.sub.planType === PlanType.EnterpriseAnnually2019 ||
this.sub.planType === PlanType.EnterpriseMonthly2019)
);
}
get subscriptionDesc() {
if (this.sub.planType === PlanType.Free) {
return this.i18nService.t("subscriptionFreePlan", this.sub.seats.toString());
@@ -289,4 +373,8 @@ export class OrganizationSubscriptionComponent implements OnInit {
get showChangePlanButton() {
return this.subscription == null && this.sub.planType === PlanType.Free && !this.showChangePlan;
}
get billingSyncSetUp() {
return this.existingBillingSyncConnection?.id != null;
}
}

View File

@@ -14,7 +14,7 @@
routerLink="billing"
class="list-group-item"
routerLinkActive="active"
*ngIf="!selfHosted"
*ngIf="showBilling"
>
{{ "billing" | i18n }}
</a>

View File

@@ -10,7 +10,7 @@ import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.se
})
export class SettingsComponent {
access2fa = false;
selfHosted: boolean;
showBilling: boolean;
constructor(
private route: ActivatedRoute,
@@ -20,8 +20,8 @@ export class SettingsComponent {
ngOnInit() {
this.route.parent.params.subscribe(async (params) => {
this.selfHosted = await this.platformUtilsService.isSelfHost();
const organization = await this.organizationService.get(params.organizationId);
this.showBilling = !this.platformUtilsService.isSelfHost() && organization.canManageBilling;
this.access2fa = organization.use2fa;
});
}

View File

@@ -0,0 +1,13 @@
<div class="mt-5 d-flex justify-content-center" *ngIf="loading">
<div>
<img class="mb-4 logo logo-themed" alt="Bitwarden" />
<p class="text-center">
<i
class="bwi bwi-spinner bwi-spin bwi-2x text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</p>
</div>
</div>

View File

@@ -0,0 +1,26 @@
import { Component } from "@angular/core";
import { BaseAcceptComponent } from "src/app/common/base.accept.component";
@Component({
selector: "app-accept-family-sponsorship",
templateUrl: "accept-family-sponsorship.component.html",
})
export class AcceptFamilySponsorshipComponent extends BaseAcceptComponent {
failedShortMessage = "inviteAcceptFailedShort";
failedMessage = "inviteAcceptFailed";
requiredParameters = ["email", "token"];
async authedHandler(qParams: any) {
this.router.navigate(["/setup/families-for-enterprise"], { queryParams: qParams });
}
async unauthedHandler(qParams: any) {
if (!qParams.register) {
this.router.navigate(["/login"], { queryParams: { email: qParams.email } });
} else {
this.router.navigate(["/register"], { queryParams: { email: qParams.email } });
}
}
}

View File

@@ -14,7 +14,7 @@ import { CipherView } from "jslib-common/models/view/cipherView";
import { ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent } from "../../reports/exposed-passwords-report.component";
@Component({
selector: "app-exposed-passwords-report",
selector: "app-org-exposed-passwords-report",
templateUrl: "../../reports/exposed-passwords-report.component.html",
})
export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportComponent {
@@ -41,12 +41,10 @@ export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportC
}
ngOnInit() {
const dynamicSuper = Object.getPrototypeOf(this.constructor.prototype);
this.route.parent.parent.params.subscribe(async (params) => {
this.organization = await this.organizationService.get(params.organizationId);
this.manageableCiphers = await this.cipherService.getAll();
// TODO: We should do something about this, calling super in an async function is bad
dynamicSuper.ngOnInit();
await this.checkAccess();
});
}

View File

@@ -5,10 +5,12 @@ import { CipherService } from "jslib-common/abstractions/cipher.service";
import { EventService } from "jslib-common/abstractions/event.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SearchService } from "jslib-common/abstractions/search.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { TokenService } from "jslib-common/abstractions/token.service";
import { TotpService } from "jslib-common/abstractions/totp.service";
import { Organization } from "jslib-common/models/domain/organization";
import { CipherView } from "jslib-common/models/view/cipherView";
@@ -32,12 +34,14 @@ export class CiphersComponent extends BaseCiphersComponent {
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
cipherService: CipherService,
private apiService: ApiService,
eventService: EventService,
totpService: TotpService,
passwordRepromptService: PasswordRepromptService,
logService: LogService,
stateService: StateService
stateService: StateService,
organizationService: OrganizationService,
tokenService: TokenService,
private apiService: ApiService
) {
super(
searchService,
@@ -48,11 +52,14 @@ export class CiphersComponent extends BaseCiphersComponent {
totpService,
stateService,
passwordRepromptService,
logService
logService,
organizationService,
tokenService
);
}
async load(filter: (cipher: CipherView) => boolean = null) {
async load(filter: (cipher: CipherView) => boolean = null, deleted = false) {
this.deleted = deleted || false;
if (this.organization.canEditAnyCollection) {
this.accessEvents = this.organization.useEvents;
this.allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organization.id);

View File

@@ -1,65 +0,0 @@
import { Component } from "@angular/core";
import { ApiService } from "jslib-common/abstractions/api.service";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { FolderService } from "jslib-common/abstractions/folder.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { CollectionData } from "jslib-common/models/data/collectionData";
import { Collection } from "jslib-common/models/domain/collection";
import { Organization } from "jslib-common/models/domain/organization";
import { CollectionDetailsResponse } from "jslib-common/models/response/collectionResponse";
import { CollectionView } from "jslib-common/models/view/collectionView";
import { GroupingsComponent as BaseGroupingsComponent } from "../../vault/groupings.component";
@Component({
selector: "app-org-vault-groupings",
templateUrl: "../../vault/groupings.component.html",
})
export class GroupingsComponent extends BaseGroupingsComponent {
organization: Organization;
constructor(
collectionService: CollectionService,
folderService: FolderService,
stateService: StateService,
private apiService: ApiService,
private i18nService: I18nService
) {
super(collectionService, folderService, stateService);
}
async loadCollections() {
if (!this.organization.canEditAnyCollection) {
await super.loadCollections(this.organization.id);
return;
}
const collections = await this.apiService.getCollections(this.organization.id);
if (collections != null && collections.data != null && collections.data.length) {
const collectionDomains = collections.data.map(
(r) => new Collection(new CollectionData(r as CollectionDetailsResponse))
);
this.collections = await this.collectionService.decryptMany(collectionDomains);
} else {
this.collections = [];
}
const unassignedCollection = new CollectionView();
unassignedCollection.name = this.i18nService.t("unassigned");
unassignedCollection.id = "unassigned";
unassignedCollection.organizationId = this.organization.id;
unassignedCollection.readOnly = true;
this.collections.push(unassignedCollection);
this.nestedCollections = await this.collectionService.getAllNested(this.collections);
}
async collapse(grouping: CollectionView) {
await super.collapse(grouping, "org_");
}
isCollapsed(grouping: CollectionView) {
return super.isCollapsed(grouping, "org_");
}
}

View File

@@ -1,10 +1,9 @@
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 { Permissions } from "jslib-common/enums/permissions";
import { AuthGuard } from "jslib-angular/guards/auth.guard";
import { LockGuard } from "jslib-angular/guards/lock.guard";
import { UnauthGuard } from "jslib-angular/guards/unauth.guard";
import { AcceptEmergencyComponent } from "./accounts/accept-emergency.component";
import { AcceptOrganizationComponent } from "./accounts/accept-organization.component";
@@ -22,70 +21,50 @@ import { UpdatePasswordComponent } from "./accounts/update-password.component";
import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component";
import { VerifyEmailTokenComponent } from "./accounts/verify-email-token.component";
import { VerifyRecoverDeleteComponent } from "./accounts/verify-recover-delete.component";
import { HomeGuard } from "./guards/home.guard";
import { FrontendLayoutComponent } from "./layouts/frontend-layout.component";
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
import { UserLayoutComponent } from "./layouts/user-layout.component";
import { CollectionsComponent as OrgManageCollectionsComponent } from "./organizations/manage/collections.component";
import { EventsComponent as OrgEventsComponent } from "./organizations/manage/events.component";
import { GroupsComponent as OrgGroupsComponent } from "./organizations/manage/groups.component";
import { ManageComponent as OrgManageComponent } from "./organizations/manage/manage.component";
import { PeopleComponent as OrgPeopleComponent } from "./organizations/manage/people.component";
import { PoliciesComponent as OrgPoliciesComponent } from "./organizations/manage/policies.component";
import { AccountComponent as OrgAccountComponent } from "./organizations/settings/account.component";
import { OrganizationBillingComponent } from "./organizations/settings/organization-billing.component";
import { OrganizationSubscriptionComponent } from "./organizations/settings/organization-subscription.component";
import { SettingsComponent as OrgSettingsComponent } from "./organizations/settings/settings.component";
import { TwoFactorSetupComponent as OrgTwoFactorSetupComponent } from "./organizations/settings/two-factor-setup.component";
import { AcceptFamilySponsorshipComponent } from "./organizations/sponsorships/accept-family-sponsorship.component";
import { FamiliesForEnterpriseSetupComponent } from "./organizations/sponsorships/families-for-enterprise-setup.component";
import { ExportComponent as OrgExportComponent } from "./organizations/tools/export.component";
import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "./organizations/tools/exposed-passwords-report.component";
import { ImportComponent as OrgImportComponent } from "./organizations/tools/import.component";
import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent } from "./organizations/tools/inactive-two-factor-report.component";
import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } from "./organizations/tools/reused-passwords-report.component";
import { ToolsComponent as OrgToolsComponent } from "./organizations/tools/tools.component";
import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "./organizations/tools/unsecured-websites-report.component";
import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "./organizations/tools/weak-passwords-report.component";
import { VaultComponent as OrgVaultComponent } from "./organizations/vault/vault.component";
import { AccessComponent } from "./send/access.component";
import { SendComponent } from "./send/send.component";
import { OrganizationGuardService } from "./services/organization-guard.service";
import { OrganizationTypeGuardService } from "./services/organization-type-guard.service";
import { AccountComponent } from "./settings/account.component";
import { CreateOrganizationComponent } from "./settings/create-organization.component";
import { DomainRulesComponent } from "./settings/domain-rules.component";
import { EmergencyAccessViewComponent } from "./settings/emergency-access-view.component";
import { EmergencyAccessComponent } from "./settings/emergency-access.component";
import { OptionsComponent } from "./settings/options.component";
import { OrganizationsComponent } from "./settings/organizations.component";
import { PremiumComponent } from "./settings/premium.component";
import { PreferencesComponent } from "./settings/preferences.component";
import { SettingsComponent } from "./settings/settings.component";
import { SponsoredFamiliesComponent } from "./settings/sponsored-families.component";
import { TwoFactorSetupComponent } from "./settings/two-factor-setup.component";
import { UserBillingComponent } from "./settings/user-billing.component";
import { UserSubscriptionComponent } from "./settings/user-subscription.component";
import { ExportComponent } from "./tools/export.component";
import { GeneratorComponent } from "./tools/generator.component";
import { ImportComponent } from "./tools/import.component";
import { ToolsComponent } from "./tools/tools.component";
import { VaultComponent } from "./vault/vault.component";
const routes: Routes = [
{
path: "",
component: FrontendLayoutComponent,
data: { doNotSaveUrl: true },
children: [
{ path: "", pathMatch: "full", component: LoginComponent, canActivate: [UnauthGuardService] },
{ path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuardService] },
{
path: "",
pathMatch: "full",
children: [], // Children lets us have an empty component.
canActivate: [HomeGuard], // Redirects either to vault, login or lock page.
},
{ path: "login", component: LoginComponent, canActivate: [UnauthGuard] },
{ path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuard] },
{
path: "register",
component: RegisterComponent,
canActivate: [UnauthGuardService],
canActivate: [UnauthGuard],
data: { titleId: "createAccount" },
},
{
path: "sso",
component: SsoComponent,
canActivate: [UnauthGuardService],
canActivate: [UnauthGuard],
data: { titleId: "enterpriseSingleSignOn" },
},
{
@@ -96,42 +75,47 @@ const routes: Routes = [
{
path: "hint",
component: HintComponent,
canActivate: [UnauthGuardService],
canActivate: [UnauthGuard],
data: { titleId: "passwordHint" },
},
{
path: "lock",
component: LockComponent,
canActivate: [LockGuardService],
canActivate: [LockGuard],
},
{ path: "verify-email", component: VerifyEmailTokenComponent },
{
path: "accept-organization",
component: AcceptOrganizationComponent,
data: { titleId: "joinOrganization" },
data: { titleId: "joinOrganization", doNotSaveUrl: false },
},
{
path: "accept-emergency",
component: AcceptEmergencyComponent,
data: { titleId: "acceptEmergency" },
data: { titleId: "acceptEmergency", doNotSaveUrl: false },
},
{
path: "accept-families-for-enterprise",
component: AcceptFamilySponsorshipComponent,
data: { titleId: "acceptFamilySponsorship", doNotSaveUrl: false },
},
{ path: "recover", pathMatch: "full", redirectTo: "recover-2fa" },
{
path: "recover-2fa",
component: RecoverTwoFactorComponent,
canActivate: [UnauthGuardService],
canActivate: [UnauthGuard],
data: { titleId: "recoverAccountTwoStep" },
},
{
path: "recover-delete",
component: RecoverDeleteComponent,
canActivate: [UnauthGuardService],
canActivate: [UnauthGuard],
data: { titleId: "deleteAccount" },
},
{
path: "verify-recover-delete",
component: VerifyRecoverDeleteComponent,
canActivate: [UnauthGuardService],
canActivate: [UnauthGuard],
data: { titleId: "deleteAccount" },
},
{
@@ -142,19 +126,19 @@ const routes: Routes = [
{
path: "update-temp-password",
component: UpdateTempPasswordComponent,
canActivate: [AuthGuardService],
canActivate: [AuthGuard],
data: { titleId: "updateTempPassword" },
},
{
path: "update-password",
component: UpdatePasswordComponent,
canActivate: [AuthGuardService],
canActivate: [AuthGuard],
data: { titleId: "updatePassword" },
},
{
path: "remove-password",
component: RemovePasswordComponent,
canActivate: [AuthGuardService],
canActivate: [AuthGuard],
data: { titleId: "removeMasterPassword" },
},
],
@@ -162,43 +146,45 @@ const routes: Routes = [
{
path: "",
component: UserLayoutComponent,
canActivate: [AuthGuardService],
canActivate: [AuthGuard],
children: [
{ path: "vault", component: VaultComponent, data: { titleId: "myVault" } },
{
path: "vault",
loadChildren: async () =>
(await import("./modules/vault/modules/individual-vault/individual-vault.module"))
.IndividualVaultModule,
},
{ path: "sends", component: SendComponent, data: { title: "Send" } },
{
path: "create-organization",
component: CreateOrganizationComponent,
data: { titleId: "newOrganization" },
},
{
path: "settings",
component: SettingsComponent,
children: [
{ path: "", pathMatch: "full", redirectTo: "account" },
{ path: "account", component: AccountComponent, data: { titleId: "myAccount" } },
{ path: "options", component: OptionsComponent, data: { titleId: "options" } },
{
path: "preferences",
component: PreferencesComponent,
data: { titleId: "preferences" },
},
{
path: "security",
loadChildren: async () =>
(await import("./settings/security-routing.module")).SecurityRoutingModule,
},
{
path: "domain-rules",
component: DomainRulesComponent,
data: { titleId: "domainRules" },
},
{
path: "two-factor",
component: TwoFactorSetupComponent,
data: { titleId: "twoStepLogin" },
},
{ path: "premium", component: PremiumComponent, data: { titleId: "goPremium" } },
{ path: "billing", component: UserBillingComponent, data: { titleId: "billing" } },
{
path: "subscription",
component: UserSubscriptionComponent,
data: { titleId: "premiumMembership" },
},
{
path: "organizations",
component: OrganizationsComponent,
data: { titleId: "organizations" },
},
{
path: "create-organization",
component: CreateOrganizationComponent,
data: { titleId: "newOrganization" },
loadChildren: async () =>
(await import("./settings/subscription-routing.module")).SubscriptionRoutingModule,
},
{
path: "emergency-access",
@@ -225,7 +211,7 @@ const routes: Routes = [
{
path: "tools",
component: ToolsComponent,
canActivate: [AuthGuardService],
canActivate: [AuthGuard],
children: [
{ path: "", pathMatch: "full", redirectTo: "generator" },
{ path: "import", component: ImportComponent, data: { titleId: "importData" } },
@@ -239,196 +225,18 @@ const routes: Routes = [
},
{
path: "reports",
loadChildren: async () => (await import("./reports/reports.module")).ReportsModule,
loadChildren: async () =>
(await import("./reports/reports-routing.module")).ReportsRoutingModule,
},
{ path: "setup/families-for-enterprise", component: FamiliesForEnterpriseSetupComponent },
],
},
{
path: "organizations/:organizationId",
component: OrganizationLayoutComponent,
canActivate: [AuthGuardService, OrganizationGuardService],
children: [
{ path: "", pathMatch: "full", redirectTo: "vault" },
{ path: "vault", component: OrgVaultComponent, data: { titleId: "vault" } },
{
path: "tools",
component: OrgToolsComponent,
canActivate: [OrganizationTypeGuardService],
data: { permissions: [Permissions.AccessImportExport, Permissions.AccessReports] },
children: [
{
path: "",
pathMatch: "full",
redirectTo: "import",
},
{
path: "import",
component: OrgImportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "importData",
permissions: [Permissions.AccessImportExport],
},
},
{
path: "export",
component: OrgExportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "exportVault",
permissions: [Permissions.AccessImportExport],
},
},
{
path: "exposed-passwords-report",
component: OrgExposedPasswordsReportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "exposedPasswordsReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "inactive-two-factor-report",
component: OrgInactiveTwoFactorReportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "inactive2faReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "reused-passwords-report",
component: OrgReusedPasswordsReportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "reusedPasswordsReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "unsecured-websites-report",
component: OrgUnsecuredWebsitesReportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "unsecuredWebsitesReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "weak-passwords-report",
component: OrgWeakPasswordsReportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "weakPasswordsReport",
permissions: [Permissions.AccessReports],
},
},
],
},
{
path: "manage",
component: OrgManageComponent,
canActivate: [OrganizationTypeGuardService],
data: {
permissions: [
Permissions.CreateNewCollections,
Permissions.EditAnyCollection,
Permissions.DeleteAnyCollection,
Permissions.EditAssignedCollections,
Permissions.DeleteAssignedCollections,
Permissions.AccessEventLogs,
Permissions.ManageGroups,
Permissions.ManageUsers,
Permissions.ManagePolicies,
],
},
children: [
{
path: "",
pathMatch: "full",
redirectTo: "people",
},
{
path: "collections",
component: OrgManageCollectionsComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "collections",
permissions: [
Permissions.CreateNewCollections,
Permissions.EditAnyCollection,
Permissions.DeleteAnyCollection,
Permissions.EditAssignedCollections,
Permissions.DeleteAssignedCollections,
],
},
},
{
path: "events",
component: OrgEventsComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "eventLogs",
permissions: [Permissions.AccessEventLogs],
},
},
{
path: "groups",
component: OrgGroupsComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "groups",
permissions: [Permissions.ManageGroups],
},
},
{
path: "people",
component: OrgPeopleComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "people",
permissions: [Permissions.ManageUsers, Permissions.ManageUsersPassword],
},
},
{
path: "policies",
component: OrgPoliciesComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "policies",
permissions: [Permissions.ManagePolicies],
},
},
],
},
{
path: "settings",
component: OrgSettingsComponent,
canActivate: [OrganizationTypeGuardService],
data: { permissions: [Permissions.ManageOrganization] },
children: [
{ path: "", pathMatch: "full", redirectTo: "account" },
{ path: "account", component: OrgAccountComponent, data: { titleId: "myOrganization" } },
{
path: "two-factor",
component: OrgTwoFactorSetupComponent,
data: { titleId: "twoStepLogin" },
},
{
path: "billing",
component: OrganizationBillingComponent,
data: { titleId: "billing" },
},
{
path: "subscription",
component: OrganizationSubscriptionComponent,
data: { titleId: "subscription" },
},
],
},
],
path: "organizations",
loadChildren: () =>
import("./organizations/organization-routing.module").then(
(m) => m.OrganizationsRoutingModule
),
},
];
@@ -437,7 +245,7 @@ const routes: Routes = [
RouterModule.forRoot(routes, {
useHash: true,
paramsInheritanceStrategy: "always",
/*enableTracing: true,*/
// enableTracing: true,
}),
],
exports: [RouterModule],

View File

@@ -1,446 +1,20 @@
import { DragDropModule } from "@angular/cdk/drag-drop";
import { DatePipe, registerLocaleData, CommonModule } from "@angular/common";
import localeAf from "@angular/common/locales/af";
import localeAz from "@angular/common/locales/az";
import localeBe from "@angular/common/locales/be";
import localeBg from "@angular/common/locales/bg";
import localeBn from "@angular/common/locales/bn";
import localeBs from "@angular/common/locales/bs";
import localeCa from "@angular/common/locales/ca";
import localeCs from "@angular/common/locales/cs";
import localeDa from "@angular/common/locales/da";
import localeDe from "@angular/common/locales/de";
import localeEl from "@angular/common/locales/el";
import localeEnGb from "@angular/common/locales/en-GB";
import localeEnIn from "@angular/common/locales/en-IN";
import localeEo from "@angular/common/locales/eo";
import localeEs from "@angular/common/locales/es";
import localeEt from "@angular/common/locales/et";
import localeFi from "@angular/common/locales/fi";
import localeFil from "@angular/common/locales/fil";
import localeFr from "@angular/common/locales/fr";
import localeHe from "@angular/common/locales/he";
import localeHi from "@angular/common/locales/hi";
import localeHr from "@angular/common/locales/hr";
import localeHu from "@angular/common/locales/hu";
import localeId from "@angular/common/locales/id";
import localeIt from "@angular/common/locales/it";
import localeJa from "@angular/common/locales/ja";
import localeKa from "@angular/common/locales/ka";
import localeKm from "@angular/common/locales/km";
import localeKn from "@angular/common/locales/kn";
import localeKo from "@angular/common/locales/ko";
import localeLv from "@angular/common/locales/lv";
import localeMl from "@angular/common/locales/ml";
import localeNb from "@angular/common/locales/nb";
import localeNl from "@angular/common/locales/nl";
import localeNn from "@angular/common/locales/nn";
import localePl from "@angular/common/locales/pl";
import localePtBr from "@angular/common/locales/pt";
import localePtPt from "@angular/common/locales/pt-PT";
import localeRo from "@angular/common/locales/ro";
import localeRu from "@angular/common/locales/ru";
import localeSi from "@angular/common/locales/si";
import localeSk from "@angular/common/locales/sk";
import localeSl from "@angular/common/locales/sl";
import localeSr from "@angular/common/locales/sr";
import localeSv from "@angular/common/locales/sv";
import localeTr from "@angular/common/locales/tr";
import localeUk from "@angular/common/locales/uk";
import localeVi from "@angular/common/locales/vi";
import localeZhCn from "@angular/common/locales/zh-Hans";
import localeZhTw from "@angular/common/locales/zh-Hant";
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { RouterModule } from "@angular/router";
import { BadgeModule, ButtonModule } from "@bitwarden/components";
import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { ToastrModule } from "ngx-toastr";
import { VerifyMasterPasswordComponent } from "jslib-angular/components/verify-master-password.component";
import { JslibModule } from "jslib-angular/jslib.module";
import { AcceptEmergencyComponent } from "./accounts/accept-emergency.component";
import { AcceptOrganizationComponent } from "./accounts/accept-organization.component";
import { HintComponent } from "./accounts/hint.component";
import { LockComponent } from "./accounts/lock.component";
import { LoginComponent } from "./accounts/login.component";
import { RecoverDeleteComponent } from "./accounts/recover-delete.component";
import { RecoverTwoFactorComponent } from "./accounts/recover-two-factor.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 { TwoFactorOptionsComponent } from "./accounts/two-factor-options.component";
import { TwoFactorComponent } from "./accounts/two-factor.component";
import { UpdatePasswordComponent } from "./accounts/update-password.component";
import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component";
import { VerifyEmailTokenComponent } from "./accounts/verify-email-token.component";
import { VerifyRecoverDeleteComponent } from "./accounts/verify-recover-delete.component";
import { NestedCheckboxComponent } from "./components/nested-checkbox.component";
import { PasswordRepromptComponent } from "./components/password-reprompt.component";
import { PasswordStrengthComponent } from "./components/password-strength.component";
import { PremiumBadgeComponent } from "./components/premium-badge.component";
import { FooterComponent } from "./layouts/footer.component";
import { FrontendLayoutComponent } from "./layouts/frontend-layout.component";
import { NavbarComponent } from "./layouts/navbar.component";
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
import { UserLayoutComponent } from "./layouts/user-layout.component";
import { BulkConfirmComponent as OrgBulkConfirmComponent } from "./organizations/manage/bulk/bulk-confirm.component";
import { BulkRemoveComponent as OrgBulkRemoveComponent } from "./organizations/manage/bulk/bulk-remove.component";
import { BulkStatusComponent as OrgBulkStatusComponent } from "./organizations/manage/bulk/bulk-status.component";
import { CollectionAddEditComponent as OrgCollectionAddEditComponent } from "./organizations/manage/collection-add-edit.component";
import { CollectionsComponent as OrgManageCollectionsComponent } from "./organizations/manage/collections.component";
import { EntityEventsComponent as OrgEntityEventsComponent } from "./organizations/manage/entity-events.component";
import { EntityUsersComponent as OrgEntityUsersComponent } from "./organizations/manage/entity-users.component";
import { EventsComponent as OrgEventsComponent } from "./organizations/manage/events.component";
import { GroupAddEditComponent as OrgGroupAddEditComponent } from "./organizations/manage/group-add-edit.component";
import { GroupsComponent as OrgGroupsComponent } from "./organizations/manage/groups.component";
import { ManageComponent as OrgManageComponent } from "./organizations/manage/manage.component";
import { PeopleComponent as OrgPeopleComponent } from "./organizations/manage/people.component";
import { PoliciesComponent as OrgPoliciesComponent } from "./organizations/manage/policies.component";
import { PolicyEditComponent as OrgPolicyEditComponent } from "./organizations/manage/policy-edit.component";
import { ResetPasswordComponent as OrgResetPasswordComponent } from "./organizations/manage/reset-password.component";
import { UserAddEditComponent as OrgUserAddEditComponent } from "./organizations/manage/user-add-edit.component";
import { UserConfirmComponent as OrgUserConfirmComponent } from "./organizations/manage/user-confirm.component";
import { UserGroupsComponent as OrgUserGroupsComponent } from "./organizations/manage/user-groups.component";
import { DisableSendPolicyComponent } from "./organizations/policies/disable-send.component";
import { MasterPasswordPolicyComponent } from "./organizations/policies/master-password.component";
import { PasswordGeneratorPolicyComponent } from "./organizations/policies/password-generator.component";
import { PersonalOwnershipPolicyComponent } from "./organizations/policies/personal-ownership.component";
import { RequireSsoPolicyComponent } from "./organizations/policies/require-sso.component";
import { ResetPasswordPolicyComponent } from "./organizations/policies/reset-password.component";
import { SendOptionsPolicyComponent } from "./organizations/policies/send-options.component";
import { SingleOrgPolicyComponent } from "./organizations/policies/single-org.component";
import { TwoFactorAuthenticationPolicyComponent } from "./organizations/policies/two-factor-authentication.component";
import { AccountComponent as OrgAccountComponent } from "./organizations/settings/account.component";
import { AdjustSubscription } from "./organizations/settings/adjust-subscription.component";
import { ChangePlanComponent } from "./organizations/settings/change-plan.component";
import { DeleteOrganizationComponent } from "./organizations/settings/delete-organization.component";
import { DownloadLicenseComponent } from "./organizations/settings/download-license.component";
import { OrganizationBillingComponent } from "./organizations/settings/organization-billing.component";
import { OrganizationSubscriptionComponent } from "./organizations/settings/organization-subscription.component";
import { SettingsComponent as OrgSettingComponent } from "./organizations/settings/settings.component";
import { TwoFactorSetupComponent as OrgTwoFactorSetupComponent } from "./organizations/settings/two-factor-setup.component";
import { FamiliesForEnterpriseSetupComponent } from "./organizations/sponsorships/families-for-enterprise-setup.component";
import { ExportComponent as OrgExportComponent } from "./organizations/tools/export.component";
import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "./organizations/tools/exposed-passwords-report.component";
import { ImportComponent as OrgImportComponent } from "./organizations/tools/import.component";
import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent } from "./organizations/tools/inactive-two-factor-report.component";
import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } from "./organizations/tools/reused-passwords-report.component";
import { ToolsComponent as OrgToolsComponent } from "./organizations/tools/tools.component";
import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "./organizations/tools/unsecured-websites-report.component";
import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "./organizations/tools/weak-passwords-report.component";
import { AddEditComponent as OrgAddEditComponent } from "./organizations/vault/add-edit.component";
import { AttachmentsComponent as OrgAttachmentsComponent } from "./organizations/vault/attachments.component";
import { CiphersComponent as OrgCiphersComponent } from "./organizations/vault/ciphers.component";
import { CollectionsComponent as OrgCollectionsComponent } from "./organizations/vault/collections.component";
import { GroupingsComponent as OrgGroupingsComponent } from "./organizations/vault/groupings.component";
import { VaultComponent as OrgVaultComponent } from "./organizations/vault/vault.component";
import { ProvidersComponent } from "./providers/providers.component";
import { BreachReportComponent } from "./reports/breach-report.component";
import { ExposedPasswordsReportComponent } from "./reports/exposed-passwords-report.component";
import { InactiveTwoFactorReportComponent } from "./reports/inactive-two-factor-report.component";
import { ReportCardComponent } from "./reports/report-card.component";
import { ReportListComponent } from "./reports/report-list.component";
import { ReportsComponent } from "./reports/reports.component";
import { ReusedPasswordsReportComponent } from "./reports/reused-passwords-report.component";
import { UnsecuredWebsitesReportComponent } from "./reports/unsecured-websites-report.component";
import { WeakPasswordsReportComponent } from "./reports/weak-passwords-report.component";
import { AccessComponent } from "./send/access.component";
import { AddEditComponent as SendAddEditComponent } from "./send/add-edit.component";
import { EffluxDatesComponent as SendEffluxDatesComponent } from "./send/efflux-dates.component";
import { SendComponent } from "./send/send.component";
import { AccountComponent } from "./settings/account.component";
import { AddCreditComponent } from "./settings/add-credit.component";
import { AdjustPaymentComponent } from "./settings/adjust-payment.component";
import { AdjustStorageComponent } from "./settings/adjust-storage.component";
import { ApiKeyComponent } from "./settings/api-key.component";
import { ChangeEmailComponent } from "./settings/change-email.component";
import { ChangeKdfComponent } from "./settings/change-kdf.component";
import { ChangePasswordComponent } from "./settings/change-password.component";
import { CreateOrganizationComponent } from "./settings/create-organization.component";
import { DeauthorizeSessionsComponent } from "./settings/deauthorize-sessions.component";
import { DeleteAccountComponent } from "./settings/delete-account.component";
import { DomainRulesComponent } from "./settings/domain-rules.component";
import { EmergencyAccessAddEditComponent } from "./settings/emergency-access-add-edit.component";
import { EmergencyAccessAttachmentsComponent } from "./settings/emergency-access-attachments.component";
import { EmergencyAccessConfirmComponent } from "./settings/emergency-access-confirm.component";
import { EmergencyAccessTakeoverComponent } from "./settings/emergency-access-takeover.component";
import { EmergencyAccessViewComponent } from "./settings/emergency-access-view.component";
import { EmergencyAccessComponent } from "./settings/emergency-access.component";
import { EmergencyAddEditComponent } from "./settings/emergency-add-edit.component";
import { LinkSsoComponent } from "./settings/link-sso.component";
import { OptionsComponent } from "./settings/options.component";
import { OrganizationPlansComponent } from "./settings/organization-plans.component";
import { OrganizationsComponent } from "./settings/organizations.component";
import { PaymentComponent } from "./settings/payment.component";
import { PremiumComponent } from "./settings/premium.component";
import { ProfileComponent } from "./settings/profile.component";
import { PurgeVaultComponent } from "./settings/purge-vault.component";
import { SettingsComponent } from "./settings/settings.component";
import { SponsoredFamiliesComponent } from "./settings/sponsored-families.component";
import { SponsoringOrgRowComponent } from "./settings/sponsoring-org-row.component";
import { TaxInfoComponent } from "./settings/tax-info.component";
import { TwoFactorAuthenticatorComponent } from "./settings/two-factor-authenticator.component";
import { TwoFactorDuoComponent } from "./settings/two-factor-duo.component";
import { TwoFactorEmailComponent } from "./settings/two-factor-email.component";
import { TwoFactorRecoveryComponent } from "./settings/two-factor-recovery.component";
import { TwoFactorSetupComponent } from "./settings/two-factor-setup.component";
import { TwoFactorVerifyComponent } from "./settings/two-factor-verify.component";
import { TwoFactorWebAuthnComponent } from "./settings/two-factor-webauthn.component";
import { TwoFactorYubiKeyComponent } from "./settings/two-factor-yubikey.component";
import { UpdateKeyComponent } from "./settings/update-key.component";
import { UpdateLicenseComponent } from "./settings/update-license.component";
import { UserBillingComponent } from "./settings/user-billing.component";
import { UserSubscriptionComponent } from "./settings/user-subscription.component";
import { VaultTimeoutInputComponent } from "./settings/vault-timeout-input.component";
import { VerifyEmailComponent } from "./settings/verify-email.component";
import { ExportComponent } from "./tools/export.component";
import { GeneratorComponent } from "./tools/generator.component";
import { ImportComponent } from "./tools/import.component";
import { PasswordGeneratorHistoryComponent } from "./tools/password-generator-history.component";
import { ToolsComponent } from "./tools/tools.component";
import { AddEditCustomFieldsComponent } from "./vault/add-edit-custom-fields.component";
import { AddEditComponent } from "./vault/add-edit.component";
import { AttachmentsComponent } from "./vault/attachments.component";
import { BulkActionsComponent } from "./vault/bulk-actions.component";
import { BulkDeleteComponent } from "./vault/bulk-delete.component";
import { BulkMoveComponent } from "./vault/bulk-move.component";
import { BulkRestoreComponent } from "./vault/bulk-restore.component";
import { BulkShareComponent } from "./vault/bulk-share.component";
import { CiphersComponent } from "./vault/ciphers.component";
import { CollectionsComponent } from "./vault/collections.component";
import { FolderAddEditComponent } from "./vault/folder-add-edit.component";
import { GroupingsComponent } from "./vault/groupings.component";
import { ShareComponent } from "./vault/share.component";
import { VaultComponent } from "./vault/vault.component";
registerLocaleData(localeAf, "af");
registerLocaleData(localeAz, "az");
registerLocaleData(localeBe, "be");
registerLocaleData(localeBg, "bg");
registerLocaleData(localeBn, "bn");
registerLocaleData(localeBs, "bs");
registerLocaleData(localeCa, "ca");
registerLocaleData(localeCs, "cs");
registerLocaleData(localeDa, "da");
registerLocaleData(localeDe, "de");
registerLocaleData(localeEl, "el");
registerLocaleData(localeEnGb, "en-GB");
registerLocaleData(localeEnIn, "en-IN");
registerLocaleData(localeEo, "eo");
registerLocaleData(localeEs, "es");
registerLocaleData(localeEt, "et");
registerLocaleData(localeFi, "fi");
registerLocaleData(localeFil, "fil");
registerLocaleData(localeFr, "fr");
registerLocaleData(localeHe, "he");
registerLocaleData(localeHi, "hi");
registerLocaleData(localeHr, "hr");
registerLocaleData(localeHu, "hu");
registerLocaleData(localeId, "id");
registerLocaleData(localeIt, "it");
registerLocaleData(localeJa, "ja");
registerLocaleData(localeKa, "ka");
registerLocaleData(localeKm, "km");
registerLocaleData(localeKn, "kn");
registerLocaleData(localeKo, "ko");
registerLocaleData(localeLv, "lv");
registerLocaleData(localeMl, "ml");
registerLocaleData(localeNb, "nb");
registerLocaleData(localeNl, "nl");
registerLocaleData(localeNn, "nn");
registerLocaleData(localePl, "pl");
registerLocaleData(localePtBr, "pt-BR");
registerLocaleData(localePtPt, "pt-PT");
registerLocaleData(localeRo, "ro");
registerLocaleData(localeRu, "ru");
registerLocaleData(localeSi, "si");
registerLocaleData(localeSk, "sk");
registerLocaleData(localeSl, "sl");
registerLocaleData(localeSr, "sr");
registerLocaleData(localeSv, "sv");
registerLocaleData(localeTr, "tr");
registerLocaleData(localeUk, "uk");
registerLocaleData(localeVi, "vi");
registerLocaleData(localeZhCn, "zh-CN");
registerLocaleData(localeZhTw, "zh-TW");
import { LooseComponentsModule } from "./modules/loose-components.module";
import { PipesModule } from "./modules/pipes/pipes.module";
import { SharedModule } from "./modules/shared.module";
import { VaultFilterModule } from "./modules/vault-filter/vault-filter.module";
import { OrganizationBadgeModule } from "./modules/vault/modules/organization-badge/organization-badge.module";
@NgModule({
imports: [
CommonModule,
DragDropModule,
FormsModule,
InfiniteScrollModule,
JslibModule,
ReactiveFormsModule,
RouterModule,
ToastrModule,
BadgeModule,
ButtonModule,
SharedModule,
LooseComponentsModule,
VaultFilterModule,
OrganizationBadgeModule,
PipesModule,
],
declarations: [
PremiumBadgeComponent,
AcceptEmergencyComponent,
AcceptOrganizationComponent,
AccessComponent,
AccountComponent,
AddCreditComponent,
AddEditComponent,
AddEditCustomFieldsComponent,
AddEditCustomFieldsComponent,
AdjustPaymentComponent,
AdjustStorageComponent,
AdjustSubscription,
ApiKeyComponent,
AttachmentsComponent,
BreachReportComponent,
BulkActionsComponent,
BulkDeleteComponent,
BulkMoveComponent,
BulkRestoreComponent,
BulkShareComponent,
ChangeEmailComponent,
ChangeKdfComponent,
ChangePasswordComponent,
ChangePlanComponent,
CiphersComponent,
CollectionsComponent,
CreateOrganizationComponent,
DeauthorizeSessionsComponent,
DeleteAccountComponent,
DeleteOrganizationComponent,
DisableSendPolicyComponent,
DomainRulesComponent,
DownloadLicenseComponent,
EmergencyAccessAddEditComponent,
EmergencyAccessAttachmentsComponent,
EmergencyAccessComponent,
EmergencyAccessConfirmComponent,
EmergencyAccessTakeoverComponent,
EmergencyAccessViewComponent,
EmergencyAddEditComponent,
ExportComponent,
ExposedPasswordsReportComponent,
FamiliesForEnterpriseSetupComponent,
FolderAddEditComponent,
FooterComponent,
FrontendLayoutComponent,
GroupingsComponent,
HintComponent,
ImportComponent,
InactiveTwoFactorReportComponent,
LinkSsoComponent,
LockComponent,
LoginComponent,
MasterPasswordPolicyComponent,
NavbarComponent,
NestedCheckboxComponent,
OptionsComponent,
OrgAccountComponent,
OrgAddEditComponent,
OrganizationBillingComponent,
OrganizationLayoutComponent,
OrganizationPlansComponent,
OrganizationsComponent,
OrganizationSubscriptionComponent,
OrgAttachmentsComponent,
OrgBulkConfirmComponent,
OrgBulkRemoveComponent,
OrgBulkStatusComponent,
OrgCiphersComponent,
OrgCollectionAddEditComponent,
OrgCollectionsComponent,
OrgEntityEventsComponent,
OrgEntityUsersComponent,
OrgEventsComponent,
OrgExportComponent,
OrgExposedPasswordsReportComponent,
OrgGroupAddEditComponent,
OrgGroupingsComponent,
OrgGroupsComponent,
OrgImportComponent,
OrgInactiveTwoFactorReportComponent,
OrgManageCollectionsComponent,
OrgManageComponent,
OrgPeopleComponent,
OrgPoliciesComponent,
OrgPolicyEditComponent,
OrgResetPasswordComponent,
OrgReusedPasswordsReportComponent,
OrgSettingComponent,
OrgToolsComponent,
OrgTwoFactorSetupComponent,
OrgUnsecuredWebsitesReportComponent,
OrgUserAddEditComponent,
OrgUserConfirmComponent,
OrgUserGroupsComponent,
OrgVaultComponent,
OrgWeakPasswordsReportComponent,
GeneratorComponent,
PasswordGeneratorHistoryComponent,
PasswordGeneratorPolicyComponent,
PasswordRepromptComponent,
PasswordStrengthComponent,
PaymentComponent,
PersonalOwnershipPolicyComponent,
PremiumComponent,
ProfileComponent,
ProvidersComponent,
PurgeVaultComponent,
RecoverDeleteComponent,
RecoverTwoFactorComponent,
RegisterComponent,
RemovePasswordComponent,
ReportCardComponent,
ReportListComponent,
ReportsComponent,
RequireSsoPolicyComponent,
ResetPasswordPolicyComponent,
ReusedPasswordsReportComponent,
SendAddEditComponent,
SendComponent,
SendEffluxDatesComponent,
SendOptionsPolicyComponent,
SetPasswordComponent,
SettingsComponent,
ShareComponent,
SingleOrgPolicyComponent,
SponsoredFamiliesComponent,
SponsoringOrgRowComponent,
SsoComponent,
TaxInfoComponent,
ToolsComponent,
TwoFactorAuthenticationPolicyComponent,
TwoFactorAuthenticatorComponent,
TwoFactorComponent,
TwoFactorDuoComponent,
TwoFactorEmailComponent,
TwoFactorOptionsComponent,
TwoFactorRecoveryComponent,
TwoFactorSetupComponent,
TwoFactorVerifyComponent,
TwoFactorWebAuthnComponent,
TwoFactorYubiKeyComponent,
UnsecuredWebsitesReportComponent,
UpdateKeyComponent,
UpdateLicenseComponent,
UpdatePasswordComponent,
UpdateTempPasswordComponent,
UserBillingComponent,
UserLayoutComponent,
UserSubscriptionComponent,
VaultComponent,
VaultTimeoutInputComponent,
VerifyEmailComponent,
VerifyEmailTokenComponent,
VerifyMasterPasswordComponent,
VerifyRecoverDeleteComponent,
WeakPasswordsReportComponent,
],
exports: [FooterComponent, NavbarComponent, OrganizationPlansComponent],
providers: [DatePipe],
exports: [LooseComponentsModule, VaultFilterModule, OrganizationBadgeModule, PipesModule],
bootstrap: [],
})
export class OssModule {}

View File

@@ -1,58 +1,33 @@
<ng-container *ngIf="vault">
<app-navbar></app-navbar>
<div class="container page-content">
<div class="page-header d-flex">
<h1>{{ "providers" | i18n }}</h1>
</div>
<p *ngIf="!loaded" class="text-muted">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</p>
<ng-container *ngIf="loaded">
<ul class="bwi-ul card-ul carets" *ngIf="providers && providers.length">
<li *ngFor="let p of providers">
<a [routerLink]="['/providers', p.id]" class="text-body">
<i class="bwi bwi-li bwi-caret-right" aria-hidden="true"></i> {{ p.name }}
<ng-container *ngIf="!p.enabled">
<i
class="bwi bwi-exclamation-triangle text-danger"
title="{{ 'providerIsDisabled' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "providerIsDisabled" | i18n }}</span>
</ng-container>
</a>
</li>
</ul>
<table class="table table-hover table-list" *ngIf="providers && providers.length">
<tbody>
<tr *ngFor="let p of providers">
<td width="30">
<app-avatar [data]="p.name" size="25" [circle]="true" [fontSize]="14"></app-avatar>
</td>
<td>
<a href="#" [routerLink]="['/providers', p.id]">{{ p.name }}</a>
<ng-container *ngIf="!p.enabled">
<i
class="bwi bwi-exclamation-triangle text-danger"
title="{{ 'providerIsDisabled' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "providerIsDisabled" | i18n }}</span>
</ng-container>
</td>
</tr>
</tbody>
</table>
</ng-container>
</ng-container>
<ng-container *ngIf="!vault">
<app-navbar></app-navbar>
<div class="container page-content">
<div class="page-header d-flex">
<h1>{{ "providers" | i18n }}</h1>
</div>
<p *ngIf="!loaded" class="text-muted">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</p>
<ng-container *ngIf="loaded">
<table class="table table-hover table-list" *ngIf="providers && providers.length">
<tbody>
<tr *ngFor="let p of providers">
<td width="30">
<app-avatar [data]="p.name" size="25" [circle]="true" [fontSize]="14"></app-avatar>
</td>
<td>
<a href="#" [routerLink]="['/providers', p.id]">{{ p.name }}</a>
<ng-container *ngIf="!p.enabled">
<i
class="bwi bwi-exclamation-triangle text-danger"
title="{{ 'providerIsDisabled' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "providerIsDisabled" | i18n }}</span>
</ng-container>
</td>
</tr>
</tbody>
</table>
</ng-container>
</div>
<app-footer></app-footer>
</ng-container>
</div>
<app-footer></app-footer>

View File

@@ -1,4 +1,4 @@
import { Component, Input, OnInit } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { ProviderService } from "jslib-common/abstractions/provider.service";
@@ -10,8 +10,6 @@ import { Provider } from "jslib-common/models/domain/provider";
templateUrl: "providers.component.html",
})
export class ProvidersComponent implements OnInit {
@Input() vault = false;
providers: Provider[];
loaded = false;
actionPromise: Promise<any>;

View File

@@ -1,7 +1,7 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { AuthGuard } from "jslib-angular/guards/auth.guard";
import { BreachReportComponent } from "./breach-report.component";
import { ExposedPasswordsReportComponent } from "./exposed-passwords-report.component";
@@ -16,7 +16,7 @@ const routes: Routes = [
{
path: "",
component: ReportsComponent,
canActivate: [AuthGuardService],
canActivate: [AuthGuard],
children: [
{ path: "", pathMatch: "full", component: ReportListComponent, data: { homepage: true } },
{
@@ -57,4 +57,4 @@ const routes: Routes = [
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class ReportsModule {}
export class ReportsRoutingModule {}

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