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

Compare commits

...

103 Commits

Author SHA1 Message Date
snyk-bot
fbb935995b fix: package.json & package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-NODEFORGE-2430337
- https://snyk.io/vuln/SNYK-JS-NODEFORGE-2430339
- https://snyk.io/vuln/SNYK-JS-NODEFORGE-2430341
2022-03-20 21:07:10 +00:00
github-actions[bot]
e08726463e Autosync the updated translations (#1554)
Co-authored-by: github-actions <>
2022-03-18 01:17:26 +01:00
Justin Baur
fdf93b610c Update CSP rule for azure blobs (#1552) 2022-03-17 09:54:14 -04:00
Vince Grassia
144038ed1c Move checkout steps before setup-node steps in Build workflow (#1553) 2022-03-17 09:02:50 -04:00
Vince Grassia
5cb5e37270 Add Node caching (#1549) 2022-03-16 11:18:47 -04:00
github-actions[bot]
e266a740ba Bump version to 2.27.0 (#1545)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-03-15 15:39:35 -06:00
Chad Scharf
3b0fc94239 Update SECURITY.md (#1544)
* Update SECURITY.md

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

* Fix extra space (prettier)

* Revise language on SECURITY.md
2022-03-15 15:45:05 -04:00
Matt Gibson
32e27b5f08 Update jslib (#1542) 2022-03-15 08:11:47 -05:00
Thomas Rittson
317c40386f Update jslib (#1540) 2022-03-15 15:14:40 +10:00
github-actions[bot]
c9eeca7def Autosync the updated translations (#1537)
Co-authored-by: github-actions <>
2022-03-11 02:31:15 +01:00
Thomas Rittson
902c568c09 Remove undefined template variable (#1534) 2022-03-11 07:20:03 +10:00
Joseph Flinn
153870693b Update hotfix release branch name to hotfix-rc (#1532) 2022-03-09 12:46:20 -08:00
Addison Beck
a8cd2a6cf7 [bug] Partially revert #1516 (#1531) 2022-03-09 11:16:53 -05:00
Vince Grassia
7404da9b3c Add web-sh repository to QA container registry (#1528) 2022-03-08 16:39:54 -05:00
Oscar Hinton
9b40ce1024 Component Library Scaffolding (#1407) 2022-03-08 18:18:03 +01:00
Daniel James Smith
80ffa965e1 Pull jslib for https://github.com/bitwarden/jslib/pull/714 (#1526) 2022-03-08 13:52:56 +01:00
Oscar Hinton
57f1a5e380 Remove msbuild from workflow (#1522) 2022-03-08 11:48:12 +01:00
Daniel James Smith
18f1929f65 Pull in Dashlane and Myki importer (#1523)
* Pull jslib

* Update copy for Dashlane json and csv importers
2022-03-08 09:01:19 +10:00
Micaiah Martin
5cb3941190 BEEEP - organize linting in CI (#1520) 2022-03-07 11:02:23 -05:00
Daniel James Smith
0e515bc6c1 Remove 2fa.directory/v2 from CSP (#1519) 2022-03-04 19:04:49 +01:00
Addison Beck
e103ddf02f [feature] Refine content of the organization delete request confirmation warning (#1508)
This commit updates the organization delete request confirmation warning based on new copy from the product team.

Changes are as follows:
* Add a load toggle to the organization delete modal, as we now have data to collect.
* Adjust how the families for enterprise error state for invalid sponserships connects with the organization delete component. Previously it just sent in a localization key to use for the description, but this commit adds a union type for identifying different delete flows and moves the FOE description localization key into the template with a condition.
* Move the callout on the organization delete component to above the description text.
* Adjust content of the typical organization delete request description based on copy from the product team.
  * This includes a list of item types in use by the organization that will be deleted and the amount of each type that exist in the organization.
2022-03-04 15:03:48 +01:00
github-actions[bot]
8242989b9d Autosync the updated translations (#1518)
Co-authored-by: github-actions <>
2022-03-04 01:25:14 +01:00
Vincent Salucci
5e7d94efb8 [Captcha] Implement captcha for 2fa (#1513)
* [Captcha] Implement captcha for 2fa

* Removed center justified captcha for now
2022-03-03 18:20:43 -06:00
Addison Beck
3bc8955dd5 [dep] Update jslib (#1517) 2022-03-03 20:14:19 +01:00
Vince Grassia
bc05d27082 Add logic for pushing latest image to QA container registry (#1515) 2022-03-03 14:00:41 -05:00
Addison Beck
e93c155885 [bug] Set full width on login (#1516) 2022-03-03 12:17:19 -05:00
Daniel James Smith
1076749635 Add importing of 1passwords 1pux files (#1507)
* Pull in jslib

* Install jszip

* Display help on selecting 1pux importer

* Unzip 1pux and pass content of export.data to 1pux importer

* Update jslib
2022-03-03 15:41:48 +01:00
Thomas Rittson
06e1af6d48 Improve SSO Config validation (#1332)
* Break form controls up into reusable components

* Add proper form styling, validation, inline error messages, etc

* Move control options into class instead of template

* Add accessibility
2022-03-03 11:08:41 +01:00
github-actions[bot]
cf9a90d10e Bumped version to 2.26.2 (#1511)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-03-02 15:08:58 -06:00
Matt Gibson
6e8c15bccd Fix mobile + url encoding issue (#1510) 2022-03-02 14:49:35 -06:00
Matt Gibson
7d018e4b59 Fix dev server connector headers (#1509) 2022-03-02 14:27:34 -06:00
Chad Scharf
f832cb4138 Remove artifact binding from SSO config UI (#1502) 2022-02-28 13:43:31 -05:00
Addison Beck
b8a23cf014 [feature] Implement scope warning for exports (#1504) 2022-02-28 11:37:02 -05:00
Daniel James Smith
d0c0e80b6c BEEEP: Add missing languages (#1501)
* Pull in jslib

* Import and register missing locales

* Update supportedTranslationLocales
2022-02-25 20:59:07 +01:00
Thomas Rittson
98fb71fcb6 Update jslib (#1494)
* Update jslib

* Add i18n error message

* Update imports
2022-02-25 10:19:37 -05:00
Micaiah Martin
1b52b5a98a Added dry run logic (#1497) 2022-02-25 09:07:54 -05:00
github-actions[bot]
c3e5c74253 Autosync the updated translations (#1500)
Co-authored-by: github-actions <>
2022-02-25 12:27:49 +01:00
Micaiah Martin
df5b175cdf Remove workflows from build trigger (#1496) 2022-02-24 08:42:14 -06:00
Oscar Hinton
1c495e87c9 Add eslint (#1437) 2022-02-24 12:10:07 +01:00
Oscar Hinton
01f128a4a9 Remove dotnet restore (#1493) 2022-02-22 16:22:45 +01:00
Oscar Hinton
a4d5b145ac Exclude connectors from CSP rules (#1489) 2022-02-22 15:46:59 +01:00
Chad Scharf
d944e0e25c We're Hiring (#1492)
Added link to README.md for Bitwarden Careers page.
2022-02-22 14:04:06 +01:00
Vincent Salucci
d141ccca52 [Icons Bug] fa-remove -> bwi-close (#1487) 2022-02-20 17:03:58 -06:00
Micaiah Martin
9e872bed2c Create initial workflow (#1482) 2022-02-18 13:28:53 -06:00
github-actions[bot]
c071b692f2 Autosync the updated translations (#1486)
Co-authored-by: github-actions <>
2022-02-18 10:24:33 +01:00
Matt Gibson
041bb1bf0a Enforce Hold label (#1478)
* Enforce Hold label

* Linting

Co-authored-by: Micaiah Martin <77340197+mimartin12@users.noreply.github.com>
2022-02-16 08:43:56 -06:00
Thomas Rittson
0b5e1eb256 Exclude jslib from prettier hook (#1458)
* Exclude jslib from prettier hook
2022-02-16 07:41:41 +10:00
Addison Beck
8c39fdb21e [lib] Update jslib (#1479) 2022-02-15 15:05:49 -05:00
Addison Beck
ca3efc8fee [bug] Disable state cache (#1477) 2022-02-14 12:26:22 -05:00
github-actions[bot]
c323f38f16 Autosync the updated translations (#1461)
Co-authored-by: github-actions <>
2022-02-11 22:48:28 +01:00
Addison Beck
9df4eb4c0d [bug] Store last sync in memory (#1471) 2022-02-11 03:39:48 -05:00
Addison Beck
1712ed53be [bug] Store vault data in memory (#1470) 2022-02-11 09:21:51 +01:00
github-actions[bot]
45a39f6200 Bumped version to 2.26.1 (#1468)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-02-10 21:51:45 -08:00
Thomas Rittson
a2d241263b Update jslib (#1466) 2022-02-11 00:30:37 -05:00
Thomas Rittson
5987d3deda Update jslib (#1465) 2022-02-11 14:58:33 +10:00
Thomas Rittson
080a3c655e Update jslib (#1464) 2022-02-11 14:30:07 +10:00
Justin Baur
dac48242b7 Update jslib (#1462) 2022-02-10 22:16:44 -05:00
Robyn MacCallum
e4d9ab52a0 update jslib (#1460)
* update jslib

* Remove call to tokenService.clear() from logout
2022-02-10 19:51:36 -05:00
Thomas Rittson
aee8a2661e Fix Copy Verification Code not showing after first log in (#1459)
* Move init logic to load()
2022-02-11 09:48:54 +10:00
Robyn MacCallum
ff6bb236c0 Update jslib (#1457) 2022-02-10 14:22:44 -05:00
Vincent Salucci
f79b20294a [Help] Update links to new pattern (#1454)
* [Help] Update links to new pattern

* Close help

* Update jslib
2022-02-08 17:44:47 -06:00
github-actions[bot]
3a0c34b934 Bump version to 2.26.0 (#1452)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-02-08 15:54:35 -07:00
github-actions[bot]
e09df347f4 Bump version to 1.26.0 (#1450)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-02-08 14:46:12 -07:00
Oscar Hinton
e68ab0031d Client & Version headers (#1434) 2022-02-08 13:22:31 +01:00
Justin Baur
64416c9406 Send in error message (#1449) 2022-02-07 16:15:49 -05:00
Matt Gibson
6779adb064 Handle password protected import export (#1448)
This updates requirements without implementing any way for the UI to
interact with the new feature
2022-02-07 14:15:22 -06:00
Thomas Rittson
1b28a4b954 Update client for authService refactor (#1387)
Co-authored-by: Hinton <oscar@oscarhinton.com>
2022-02-07 20:24:49 +01:00
Addison Beck
6320498fb3 [chore] Update jslib (#1447) 2022-02-07 12:04:51 -05:00
Oscar Hinton
bfd5f3e564 Fix register button using wrong icon (#1446) 2022-02-07 12:28:28 +01:00
Thomas Rittson
c755443735 Make husky pre-commit hook executable (#1432) 2022-02-07 12:18:33 +10:00
Justin Baur
0e5f2530a9 Switch option values to be a number (#1444) 2022-02-04 16:59:20 -05:00
Vincent Salucci
5105633fa4 [Icons] Fix button icon/text margins (#1443) 2022-02-04 13:49:44 -06:00
github-actions[bot]
e975056c21 Autosync the updated translations (#1441)
Co-authored-by: github-actions <>
2022-02-04 12:48:56 +01:00
Daniel James Smith
be21167ef8 Pull jslib for https://github.com/bitwarden/jslib/pull/654 (#1439) 2022-02-03 23:44:28 +01:00
Addison Beck
e09898e4d8 Update jslib (#1436) 2022-02-03 13:54:15 -05:00
Vincent Salucci
868d235faa [Icons] FF - requested icon changes (#1435)
* [Icons] Removed FA

* Icon changes // Webpack correction // Padding updates
2022-02-03 10:20:31 -06:00
Oscar Hinton
5c764a95f4 Add CSP for development (#1431) 2022-02-03 10:17:33 +01:00
Jake Fink
596c3e86e9 Master password policy is not checked when accepting invite from an existing account (#1371)
* validate password against org policy and create update-password component

* linting and prettier

* [bug] Default rememberEmail to true (#1429)

* switching the dashes to underscores for the branch name (#1433)

(cherry picked from commit 8910430dfb)

* fix merge conflicts

* Update src/app/accounts/update-password.component.html

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

* Update src/locales/en/messages.json

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

* update jslib

* prettier

Co-authored-by: Addison Beck <abeck@bitwarden.com>
Co-authored-by: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com>
Co-authored-by: Justin Baur <admin@justinbaur.com>
2022-02-03 00:00:57 -05:00
Addison Beck
8030da2ed5 [bug] Default rememberEmail to true (#1429) 2022-02-02 11:20:15 -05:00
Joseph Flinn
8910430dfb switching the dashes to underscores for the branch name (#1433) 2022-02-02 07:30:19 -08:00
Joseph Flinn
6bf6d4b47f Reverting tis change for next release (#1430) 2022-02-02 06:22:25 -08:00
Oscar Hinton
ca199a398e Fix captcha and webauthn-fallback not using theme_light (#1427) 2022-02-01 15:46:16 +01:00
Chad Scharf
61ab2fbda3 Hide unsolicited SSO option in config (#1425) 2022-01-31 17:43:56 -05:00
Addison Beck
d79f074825 [bug] Extend GlobalState to supply correct default theme (#1422)
* [bug] Extend GlobalState to supply correct default theme

The default theme for most clients is System, but web uses Light.
We need to extend GlobalState in web to reflect this.

* [chore] Update jslib
2022-01-31 14:53:55 -05:00
Oscar Hinton
e3b962a779 Apply prettier on help pr (#1423) 2022-01-31 20:31:50 +01:00
DanHillesheim
cc657eb853 Update help site URLs (#1409) 2022-01-31 20:11:27 +01:00
DanHillesheim
e14a266ee0 Registration page updates (#1390)
Co-authored-by: Hinton <oscar@oscarhinton.com>
2022-01-31 20:10:35 +01:00
Thomas Rittson
e1732cfa10 Fix various bugs in Options page (#1418)
* Remove duplicate message in toast

* Set starting variables properly
2022-01-31 09:11:25 -05:00
Addison Beck
ce1ae208d1 [bug] Update theme.js to refelect new storage structure (#1416)
* [bug] Update theme.js to refelect new storage structure

* [bug] Remove unecassary defaults
2022-01-28 11:30:45 -05:00
Addison Beck
6996b06fa2 [chore] Update jslib (#1415) 2022-01-28 09:07:33 -05:00
github-actions[bot]
dc503d3461 Autosync the updated translations (#1414)
Co-authored-by: github-actions <>
2022-01-28 11:06:25 +01:00
Daniel James Smith
d95db8fb74 BEEEP: Add importer for Keeper in json format (#1392)
* Updated instructions to export a csv file from Keeper

* Add instructions to export a json file from Keeper

* Bump jslib to include Keeper json importer

* Revert change to README.md

* Pull in jslib
2022-01-27 21:51:56 +01:00
Justin Baur
1a219daa12 Remove F4E vault card (#1413)
* Remove F4E card from vault page

* Remove unneeded property
2022-01-27 15:47:56 -05:00
Vincent Salucci
2ae98887b7 [Icons] Update Font Sheet (#1343)
* [Icons] Update to new font sheet

* Rebased - updated all icon remaining icon references

* Temporarily Updating gitmodules branch

* Fixed class reference

* Revert temporary gitmodule branch

* Icon updates/changes

* Pull jslib m-icon-updates latest

* Prettier

* Update jslib to master

* Reset jslib to master

* Removed obsolete variable reference, replaced bolt references

* Removed all instances of base class - maps create automatically

* Updated toast icon references

* Imported styles to reference variable/map

* Reverted to using base class

* Update jslib

* Rename eye-2 to eye and eye-slash-2 to eye-slash

* Bump jslib

* Remove duplicate scss

* Remove old fa

* Update fallback image

* Bump jslib

* Rename eye-2 to eye, and eye-slash-2 to eye-slash

* Fix 404

* Fix integrity of bootstrap.min.css

* Fix callout missing bwi

* Add bwi to change-kdf

* Remove bwi from callout again

* Bump jslib

Co-authored-by: Hinton <oscar@oscarhinton.com>
2022-01-27 11:25:58 -06:00
Joseph Flinn
f0c47252e4 Updating the base Docker image for testing purposes (#1411) 2022-01-27 07:25:13 -08:00
Danny Murphy
2ffe3bd6ad Cleanup of the SCSS Variables (#1255)
* Clean up variable names and comments

* Fix Option Colour - issue #1338

* Update old scss variable name

Co-authored-by: Thomas Rittson <trittson@bitwarden.com>
2022-01-26 14:54:15 +10:00
Oscar Hinton
f387a4d469 Remove old u2f connector (#1406) 2022-01-21 15:00:53 +01:00
github-actions[bot]
a0f1b4dd0d Autosync the updated translations (#1404)
Co-authored-by: github-actions <>
2022-01-21 11:53:09 +01:00
Thomas Rittson
84a65edc08 Move keyConnectorService call to syncService (#1405) 2022-01-21 19:33:09 +10:00
Addison Beck
caad11c571 [chore] Update jslib (#1403) 2022-01-20 09:38:34 -05:00
Daniel James Smith
b73449159d Update copy on fingerprint phrase prompt (#1399) 2022-01-20 10:07:47 +01:00
Oscar Hinton
bf48434d0f Rename package to @bitwarden/web-vault (#1396) 2022-01-17 17:23:14 +01:00
Oscar Hinton
b6d2d5bf71 [BEEEP] Use shared tsconfig (#1393) 2022-01-17 15:53:19 +01:00
417 changed files with 24398 additions and 7850 deletions

8
.eslintignore Normal file
View File

@@ -0,0 +1,8 @@
**/dist
**/build
jslib
webpack.config.js
scripts/optimize.js
config.js
**/node_modules

31
.eslintrc.json Normal file
View File

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

View File

@@ -11,6 +11,9 @@ on:
branches-ignore: branches-ignore:
- "l10n_master" - "l10n_master"
- "gh-pages" - "gh-pages"
paths-ignore:
- '.github/workflows/**'
jobs: jobs:
cloc: cloc:
@@ -28,6 +31,28 @@ jobs:
- name: Print lines of code - name: Print lines of code
run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git
lint:
name: Lint
runs-on: ubuntu-20.04
steps:
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
- name: Cache npm
id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: "~/.npm"
key: ${{ runner.os }}-npm-lint-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
setup: setup:
name: Setup name: Setup
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
@@ -41,24 +66,25 @@ jobs:
id: version id: version
run: echo "::set-output name=value::${GITHUB_SHA:0:7}" run: echo "::set-output name=value::${GITHUB_SHA:0:7}"
build-oss-selfhost: build-oss-selfhost:
name: Build OSS zip name: Build OSS zip
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
needs: setup needs:
- setup
- lint
env: env:
_VERSION: ${{ needs.setup.outputs.version }} _VERSION: ${{ needs.setup.outputs.version }}
steps: steps:
- name: Set up Node - name: Checkout repo
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
with:
node-version: "16"
- name: Cache npm - name: Set up Node
id: npm-cache uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with: with:
path: "~/.npm" cache: 'npm'
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} cache-dependency-path: '**/package-lock.json'
node-version: "16"
- name: Print environment - name: Print environment
run: | run: |
@@ -70,9 +96,6 @@ jobs:
echo "GitHub ref: $GITHUB_REF" echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT" echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
@@ -88,24 +111,25 @@ jobs:
path: ./web-${{ env._VERSION }}-selfhosted-open-source.zip path: ./web-${{ env._VERSION }}-selfhosted-open-source.zip
if-no-files-found: error if-no-files-found: error
build-cloud: build-cloud:
name: Build Cloud zip name: Build Cloud zip
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
needs: setup needs:
- setup
- lint
env: env:
_VERSION: ${{ needs.setup.outputs.version }} _VERSION: ${{ needs.setup.outputs.version }}
steps: steps:
- name: Set up Node - name: Checkout repo
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
with:
node-version: "16"
- name: Cache npm - name: Set up Node
id: npm-cache uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with: with:
path: "~/.npm" cache: 'npm'
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} cache-dependency-path: '**/package-lock.json'
node-version: "16"
- name: Print environment - name: Print environment
run: | run: |
@@ -117,9 +141,6 @@ jobs:
echo "GitHub ref: $GITHUB_REF" echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT" echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
@@ -135,24 +156,25 @@ jobs:
path: ./web-${{ env._VERSION }}-cloud-COMMERCIAL.zip path: ./web-${{ env._VERSION }}-cloud-COMMERCIAL.zip
if-no-files-found: error if-no-files-found: error
build-commercial-selfhost: build-commercial-selfhost:
name: Build SelfHost Docker image name: Build SelfHost Docker image
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
needs: setup needs:
- setup
- lint
env: env:
_VERSION: ${{ needs.setup.outputs.version }} _VERSION: ${{ needs.setup.outputs.version }}
steps: steps:
- name: Set up Node - name: Checkout repo
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
with:
node-version: "16"
- name: Cache npm - name: Set up Node
id: npm-cache uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with: with:
path: "~/.npm" cache: 'npm'
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} cache-dependency-path: '**/package-lock.json'
node-version: "16"
- name: Print environment - name: Print environment
run: | run: |
@@ -165,19 +187,13 @@ jobs:
echo "GitHub event: $GITHUB_EVENT" echo "GitHub event: $GITHUB_EVENT"
- name: Setup DCT - name: Setup DCT
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix' if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
id: setup-dct id: setup-dct
uses: bitwarden/gh-actions/setup-docker-trust@a8c384a05a974c05c48374c818b004be221d43ff uses: bitwarden/gh-actions/setup-docker-trust@a8c384a05a974c05c48374c818b004be221d43ff
with: with:
azure-creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} azure-creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
azure-keyvault-name: "bitwarden-prod-kv" azure-keyvault-name: "bitwarden-prod-kv"
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Restore
run: dotnet tool restore
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
@@ -212,11 +228,11 @@ jobs:
run: docker tag bitwarden/web bitwarden/web:dev run: docker tag bitwarden/web bitwarden/web:dev
- name: Tag hotfix branch - name: Tag hotfix branch
if: github.ref == 'refs/heads/hotfix' if: github.ref == 'refs/heads/hotfix-rc'
run: docker tag bitwarden/web bitwarden/web:hotfix run: docker tag bitwarden/web bitwarden/web:hotfix-rc
- name: List Docker images - name: List Docker images
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix' if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
run: docker images run: docker images
- name: Push rc image - name: Push rc image
@@ -234,31 +250,58 @@ jobs:
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }} DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
- name: Push hotfix image - name: Push hotfix image
if: github.ref == 'refs/heads/hotfix' if: github.ref == 'refs/heads/hotfix-rc'
run: docker push bitwarden/web:hotfix run: docker push bitwarden/web:hotfix-rc
env: env:
DOCKER_CONTENT_TRUST: 1 DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }} DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
- name: Log out of Docker - name: Log out of Docker
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix' if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
run: |
docker logout
echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV
- name: Login to Azure - QA Subscription
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
- name: Login to Azure ACR
run: az acr login -n bitwardenqa
- name: Tag and Push RC to Azure ACR QA registry
env:
REGISTRY: bitwardenqa.azurecr.io
run: |
IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") # slash safe branch name
if [[ "$IMAGE_TAG" == "master" ]]; then
IMAGE_TAG=dev
fi
docker tag bitwarden/web \
$REGISTRY/web-sh:$IMAGE_TAG
docker push $REGISTRY/web-sh:$IMAGE_TAG
- name: Log out of Docker
run: docker logout run: docker logout
build-qa: build-qa:
name: Build Docker images for QA environment name: Build Docker images for QA environment
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
needs:
- setup
- lint
steps: steps:
- name: Set up Node - name: Checkout repo
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
with:
node-version: "16"
- name: Cache npm - name: Set up Node
id: npm-cache uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with: with:
path: "~/.npm" cache: 'npm'
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} cache-dependency-path: '**/package-lock.json'
node-version: "16"
- name: Print environment - name: Print environment
run: | run: |
@@ -278,12 +321,6 @@ jobs:
- name: Log into container registry - name: Log into container registry
run: az acr login -n bitwardenqa run: az acr login -n bitwardenqa
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Restore
run: dotnet tool restore
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
@@ -339,35 +376,29 @@ jobs:
- name: Log out of Docker - name: Log out of Docker
run: docker logout run: docker logout
windows: windows:
name: Test code on Windows name: Test code on Windows
runs-on: windows-2019 runs-on: windows-2019
steps: steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Set up NuGet - name: Set up NuGet
uses: nuget/setup-nuget@04b0c2b8d1b97922f67eca497d7cf0bf17b8ffe1 uses: nuget/setup-nuget@04b0c2b8d1b97922f67eca497d7cf0bf17b8ffe1
with: with:
nuget-version: "latest" nuget-version: "latest"
- name: Set up MSBuild
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d
- name: Cache npm
id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: "~/.npm"
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Set up Node - name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
with: with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: "16" node-version: "16"
- name: Print environment - name: Print environment
run: | run: |
nuget help | grep Version nuget help | grep Version
msbuild -version
dotnet --info
node --version node --version
npm --version npm --version
echo "GitHub ref: $GITHUB_REF" echo "GitHub ref: $GITHUB_REF"
@@ -376,18 +407,13 @@ jobs:
GITHUB_REF: ${{ github.ref }} GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }} GITHUB_EVENT: ${{ github.event_name }}
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: Run linter
run: npm run lint
- name: NPM build - name: NPM build
run: npm run build:bit:cloud run: npm run build:bit:cloud
crowdin-push: crowdin-push:
name: Crowdin Push name: Crowdin Push
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
@@ -426,6 +452,7 @@ jobs:
upload_sources: true upload_sources: true
upload_translations: false upload_translations: false
check-failures: check-failures:
name: Check for failures name: Check for failures
if: always() if: always()
@@ -433,6 +460,7 @@ jobs:
needs: needs:
- cloc - cloc
- setup - setup
- lint
- build-oss-selfhost - build-oss-selfhost
- build-cloud - build-cloud
- build-commercial-selfhost - build-commercial-selfhost
@@ -444,6 +472,7 @@ jobs:
if: ${{ (github.ref == 'refs/heads/master') || (github.ref == 'refs/heads/rc') }} if: ${{ (github.ref == 'refs/heads/master') || (github.ref == 'refs/heads/rc') }}
env: env:
CLOC_STATUS: ${{ needs.cloc.result }} CLOC_STATUS: ${{ needs.cloc.result }}
LINT_STATUS: ${{ needs.lint.result }}
SETUP_STATUS: ${{ needs.setup.result }} SETUP_STATUS: ${{ needs.setup.result }}
BUILD_OSS_SELFHOST_STATUS: ${{ needs.build-oss-selfhost.result }} BUILD_OSS_SELFHOST_STATUS: ${{ needs.build-oss-selfhost.result }}
BUILD_CLOUD_STATUS: ${{ needs.build-cloud.result }} BUILD_CLOUD_STATUS: ${{ needs.build-cloud.result }}
@@ -454,6 +483,8 @@ jobs:
run: | run: |
if [ "$CLOC_STATUS" = "failure" ]; then if [ "$CLOC_STATUS" = "failure" ]; then
exit 1 exit 1
elif [ "$LINT_STATUS" = "failure" ]; then
exit 1
elif [ "$SETUP_STATUS" = "failure" ]; then elif [ "$SETUP_STATUS" = "failure" ]; then
exit 1 exit 1
elif [ "$BUILD_OSS_SELFHOST_STATUS" = "failure" ]; then elif [ "$BUILD_OSS_SELFHOST_STATUS" = "failure" ]; then

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

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

View File

@@ -12,6 +12,7 @@ on:
options: options:
- Initial Release - Initial Release
- Redeploy - Redeploy
- Dry Run
jobs: jobs:
setup: setup:
@@ -20,19 +21,20 @@ jobs:
outputs: outputs:
release_version: ${{ steps.version.outputs.package }} release_version: ${{ steps.version.outputs.package }}
tag_version: ${{ steps.version.outputs.tag }} tag_version: ${{ steps.version.outputs.tag }}
branch-name: ${{ steps.branch.outputs.branch-name }} branch_name: ${{ steps.branch.outputs.branch_name }}
steps: steps:
- name: Branch check - name: Branch check
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
run: | run: |
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix" ]]; then if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
echo "===================================" echo "==================================="
echo "[!] Can only release from the 'rc' or 'hotfix' branches" echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches"
echo "===================================" echo "==================================="
exit 1 exit 1
fi fi
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # 2.3.4 uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # 2.4.0
- name: Check Release Version - name: Check Release Version
id: version id: version
@@ -55,15 +57,17 @@ jobs:
id: branch id: branch
run: | run: |
BRANCH_NAME=$(basename ${{ github.ref }}) BRANCH_NAME=$(basename ${{ github.ref }})
echo "::set-output name=branch-name::$BRANCH_NAME" echo "::set-output name=branch_name::$BRANCH_NAME"
self-host: self-host:
name: Release self-host docker name: Release self-host docker
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
needs: setup needs: setup
env: env:
_BRANCH_NAME: ${{ needs.setup.outputs.branch-name }} _BRANCH_NAME: ${{ needs.setup.outputs.branch_name }}
_RELEASE_VERSION: ${{ needs.setup.outputs.release_version }} _RELEASE_VERSION: ${{ needs.setup.outputs.release_version }}
_RELEASE_OPTION: ${{ github.event.inputs.release_type }}
steps: steps:
- name: Print environment - name: Print environment
run: | run: |
@@ -71,7 +75,12 @@ jobs:
docker --version docker --version
echo "GitHub ref: $GITHUB_REF" echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT" echo "GitHub event: $GITHUB_EVENT"
echo "Github Release Option: $_RELEASE_OPTION"
- name: Checkout repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
########## DockerHub ##########
- name: Setup DCT - name: Setup DCT
id: setup-dct id: setup-dct
uses: bitwarden/gh-actions/setup-docker-trust@a8c384a05a974c05c48374c818b004be221d43ff uses: bitwarden/gh-actions/setup-docker-trust@a8c384a05a974c05c48374c818b004be221d43ff
@@ -79,21 +88,25 @@ jobs:
azure-creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} azure-creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
azure-keyvault-name: "bitwarden-prod-kv" azure-keyvault-name: "bitwarden-prod-kv"
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Pull latest selfhost image - name: Pull latest selfhost image
run: docker pull bitwarden/web:$_BRANCH_NAME run: |
if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then
docker pull bitwarden/web:latest
else
docker pull bitwarden/web:$_BRANCH_NAME
fi
- name: Tag version and latest - name: Tag version and latest
run: | run: |
if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then
docker tag bitwarden/web:latest bitwarden/web:dryrun
else
docker tag bitwarden/web:$_BRANCH_NAME bitwarden/web:$_RELEASE_VERSION docker tag bitwarden/web:$_BRANCH_NAME bitwarden/web:$_RELEASE_VERSION
docker tag bitwarden/web:$_BRANCH_NAME bitwarden/web:latest docker tag bitwarden/web:$_BRANCH_NAME bitwarden/web:latest
fi
- name: List Docker images
run: docker images
- name: Push version and latest image - name: Push version and latest image
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
env: env:
DOCKER_CONTENT_TRUST: 1 DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }} DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
@@ -101,9 +114,43 @@ jobs:
docker push bitwarden/web:$_RELEASE_VERSION docker push bitwarden/web:$_RELEASE_VERSION
docker push bitwarden/web:latest docker push bitwarden/web:latest
- name: Log out of Docker and disable Docker Notary
run: |
docker logout
echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV
########## ACR ##########
- name: Login to Azure - QA Subscription
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
- name: Login to Azure ACR
run: az acr login -n bitwardenqa
- name: Tag version and latest
env:
REGISTRY: bitwardenqa.azurecr.io
run: |
if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then
docker tag bitwarden/web:latest $REGISTRY/web:dryrun
else
docker tag bitwarden/web:$_BRANCH_NAME $REGISTRY/web:$_RELEASE_VERSION
docker tag bitwarden/web:$_BRANCH_NAME $REGISTRY/web:latest
fi
- name: Push version and latest image
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
env:
REGISTRY: bitwardenqa.azurecr.io
run: |
docker push $REGISTRY/web:$_RELEASE_VERSION
docker push $REGISTRY/web:latest
- name: Log out of Docker - name: Log out of Docker
run: docker logout run: docker logout
ghpages-deploy: ghpages-deploy:
name: Deploy Web Vault name: Deploy Web Vault
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
@@ -115,7 +162,7 @@ jobs:
_TAG_VERSION: ${{ needs.setup.outputs.tag_version }} _TAG_VERSION: ${{ needs.setup.outputs.tag_version }}
steps: steps:
- name: Checkout Repo - name: Checkout Repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
with: with:
ref: gh-pages ref: gh-pages
@@ -125,7 +172,7 @@ jobs:
git push -u origin deploy-$_TAG_VERSION git push -u origin deploy-$_TAG_VERSION
- name: Checkout Repo - name: Checkout Repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
- name: Setup git config - name: Setup git config
run: | run: |
@@ -139,7 +186,7 @@ jobs:
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
branch: ${{ needs.setup.outputs.branch-name }} branch: ${{ needs.setup.outputs.branch_name }}
artifacts: web-*-cloud-COMMERCIAL.zip artifacts: web-*-cloud-COMMERCIAL.zip
# This should result in a build directory in the current working directory # This should result in a build directory in the current working directory
@@ -147,7 +194,7 @@ jobs:
run: unzip web-*-cloud-COMMERCIAL.zip run: unzip web-*-cloud-COMMERCIAL.zip
- name: Deploy GitHub Pages - name: Deploy GitHub Pages
uses: crazy-max/ghaction-github-pages@db4476a01402e1a7ce05f41832040eef16d14925 # v2.5.0 uses: crazy-max/ghaction-github-pages@a117e4aa1fb4854d021546d2abdfac95be568a3a # v2.6.0
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
@@ -155,8 +202,10 @@ jobs:
build_dir: build build_dir: build
keep_history: true keep_history: true
commit_message: "Staging deploy ${{ needs.setup.outputs.release_version }}" commit_message: "Staging deploy ${{ needs.setup.outputs.release_version }}"
dry_run: ${{ github.event.inputs.release_type == 'Dry Run' }}
- name: Create Deploy PR - name: Create Deploy PR
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
env: env:
PR_BRANCH: deploy-${{ env._TAG_VERSION }} PR_BRANCH: deploy-${{ env._TAG_VERSION }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -166,6 +215,7 @@ jobs:
--base gh-pages \ --base gh-pages \
--head "$PR_BRANCH" --head "$PR_BRANCH"
release: release:
name: Create GitHub Release name: Create GitHub Release
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
@@ -179,7 +229,7 @@ jobs:
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
branch: ${{ needs.setup.outputs.branch-name }} branch: ${{ needs.setup.outputs.branch_name }}
artifacts: "web-*-selfhosted-COMMERCIAL.zip, artifacts: "web-*-selfhosted-COMMERCIAL.zip,
web-*-selfhosted-open-source.zip" web-*-selfhosted-open-source.zip"
@@ -189,7 +239,8 @@ jobs:
mv web-*-selfhosted-open-source.zip web-${{ needs.setup.outputs.release_version }}-selfhosted-open-source.zip mv web-*-selfhosted-open-source.zip web-${{ needs.setup.outputs.release_version }}-selfhosted-open-source.zip
- name: Create release - name: Create release
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: ncipollo/release-action@40bb172bd05f266cf9ba4ff965cb61e9ee5f6d01
with: with:
name: "Version ${{ needs.setup.outputs.release_version }}" name: "Version ${{ needs.setup.outputs.release_version }}"
commit: ${{ github.sha }} commit: ${{ github.sha }}
@@ -199,3 +250,20 @@ jobs:
web-${{ needs.setup.outputs.release_version }}-selfhosted-open-source.zip" web-${{ needs.setup.outputs.release_version }}-selfhosted-open-source.zip"
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
draft: true draft: true
dry-run:
name: Dry Run Cleanup
runs-on: ubuntu-20.04
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
env:
_TAG_VERSION: ${{ needs.setup.outputs.tag_version }}
needs:
- setup
- release
steps:
- name: Checkout repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # 2.4.0
- name: Remove deploy branch
run: git push origin --delete deploy-$_TAG_VERSION

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

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

0
.husky/pre-commit Normal file → Executable file
View File

View File

@@ -7,7 +7,6 @@ jslib
# External libraries / auto synced locales # External libraries / auto synced locales
src/locales src/locales
src/404/*.min.css src/404/*.min.css
src/scripts/u2f.js
# Github Workflows # Github Workflows
.github/workflows .github/workflows

View File

@@ -61,6 +61,10 @@ You can also manually adjusting your API endpoint settings by adding `config/loc
Where the `urls` object is defined by the [Urls type in jslib](https://github.com/bitwarden/jslib/blob/master/common/src/abstractions/environment.service.ts). Where the `urls` object is defined by the [Urls type in jslib](https://github.com/bitwarden/jslib/blob/master/common/src/abstractions/environment.service.ts).
## We're Hiring!
Interested in contributing in a big way? Consider joining our team! We're hiring for many positions. Please take a look at our [Careers page](https://bitwarden.com/careers/) to see what opportunities are currently open as well as what it's like to work at Bitwarden.
## Contribute ## Contribute
Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [`CONTRIBUTING.md`](CONTRIBUTING.md) file. Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [`CONTRIBUTING.md`](CONTRIBUTING.md) file.

View File

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

View File

@@ -1,6 +1,7 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { AppComponent as BaseAppComponent } from "src/app/app.component"; import { AppComponent as BaseAppComponent } from "src/app/app.component";
import { DisablePersonalVaultExportPolicy } from "./policies/disable-personal-vault-export.component"; import { DisablePersonalVaultExportPolicy } from "./policies/disable-personal-vault-export.component";
import { MaximumVaultTimeoutPolicy } from "./policies/maximum-vault-timeout.component"; import { MaximumVaultTimeoutPolicy } from "./policies/maximum-vault-timeout.component";

View File

@@ -7,17 +7,17 @@ import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { BitwardenToastModule } from "jslib-angular/components/toastr.component"; import { BitwardenToastModule } from "jslib-angular/components/toastr.component";
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";
import { AppRoutingModule } from "./app-routing.module"; import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from "./app.component"; import { AppComponent } from "./app.component";
import { OrganizationsModule } from "./organizations/organizations.module"; import { OrganizationsModule } from "./organizations/organizations.module";
import { DisablePersonalVaultExportPolicyComponent } from "./policies/disable-personal-vault-export.component"; import { DisablePersonalVaultExportPolicyComponent } from "./policies/disable-personal-vault-export.component";
import { MaximumVaultTimeoutPolicyComponent } from "./policies/maximum-vault-timeout.component"; import { MaximumVaultTimeoutPolicyComponent } from "./policies/maximum-vault-timeout.component";
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";
@NgModule({ @NgModule({
imports: [ imports: [
OssModule, OssModule,

View File

@@ -5,8 +5,8 @@ import "bootstrap";
import "jquery"; import "jquery";
import "popper.js"; import "popper.js";
// tslint:disable-next-line
require("src/scss/styles.scss"); require("src/scss/styles.scss");
require("src/scss/tailwind.css");
import { AppModule } from "./app.module"; import { AppModule } from "./app.module";

View File

@@ -0,0 +1,68 @@
import { Directive, Input, OnInit, Self } from "@angular/core";
import { ControlValueAccessor, FormControl, NgControl, Validators } from "@angular/forms";
import { dirtyRequired } from "jslib-angular/validators/dirty.validator";
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
@Directive()
export abstract class BaseCvaComponent implements ControlValueAccessor, OnInit {
get describedById() {
return this.showDescribedBy ? this.controlId + "Desc" : null;
}
get showDescribedBy() {
return this.helperText != null || this.controlDir.control.hasError("required");
}
get isRequired() {
return (
this.controlDir.control.hasValidator(Validators.required) ||
this.controlDir.control.hasValidator(dirtyRequired)
);
}
@Input() label: string;
@Input() controlId: string;
@Input() helperText: string;
internalControl = new FormControl("");
protected onChange: any;
protected onTouched: any;
constructor(@Self() public controlDir: NgControl) {
this.controlDir.valueAccessor = this;
}
ngOnInit() {
this.internalControl.valueChanges.subscribe(this.onValueChangesInternal);
}
onBlurInternal() {
this.onTouched();
}
// CVA interfaces
writeValue(value: string) {
this.internalControl.setValue(value);
}
registerOnChange(fn: any) {
this.onChange = fn;
}
registerOnTouched(fn: any) {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean) {
if (isDisabled) {
this.internalControl.disable();
} else {
this.internalControl.enable();
}
}
protected onValueChangesInternal: any = (value: string) => this.onChange(value);
// End CVA interfaces
}

View File

@@ -0,0 +1,16 @@
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
[attr.id]="controlId"
[attr.aria-describedby]="describedById"
[formControl]="internalControl"
(blur)="onBlurInternal()"
/>
<label class="form-check-label" [attr.for]="controlId">{{ label }}</label>
</div>
<small *ngIf="showDescribedBy" [attr.id]="describedById" class="form-text text-muted">{{
helperText
}}</small>
</div>

View File

@@ -0,0 +1,10 @@
import { Component } from "@angular/core";
import { BaseCvaComponent } from "./base-cva.component";
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
@Component({
selector: "app-input-checkbox",
templateUrl: "input-checkbox.component.html",
})
export class InputCheckboxComponent extends BaseCvaComponent {}

View File

@@ -0,0 +1,26 @@
<div class="form-group">
<label>{{ label }}</label>
<div class="input-group">
<input class="form-control" readonly [value]="controlValue" />
<div class="input-group-append" *ngIf="showLaunch">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'launch' | i18n }}"
(click)="launchUri(controlValue)"
>
<i class="bwi bwi-lg bwi-external-link" aria-hidden="true"></i>
</button>
</div>
<div class="input-group-append" *ngIf="showCopy">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyValue' | i18n }}"
(click)="copy(controlValue)"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,25 @@
import { Component, Input } from "@angular/core";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
@Component({
selector: "app-input-text-readonly",
templateUrl: "input-text-readonly.component.html",
})
export class InputTextReadOnlyComponent {
@Input() controlValue: string;
@Input() label: string;
@Input() showCopy = true;
@Input() showLaunch = false;
constructor(private platformUtilsService: PlatformUtilsService) {}
copy(value: string) {
this.platformUtilsService.copyToClipboard(value);
}
launchUri(url: string) {
this.platformUtilsService.launchUri(url);
}
}

View File

@@ -0,0 +1,33 @@
<div class="form-group">
<label [attr.for]="controlId">
{{ label }}
<small *ngIf="isRequired" class="text-muted form-text d-inline"
>({{ "required" | i18n }})</small
>
</label>
<input
[formControl]="internalControl"
class="form-control"
[attr.id]="controlId"
[attr.aria-describedby]="describedById"
[attr.aria-invalid]="controlDir.control.invalid"
(blur)="onBlurInternal()"
/>
<div *ngIf="showDescribedBy" [attr.id]="describedById">
<small
*ngIf="helperText != null && !controlDir.control.hasError(helperTextSameAsError)"
class="form-text text-muted"
>
{{ helperText }}
</small>
<small class="error-inline" *ngIf="controlDir.control.hasError('required')" role="alert">
<i class="bwi bwi-exclamation-circle" aria-hidden="true"></i>
<span class="sr-only">{{ "error" | i18n }}:</span>
{{
controlDir.control.hasError(helperTextSameAsError)
? helperText
: ("fieldRequiredError" | i18n: label)
}}
</small>
</div>
</div>

View File

@@ -0,0 +1,48 @@
import { Component, Input, OnInit } from "@angular/core";
import { BaseCvaComponent } from "./base-cva.component";
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
@Component({
selector: "app-input-text[label][controlId]",
templateUrl: "input-text.component.html",
})
export class InputTextComponent extends BaseCvaComponent implements OnInit {
@Input() helperTextSameAsError: string;
@Input() requiredErrorMessage: string;
@Input() stripSpaces = false;
transformValue: (value: string) => string = null;
ngOnInit() {
super.ngOnInit();
if (this.stripSpaces) {
this.transformValue = this.doStripSpaces;
}
}
writeValue(value: string) {
this.internalControl.setValue(value == null ? "" : value);
}
protected onValueChangesInternal: any = (value: string) => {
let newValue = value;
if (this.transformValue != null) {
newValue = this.transformValue(value);
this.internalControl.setValue(newValue, { emitEvent: false });
}
this.onChange(newValue);
};
protected onValueChangeInternal(value: string) {
let newValue = value;
if (this.transformValue != null) {
newValue = this.transformValue(value);
this.internalControl.setValue(newValue, { emitEvent: false });
}
}
private doStripSpaces(value: string) {
return value.replace(/ /g, "");
}
}

View File

@@ -0,0 +1,19 @@
<div class="form-group">
<label [attr.for]="controlId">
{{ label }}
<small *ngIf="isRequired" class="text-muted form-text d-inline"
>({{ "required" | i18n }})</small
>
</label>
<select
class="form-control"
[attr.id]="controlId"
[attr.aria-invalid]="controlDir.control.invalid"
[formControl]="internalControl"
(blur)="onBlurInternal()"
>
<option *ngFor="let o of selectOptions" [ngValue]="o.value" disabled="{{ o.disabled }}">
{{ o.name }}
</option>
</select>
</div>

View File

@@ -0,0 +1,14 @@
import { Component, Input } from "@angular/core";
import { SelectOptions } from "jslib-angular/interfaces/selectOptions";
import { BaseCvaComponent } from "./base-cva.component";
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
@Component({
selector: "app-select",
templateUrl: "select.component.html",
})
export class SelectComponent extends BaseCvaComponent {
@Input() selectOptions: SelectOptions[];
}

View File

@@ -3,17 +3,20 @@
</div> </div>
<ng-container *ngIf="loading"> <ng-container *ngIf="loading">
<i class="fa fa-spinner fa-spin text-muted" title="{{ 'loading' | i18n }}" aria-hidden="true"></i> <i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span> <span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container> </ng-container>
<form <form
#form #form
(ngSubmit)="submit()" (ngSubmit)="submit()"
[formGroup]="data" [formGroup]="ssoConfigForm"
[appApiAction]="formPromise" [appApiAction]="formPromise"
*ngIf="!loading" *ngIf="!loading"
ngNativeValidate
> >
<p> <p>
{{ "ssoPolicyHelpStart" | i18n }} {{ "ssoPolicyHelpStart" | i18n }}
@@ -23,19 +26,14 @@
{{ "ssoPolicyHelpKeyConnector" | i18n }} {{ "ssoPolicyHelpKeyConnector" | i18n }}
</p> </p>
<div class="form-group"> <!-- Root form -->
<div class="form-check"> <ng-container>
<input <app-input-checkbox
class="form-check-input" controlId="enabled"
type="checkbox"
id="enabled"
[formControl]="enabled" [formControl]="enabled"
name="Enabled" [label]="'allowSso' | i18n"
/> [helperText]="'allowSsoDesc' | i18n"
<label class="form-check-label" for="enabled">{{ "allowSso" | i18n }}</label> ></app-input-checkbox>
</div>
<small class="form-text text-muted">{{ "allowSsoDesc" | i18n }}</small>
</div>
<div class="form-group"> <div class="form-group">
<label>{{ "memberDecryptionOption" | i18n }}</label> <label>{{ "memberDecryptionOption" | i18n }}</label>
@@ -67,28 +65,35 @@
target="_blank" target="_blank"
rel="noopener" rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}" appA11yTitle="{{ 'learnMore' | i18n }}"
href="https://bitwarden.com/help/article/about-key-connector/" href="https://bitwarden.com/help/about-key-connector/"
> >
<i class="fa fa-question-circle-o" aria-hidden="true"></i> <i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a> </a>
<small>{{ "memberDecryptionKeyConnectorDesc" | i18n }}</small> <small>{{ "memberDecryptionKeyConnectorDesc" | i18n }}</small>
</label> </label>
</div> </div>
</div> </div>
<ng-container *ngIf="data.value.keyConnectorEnabled"> <!-- Key Connector -->
<ng-container *ngIf="ssoConfigForm.get('keyConnectorEnabled').value">
<app-callout type="warning" [useAlertRole]="true"> <app-callout type="warning" [useAlertRole]="true">
{{ "keyConnectorWarning" | i18n }} {{ "keyConnectorWarning" | i18n }}
</app-callout> </app-callout>
<div class="form-group"> <div class="form-group">
<label for="keyConnectorUrl">{{ "keyConnectorUrl" | i18n }}</label> <label for="keyConnectorUrl">
{{ "keyConnectorUrl" | i18n }}
<small class="text-muted form-text d-inline">({{ "required" | i18n }})</small>
</label>
<div class="input-group"> <div class="input-group">
<input <input
class="form-control" class="form-control"
formControlName="keyConnectorUrl" formControlName="keyConnectorUrl"
id="keyConnectorUrl" id="keyConnectorUrl"
required aria-describedby="keyConnectorUrlDesc"
(change)="haveTestedKeyConnector = false"
appInputStripSpaces
appA11yInvalid
/> />
<div class="input-group-append"> <div class="input-group-append">
<button <button
@@ -98,7 +103,7 @@
[disabled]="!enableTestKeyConnector" [disabled]="!enableTestKeyConnector"
> >
<i <i
class="fa fa-spinner fa-spin" class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
*ngIf="keyConnectorUrl.pending" *ngIf="keyConnectorUrl.pending"
@@ -109,380 +114,335 @@
</button> </button>
</div> </div>
</div> </div>
<ng-container *ngIf="keyConnectorUrl.pristine && !keyConnectorUrl.pending"> <div *ngIf="haveTestedKeyConnector" id="keyConnectorUrlDesc" aria-live="polite">
<div class="text-danger" *ngIf="keyConnectorUrl.hasError('invalidUrl')" role="alert"> <small
<i class="fa fa-exclamation-circle" aria-hidden="true"></i> class="error-inline"
*ngIf="keyConnectorUrl.hasError('invalidUrl'); else keyConnectorSuccess"
>
<i class="bwi bwi-exclamation-circle" aria-hidden="true"></i>
<span class="sr-only">{{ "error" | i18n }}:</span>
{{ "keyConnectorTestFail" | i18n }} {{ "keyConnectorTestFail" | i18n }}
</div> </small>
<div class="text-success" *ngIf="!keyConnectorUrl.hasError('invalidUrl')" role="alert"> <ng-template #keyConnectorSuccess>
<i class="fa fa-check-circle-o" aria-hidden="true"></i> <small class="text-success">
<i class="bwi bwi-check-circle" aria-hidden="true"></i>
{{ "keyConnectorTestSuccess" | i18n }} {{ "keyConnectorTestSuccess" | i18n }}
</small>
</ng-template>
</div> </div>
</ng-container>
</div> </div>
</ng-container> </ng-container>
<div class="form-group"> <app-select
<label for="type">{{ "type" | i18n }}</label> controlId="type"
<select class="form-control" id="type" formControlName="configType"> [label]="'type' | i18n"
<option [ngValue]="0" disabled>{{ "selectType" | i18n }}</option> [selectOptions]="ssoTypeOptions"
<option [ngValue]="1">OpenID Connect</option> formControlName="configType"
<option [ngValue]="2">SAML 2.0</option> >
</select> </app-select>
</div> </ng-container>
<!-- OIDC --> <!-- OIDC -->
<div *ngIf="data.value.configType == 1"> <div
*ngIf="ssoConfigForm.get('configType').value === ssoType.OpenIdConnect"
[formGroup]="openIdForm"
>
<div class="config-section"> <div class="config-section">
<h2>{{ "openIdConnectConfig" | i18n }}</h2> <h2 class="secondary-header">{{ "openIdConnectConfig" | i18n }}</h2>
<div class="form-group">
<label>{{ "callbackPath" | i18n }}</label> <app-input-text-readonly
<div class="input-group"> [label]="'callbackPath' | i18n"
<input class="form-control" readonly [value]="callbackPath" /> [controlValue]="callbackPath"
<div class="input-group-append"> ></app-input-text-readonly>
<button
type="button" <app-input-text-readonly
class="btn btn-outline-secondary" [label]="'signedOutCallbackPath' | i18n"
appA11yTitle="{{ 'copyValue' | i18n }}" [controlValue]="signedOutCallbackPath"
(click)="copy(callbackPath)" ></app-input-text-readonly>
<app-input-text
[label]="'authority' | i18n"
controlId="authority"
[stripSpaces]="true"
formControlName="authority"
></app-input-text>
<app-input-text
[label]="'clientId' | i18n"
controlId="clientId"
[stripSpaces]="true"
formControlName="clientId"
></app-input-text>
<app-input-text
[label]="'clientSecret' | i18n"
controlId="clientSecret"
[stripSpaces]="true"
formControlName="clientSecret"
></app-input-text>
<app-input-text
[label]="'metadataAddress' | i18n"
controlId="metadataAddress"
[stripSpaces]="true"
[helperText]="'openIdAuthorityRequired' | i18n"
formControlName="metadataAddress"
></app-input-text>
<app-select
controlId="redirectBehavior"
[label]="'oidcRedirectBehavior' | i18n"
[selectOptions]="connectRedirectOptions"
formControlName="redirectBehavior"
> >
<i class="fa fa-lg fa-clone" aria-hidden="true"></i> </app-select>
</button>
</div> <app-input-checkbox
</div> controlId="getClaimsFromUserInfoEndpoint"
</div>
<div class="form-group">
<label>{{ "signedOutCallbackPath" | i18n }}</label>
<div class="input-group">
<input class="form-control" readonly [value]="signedOutCallbackPath" />
<div class="input-group-append">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyValue' | i18n }}"
(click)="copy(signedOutCallbackPath)"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="form-group">
<label for="authority">{{ "authority" | i18n }}</label>
<input class="form-control" formControlName="authority" id="authority" />
</div>
<div class="form-group">
<label for="clientId">{{ "clientId" | i18n }}</label>
<input class="form-control" formControlName="clientId" id="clientId" />
</div>
<div class="form-group">
<label for="clientSecret">{{ "clientSecret" | i18n }}</label>
<input class="form-control" formControlName="clientSecret" id="clientSecret" />
</div>
<div class="form-group">
<label for="metadataAddress">{{ "metadataAddress" | i18n }}</label>
<input class="form-control" formControlName="metadataAddress" id="metadataAddress" />
</div>
<div class="form-group">
<label for="redirectBehavior">{{ "oidcRedirectBehavior" | i18n }}</label>
<select class="form-control" formControlName="redirectBehavior" id="redirectBehavior">
<option [ngValue]="0">Redirect GET</option>
<option [ngValue]="1">Form POST</option>
</select>
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="getClaimsFromUserInfoEndpoint"
formControlName="getClaimsFromUserInfoEndpoint" formControlName="getClaimsFromUserInfoEndpoint"
/> [label]="'getClaimsFromUserInfoEndpoint' | i18n"
<label class="form-check-label" for="getClaimsFromUserInfoEndpoint"> ></app-input-checkbox>
{{ "getClaimsFromUserInfoEndpoint" | i18n }}
</label> <!-- Optional customizations -->
<div
class="section-header d-flex flex-row align-items-center mt-3 mb-3"
(click)="toggleOpenIdCustomizations()"
>
<h3 class="mb-0 mr-2" id="customizations-header">
{{ "openIdOptionalCustomizations" | i18n }}
</h3>
<button
class="mb-1 btn btn-link"
type="button"
appStopClick
role="button"
aria-controls="customizations"
[attr.aria-expanded]="showOpenIdCustomizations"
aria-labelledby="customizations-header"
>
<i
class="bwi"
aria-hidden="true"
[ngClass]="{
'bwi-angle-down': !showOpenIdCustomizations,
'bwi-chevron-up': showOpenIdCustomizations
}"
></i>
</button>
</div> </div>
</div> <div id="customizations" [hidden]="!showOpenIdCustomizations">
<div class="form-group"> <app-input-text
<label for="additionalScopes">{{ "additionalScopes" | i18n }}</label> [label]="'additionalScopes' | i18n"
<input class="form-control" formControlName="additionalScopes" id="additionalScopes" /> controlId="additionalScopes"
</div> [helperText]="'separateMultipleWithComma' | i18n"
<div class="form-group"> formControlName="additionalScopes"
<label for="additionalUserIdClaimTypes">{{ "additionalUserIdClaimTypes" | i18n }}</label> ></app-input-text>
<input
class="form-control" <app-input-text
[label]="'additionalUserIdClaimTypes' | i18n"
controlId="additionalUserIdClaimTypes"
[helperText]="'separateMultipleWithComma' | i18n"
formControlName="additionalUserIdClaimTypes" formControlName="additionalUserIdClaimTypes"
id="additionalUserIdClaimTypes" ></app-input-text>
/>
</div> <app-input-text
<div class="form-group"> [label]="'additionalEmailClaimTypes' | i18n"
<label for="additionalEmailClaimTypes">{{ "additionalEmailClaimTypes" | i18n }}</label> controlId="additionalEmailClaimTypes"
<input [helperText]="'separateMultipleWithComma' | i18n"
class="form-control"
formControlName="additionalEmailClaimTypes" formControlName="additionalEmailClaimTypes"
id="additionalEmailClaimTypes" ></app-input-text>
/>
</div> <app-input-text
<div class="form-group"> [label]="'additionalNameClaimTypes' | i18n"
<label for="additionalNameClaimTypes">{{ "additionalNameClaimTypes" | i18n }}</label> controlId="additionalNameClaimTypes"
<input [helperText]="'separateMultipleWithComma' | i18n"
class="form-control"
formControlName="additionalNameClaimTypes" formControlName="additionalNameClaimTypes"
id="additionalNameClaimTypes" ></app-input-text>
/>
</div> <app-input-text
<div class="form-group"> [label]="'acrValues' | i18n"
<label for="acrValues">{{ "acrValues" | i18n }}</label> controlId="acrValues"
<input class="form-control" formControlName="acrValues" id="acrValues" /> helperText="acr_values"
</div> formControlName="acrValues"
<div class="form-group"> ></app-input-text>
<label for="expectedReturnAcrValue">{{ "expectedReturnAcrValue" | i18n }}</label>
<input <app-input-text
class="form-control" [label]="'expectedReturnAcrValue' | i18n"
controlId="expectedReturnAcrValue"
helperText="acr_validation"
formControlName="expectedReturnAcrValue" formControlName="expectedReturnAcrValue"
id="expectedReturnAcrValue" ></app-input-text>
/>
</div> </div>
</div> </div>
</div> </div>
<div *ngIf="data.value.configType == 2"> <!-- SAML2 SP -->
<div *ngIf="ssoConfigForm.get('configType').value === ssoType.Saml2" [formGroup]="samlForm">
<!-- SAML2 SP --> <!-- SAML2 SP -->
<div class="config-section"> <div class="config-section">
<h2>{{ "samlSpConfig" | i18n }}</h2> <h2 class="secondary-header">{{ "samlSpConfig" | i18n }}</h2>
<div class="form-group">
<label>{{ "spEntityId" | i18n }}</label> <app-input-text-readonly
<div class="input-group"> [label]="'spEntityId' | i18n"
<input class="form-control" readonly [value]="spEntityId" /> [controlValue]="spEntityId"
<div class="input-group-append"> ></app-input-text-readonly>
<button
type="button" <app-input-text-readonly
class="btn btn-outline-secondary" [label]="'spMetadataUrl' | i18n"
appA11yTitle="{{ 'copyValue' | i18n }}" [controlValue]="spMetadataUrl"
(click)="copy(spEntityId)" [showLaunch]="true"
></app-input-text-readonly>
<app-input-text-readonly
[label]="'spAcsUrl' | i18n"
[controlValue]="spAcsUrl"
></app-input-text-readonly>
<app-select
controlId="spNameIdFormat"
[label]="'spNameIdFormat' | i18n"
[selectOptions]="saml2NameIdFormatOptions"
formControlName="spNameIdFormat"
> >
<i class="fa fa-lg fa-clone" aria-hidden="true"></i> </app-select>
</button>
</div> <app-select
</div> controlId="spOutboundSigningAlgorithm"
</div> [label]="'spOutboundSigningAlgorithm' | i18n"
<div class="form-group"> [selectOptions]="samlSigningAlgorithmOptions"
<label>{{ "spMetadataUrl" | i18n }}</label>
<div class="input-group">
<input class="form-control" readonly [value]="spMetadataUrl" />
<div class="input-group-append">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'launch' | i18n }}"
(click)="launchUri(spMetadataUrl)"
>
<i class="fa fa-lg fa-external-link" aria-hidden="true"></i>
</button>
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyValue' | i18n }}"
(click)="copy(spMetadataUrl)"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="form-group">
<label>{{ "spAcsUrl" | i18n }}</label>
<div class="input-group">
<input class="form-control" readonly [value]="spAcsUrl" />
<div class="input-group-append">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyValue' | i18n }}"
(click)="copy(spAcsUrl)"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="form-group">
<label for="spNameIdFormat">{{ "spNameIdFormat" | i18n }}</label>
<select class="form-control" formControlName="spNameIdFormat" id="spNameIdFormat">
<option value="0">Not Configured</option>
<option value="1">Unspecified</option>
<option value="2">Email Address</option>
<option value="3">X.509 Subject Name</option>
<option value="4">Windows Domain Qualified Name</option>
<option value="5">Kerberos Principal Name</option>
<option value="6">Entity Identifier</option>
<option value="7">Persistent</option>
<option value="8">Transient</option>
</select>
</div>
<div class="form-group">
<label for="spOutboundSigningAlgorithm">{{ "spOutboundSigningAlgorithm" | i18n }}</label>
<select
class="form-control"
formControlName="spOutboundSigningAlgorithm" formControlName="spOutboundSigningAlgorithm"
id="spOutboundSigningAlgorithm"
> >
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{ o }}</option> </app-select>
</select>
</div> <app-select
<div class="form-group"> controlId="spSigningBehavior"
<label for="spSigningBehavior">{{ "spSigningBehavior" | i18n }}</label> [label]="'spSigningBehavior' | i18n"
<select class="form-control" formControlName="spSigningBehavior" id="spSigningBehavior"> [selectOptions]="saml2SigningBehaviourOptions"
<option value="0">If IdP Wants Authn Requests Signed</option> formControlName="spSigningBehavior"
<option value="1">Always</option> >
<option value="3">Never</option> </app-select>
</select>
</div> <app-select
<div class="form-group"> controlId="spMinIncomingSigningAlgorithm"
<label for="spMinIncomingSigningAlgorithm">{{ [label]="'spMinIncomingSigningAlgorithm' | i18n"
"spMinIncomingSigningAlgorithm" | i18n [selectOptions]="samlSigningAlgorithmOptions"
}}</label>
<select
class="form-control"
formControlName="spMinIncomingSigningAlgorithm" formControlName="spMinIncomingSigningAlgorithm"
id="spMinIncomingSigningAlgorithm"
> >
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{ o }}</option> </app-select>
</select>
</div> <app-input-checkbox
<div class="form-group"> controlId="spWantAssertionsSigned"
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="spWantAssertionsSigned"
formControlName="spWantAssertionsSigned" formControlName="spWantAssertionsSigned"
/> [label]="'spWantAssertionsSigned' | i18n"
<label class="form-check-label" for="spWantAssertionsSigned"> ></app-input-checkbox>
{{ "spWantAssertionsSigned" | i18n }}
</label> <app-input-checkbox
</div> controlId="spValidateCertificates"
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="spValidateCertificates"
formControlName="spValidateCertificates" formControlName="spValidateCertificates"
/> [label]="'spValidateCertificates' | i18n"
<label class="form-check-label" for="spValidateCertificates"> ></app-input-checkbox>
{{ "spValidateCertificates" | i18n }}
</label>
</div>
</div>
</div> </div>
<!-- SAML2 IDP --> <!-- SAML2 IDP -->
<div class="config-section"> <div class="config-section">
<h2>{{ "samlIdpConfig" | i18n }}</h2> <h2 class="secondary-header">{{ "samlIdpConfig" | i18n }}</h2>
<app-input-text
[label]="'idpEntityId' | i18n"
controlId="idpEntityId"
formControlName="idpEntityId"
></app-input-text>
<app-select
controlId="idpBindingType"
[label]="'idpBindingType' | i18n"
[selectOptions]="saml2BindingTypeOptions"
formControlName="idpBindingType"
>
</app-select>
<app-input-text
[label]="'idpSingleSignOnServiceUrl' | i18n"
controlId="idpSingleSignOnServiceUrl"
[helperText]="'idpSingleSignOnServiceUrlRequired' | i18n"
[stripSpaces]="true"
formControlName="idpSingleSignOnServiceUrl"
></app-input-text>
<app-input-text
[label]="'idpSingleLogoutServiceUrl' | i18n"
controlId="idpSingleLogoutServiceUrl"
[stripSpaces]="true"
formControlName="idpSingleLogoutServiceUrl"
></app-input-text>
<div class="form-group"> <div class="form-group">
<label for="idpEntityId">{{ "idpEntityId" | i18n }}</label> <label for="idpX509PublicCert">
<input class="form-control" formControlName="idpEntityId" id="idpEntityId" /> {{ "idpX509PublicCert" | i18n }}
</div> <small class="text-muted form-text d-inline">({{ "required" | i18n }})</small>
<div class="form-group"> </label>
<label for="idpBindingType">{{ "idpBindingType" | i18n }}</label>
<select class="form-control" formControlName="idpBindingType" id="idpBindingType">
<option value="1">Redirect</option>
<option value="2">HTTP POST</option>
<option value="4">Artifact</option>
</select>
</div>
<div class="form-group">
<label for="idpSingleSignOnServiceUrl">{{ "idpSingleSignOnServiceUrl" | i18n }}</label>
<input
class="form-control"
formControlName="idpSingleSignOnServiceUrl"
id="idpSingleSignOnServiceUrl"
/>
</div>
<div class="form-group">
<label for="idpSingleLogoutServiceUrl">{{ "idpSingleLogoutServiceUrl" | i18n }}</label>
<input
class="form-control"
formControlName="idpSingleLogoutServiceUrl"
id="idpSingleLogoutServiceUrl"
/>
</div>
<div class="form-group">
<label for="idpArtifactResolutionServiceUrl">{{
"idpArtifactResolutionServiceUrl" | i18n
}}</label>
<input
class="form-control"
formControlName="idpArtifactResolutionServiceUrl"
id="idpArtifactResolutionServiceUrl"
/>
</div>
<div class="form-group">
<label for="idpX509PublicCert">{{ "idpX509PublicCert" | i18n }}</label>
<textarea <textarea
formControlName="idpX509PublicCert" formControlName="idpX509PublicCert"
class="form-control form-control-sm text-monospace" class="form-control form-control-sm text-monospace"
rows="6" rows="6"
id="idpX509PublicCert" id="idpX509PublicCert"
appA11yInvalid
aria-describedby="idpX509PublicCertDesc"
></textarea> ></textarea>
</div> <small
<div class="form-group"> id="idpX509PublicCertDesc"
<label for="idpOutboundSigningAlgorithm">{{ "idpOutboundSigningAlgorithm" | i18n }}</label> class="error-inline"
<select role="alert"
class="form-control" *ngIf="samlForm.get('idpX509PublicCert').hasError('required')"
formControlName="idpOutboundSigningAlgorithm"
id="idpOutboundSigningAlgorithm"
> >
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{ o }}</option> <i class="bwi bwi-exclamation-circle" aria-hidden="true"></i>
</select> <span class="sr-only">{{ "error" | i18n }}:</span>
{{ "fieldRequiredError" | i18n: ("idpX509PublicCert" | i18n) }}
</small>
</div> </div>
<div class="form-group">
<div class="form-check"> <app-select
<input controlId="idpOutboundSigningAlgorithm"
class="form-check-input" [label]="'idpOutboundSigningAlgorithm' | i18n"
type="checkbox" [selectOptions]="samlSigningAlgorithmOptions"
id="idpAllowUnsolicitedAuthnResponse" formControlName="idpOutboundSigningAlgorithm"
>
</app-select>
<!--TODO: Uncomment once Unsolicited IdP Response is supported-->
<!-- <app-input-checkbox
controlId="idpAllowUnsolicitedAuthnResponse"
formControlName="idpAllowUnsolicitedAuthnResponse" formControlName="idpAllowUnsolicitedAuthnResponse"
/> [label]="'idpAllowUnsolicitedAuthnResponse' | i18n"
<label class="form-check-label" for="idpAllowUnsolicitedAuthnResponse"> ></app-input-checkbox> -->
{{ "idpAllowUnsolicitedAuthnResponse" | i18n }}
</label> <app-input-checkbox
</div> controlId="idpAllowOutboundLogoutRequests"
</div> formControlName="idpAllowOutboundLogoutRequests"
<div class="form-group"> [label]="'idpAllowOutboundLogoutRequests' | i18n"
<div class="form-check"> ></app-input-checkbox>
<input
class="form-check-input" <app-input-checkbox
type="checkbox" controlId="idpWantAuthnRequestsSigned"
id="idpDisableOutboundLogoutRequests"
formControlName="idpDisableOutboundLogoutRequests"
/>
<label class="form-check-label" for="idpDisableOutboundLogoutRequests">
{{ "idpDisableOutboundLogoutRequests" | i18n }}
</label>
</div>
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="idpWantAuthnRequestsSigned"
formControlName="idpWantAuthnRequestsSigned" formControlName="idpWantAuthnRequestsSigned"
/> [label]="'idpSignAuthenticationRequests' | i18n"
<label class="form-check-label" for="idpWantAuthnRequestsSigned"> ></app-input-checkbox>
{{ "idpWantAuthnRequestsSigned" | i18n }}
</label>
</div>
</div>
</div> </div>
</div> </div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading"> <button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i> <i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "save" | i18n }}</span> <span>{{ "save" | i18n }}</span>
</button> </button>
<div
id="errorSummary"
class="error-summary text-danger"
*ngIf="this.getErrorCount(ssoConfigForm) as errorCount"
>
<i class="bwi bwi-exclamation-circle" aria-hidden="true"></i>
<span class="sr-only">{{ "error" | i18n }}:</span>
{{
(errorCount === 1 ? "formErrorSummarySingle" : "formErrorSummaryPlural") | i18n: errorCount
}}
</div>
</form> </form>

View File

@@ -1,29 +1,82 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { FormBuilder } from "@angular/forms"; import { AbstractControl, FormBuilder, FormGroup } from "@angular/forms";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
import { SelectOptions } from "jslib-angular/interfaces/selectOptions";
import { dirtyRequired } from "jslib-angular/validators/dirty.validator";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service"; import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import {
OpenIdConnectRedirectBehavior,
Saml2BindingType,
Saml2NameIdFormat,
Saml2SigningBehavior,
SsoType,
} from "jslib-common/enums/ssoEnums";
import { Utils } from "jslib-common/misc/utils";
import { SsoConfigApi } from "jslib-common/models/api/ssoConfigApi";
import { Organization } from "jslib-common/models/domain/organization"; import { Organization } from "jslib-common/models/domain/organization";
import { OrganizationSsoRequest } from "jslib-common/models/request/organization/organizationSsoRequest"; import { OrganizationSsoRequest } from "jslib-common/models/request/organization/organizationSsoRequest";
import { OrganizationSsoResponse } from "jslib-common/models/response/organization/organizationSsoResponse";
import { SsoConfigView } from "jslib-common/models/view/ssoConfigView";
const defaultSigningAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
@Component({ @Component({
selector: "app-org-manage-sso", selector: "app-org-manage-sso",
templateUrl: "sso.component.html", templateUrl: "sso.component.html",
}) })
export class SsoComponent implements OnInit { export class SsoComponent implements OnInit {
samlSigningAlgorithms = [ readonly ssoType = SsoType;
readonly ssoTypeOptions: SelectOptions[] = [
{ name: this.i18nService.t("selectType"), value: SsoType.None, disabled: true },
{ name: "OpenID Connect", value: SsoType.OpenIdConnect },
{ name: "SAML 2.0", value: SsoType.Saml2 },
];
readonly samlSigningAlgorithms = [
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
"http://www.w3.org/2000/09/xmldsig#rsa-sha384", "http://www.w3.org/2000/09/xmldsig#rsa-sha384",
"http://www.w3.org/2000/09/xmldsig#rsa-sha512", "http://www.w3.org/2000/09/xmldsig#rsa-sha512",
"http://www.w3.org/2000/09/xmldsig#rsa-sha1", "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
]; ];
readonly saml2SigningBehaviourOptions: SelectOptions[] = [
{
name: "If IdP Wants Authn Requests Signed",
value: Saml2SigningBehavior.IfIdpWantAuthnRequestsSigned,
},
{ name: "Always", value: Saml2SigningBehavior.Always },
{ name: "Never", value: Saml2SigningBehavior.Never },
];
readonly saml2BindingTypeOptions: SelectOptions[] = [
{ name: "Redirect", value: Saml2BindingType.HttpRedirect },
{ name: "HTTP POST", value: Saml2BindingType.HttpPost },
];
readonly saml2NameIdFormatOptions: SelectOptions[] = [
{ name: "Not Configured", value: Saml2NameIdFormat.NotConfigured },
{ name: "Unspecified", value: Saml2NameIdFormat.Unspecified },
{ name: "Email Address", value: Saml2NameIdFormat.EmailAddress },
{ name: "X.509 Subject Name", value: Saml2NameIdFormat.X509SubjectName },
{ name: "Windows Domain Qualified Name", value: Saml2NameIdFormat.WindowsDomainQualifiedName },
{ name: "Kerberos Principal Name", value: Saml2NameIdFormat.KerberosPrincipalName },
{ name: "Entity Identifier", value: Saml2NameIdFormat.EntityIdentifier },
{ name: "Persistent", value: Saml2NameIdFormat.Persistent },
{ name: "Transient", value: Saml2NameIdFormat.Transient },
];
readonly connectRedirectOptions: SelectOptions[] = [
{ name: "Redirect GET", value: OpenIdConnectRedirectBehavior.RedirectGet },
{ name: "Form POST", value: OpenIdConnectRedirectBehavior.FormPost },
];
showOpenIdCustomizations = false;
loading = true; loading = true;
haveTestedKeyConnector = false;
organizationId: string; organizationId: string;
organization: Organization; organization: Organization;
formPromise: Promise<any>; formPromise: Promise<any>;
@@ -35,18 +88,14 @@ export class SsoComponent implements OnInit {
spAcsUrl: string; spAcsUrl: string;
enabled = this.formBuilder.control(false); enabled = this.formBuilder.control(false);
data = this.formBuilder.group({
configType: [],
keyConnectorEnabled: [], openIdForm = this.formBuilder.group(
keyConnectorUrl: [], {
authority: ["", dirtyRequired],
// OpenId clientId: ["", dirtyRequired],
authority: [], clientSecret: ["", dirtyRequired],
clientId: [],
clientSecret: [],
metadataAddress: [], metadataAddress: [],
redirectBehavior: [], redirectBehavior: [OpenIdConnectRedirectBehavior.RedirectGet, dirtyRequired],
getClaimsFromUserInfoEndpoint: [], getClaimsFromUserInfoEndpoint: [],
additionalScopes: [], additionalScopes: [],
additionalUserIdClaimTypes: [], additionalUserIdClaimTypes: [],
@@ -54,25 +103,42 @@ export class SsoComponent implements OnInit {
additionalNameClaimTypes: [], additionalNameClaimTypes: [],
acrValues: [], acrValues: [],
expectedReturnAcrValue: [], expectedReturnAcrValue: [],
},
{
updateOn: "blur",
}
);
// SAML samlForm = this.formBuilder.group(
spNameIdFormat: [], {
spOutboundSigningAlgorithm: [], spNameIdFormat: [Saml2NameIdFormat.NotConfigured],
spSigningBehavior: [], spOutboundSigningAlgorithm: [defaultSigningAlgorithm],
spMinIncomingSigningAlgorithm: [], spSigningBehavior: [Saml2SigningBehavior.IfIdpWantAuthnRequestsSigned],
spMinIncomingSigningAlgorithm: [defaultSigningAlgorithm],
spWantAssertionsSigned: [], spWantAssertionsSigned: [],
spValidateCertificates: [], spValidateCertificates: [],
idpEntityId: [], idpEntityId: ["", dirtyRequired],
idpBindingType: [], idpBindingType: [Saml2BindingType.HttpRedirect],
idpSingleSignOnServiceUrl: [], idpSingleSignOnServiceUrl: [],
idpSingleLogoutServiceUrl: [], idpSingleLogoutServiceUrl: [],
idpArtifactResolutionServiceUrl: [], idpX509PublicCert: ["", dirtyRequired],
idpX509PublicCert: [], idpOutboundSigningAlgorithm: [defaultSigningAlgorithm],
idpOutboundSigningAlgorithm: [],
idpAllowUnsolicitedAuthnResponse: [], idpAllowUnsolicitedAuthnResponse: [],
idpDisableOutboundLogoutRequests: [], idpAllowOutboundLogoutRequests: [true],
idpWantAuthnRequestsSigned: [], idpWantAuthnRequestsSigned: [],
},
{
updateOn: "blur",
}
);
ssoConfigForm = this.formBuilder.group({
configType: [SsoType.None],
keyConnectorEnabled: [false],
keyConnectorUrl: [""],
openId: this.openIdForm,
saml: this.samlForm,
}); });
constructor( constructor(
@@ -85,6 +151,25 @@ export class SsoComponent implements OnInit {
) {} ) {}
async ngOnInit() { async ngOnInit() {
this.ssoConfigForm.get("configType").valueChanges.subscribe((newType: SsoType) => {
if (newType === SsoType.OpenIdConnect) {
this.openIdForm.enable();
this.samlForm.disable();
} else if (newType === SsoType.Saml2) {
this.openIdForm.disable();
this.samlForm.enable();
} else {
this.openIdForm.disable();
this.samlForm.disable();
}
});
this.samlForm
.get("spSigningBehavior")
.valueChanges.subscribe(() =>
this.samlForm.get("idpX509PublicCert").updateValueAndValidity()
);
this.route.parent.parent.params.subscribe(async (params) => { this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId; this.organizationId = params.organizationId;
await this.load(); await this.load();
@@ -94,9 +179,7 @@ export class SsoComponent implements OnInit {
async load() { async load() {
this.organization = await this.organizationService.get(this.organizationId); this.organization = await this.organizationService.get(this.organizationId);
const ssoSettings = await this.apiService.getOrganizationSso(this.organizationId); const ssoSettings = await this.apiService.getOrganizationSso(this.organizationId);
this.populateForm(ssoSettings);
this.data.patchValue(ssoSettings.data);
this.enabled.setValue(ssoSettings.enabled);
this.callbackPath = ssoSettings.urls.callbackPath; this.callbackPath = ssoSettings.urls.callbackPath;
this.signedOutCallbackPath = ssoSettings.urls.signedOutCallbackPath; this.signedOutCallbackPath = ssoSettings.urls.signedOutCallbackPath;
@@ -104,28 +187,30 @@ export class SsoComponent implements OnInit {
this.spMetadataUrl = ssoSettings.urls.spMetadataUrl; this.spMetadataUrl = ssoSettings.urls.spMetadataUrl;
this.spAcsUrl = ssoSettings.urls.spAcsUrl; this.spAcsUrl = ssoSettings.urls.spAcsUrl;
this.keyConnectorUrl.markAsDirty();
this.loading = false; this.loading = false;
} }
copy(value: string) {
this.platformUtilsService.copyToClipboard(value);
}
launchUri(url: string) {
this.platformUtilsService.launchUri(url);
}
async submit() { async submit() {
this.formPromise = this.postData(); this.validateForm(this.ssoConfigForm);
if (this.ssoConfigForm.get("keyConnectorEnabled").value) {
await this.validateKeyConnectorUrl();
}
if (!this.ssoConfigForm.valid) {
this.readOutErrors();
return;
}
const request = new OrganizationSsoRequest();
request.enabled = this.enabled.value;
request.data = SsoConfigApi.fromView(this.ssoConfigForm.value as SsoConfigView);
this.formPromise = this.apiService.postOrganizationSso(this.organizationId, request);
try { try {
const response = await this.formPromise; const response = await this.formPromise;
this.populateForm(response);
this.data.patchValue(response.data);
this.enabled.setValue(response.enabled);
this.platformUtilsService.showToast("success", null, this.i18nService.t("ssoSettingsSaved")); this.platformUtilsService.showToast("success", null, this.i18nService.t("ssoSettingsSaved"));
} catch { } catch {
// Logged by appApiAction, do nothing // Logged by appApiAction, do nothing
@@ -134,24 +219,8 @@ export class SsoComponent implements OnInit {
this.formPromise = null; this.formPromise = null;
} }
async postData() {
if (this.data.get("keyConnectorEnabled").value) {
await this.validateKeyConnectorUrl();
if (this.keyConnectorUrl.hasError("invalidUrl")) {
throw new Error(this.i18nService.t("keyConnectorTestFail"));
}
}
const request = new OrganizationSsoRequest();
request.enabled = this.enabled.value;
request.data = this.data.value;
return this.apiService.postOrganizationSso(this.organizationId, request);
}
async validateKeyConnectorUrl() { async validateKeyConnectorUrl() {
if (this.keyConnectorUrl.pristine) { if (this.haveTestedKeyConnector) {
return; return;
} }
@@ -166,18 +235,84 @@ export class SsoComponent implements OnInit {
}); });
} }
this.keyConnectorUrl.markAsPristine(); this.haveTestedKeyConnector = true;
}
toggleOpenIdCustomizations() {
this.showOpenIdCustomizations = !this.showOpenIdCustomizations;
}
getErrorCount(form: FormGroup): number {
return Object.values(form.controls).reduce((acc: number, control: AbstractControl) => {
if (control instanceof FormGroup) {
return acc + this.getErrorCount(control);
}
if (control.errors == null) {
return acc;
}
return acc + Object.keys(control.errors).length;
}, 0);
} }
get enableTestKeyConnector() { get enableTestKeyConnector() {
return ( return (
this.data.get("keyConnectorEnabled").value && this.ssoConfigForm.get("keyConnectorEnabled").value &&
this.keyConnectorUrl != null && !Utils.isNullOrWhitespace(this.keyConnectorUrl?.value)
this.keyConnectorUrl.value !== ""
); );
} }
get keyConnectorUrl() { get keyConnectorUrl() {
return this.data.get("keyConnectorUrl"); return this.ssoConfigForm.get("keyConnectorUrl");
}
get samlSigningAlgorithmOptions(): SelectOptions[] {
return this.samlSigningAlgorithms.map((algorithm) => ({ name: algorithm, value: algorithm }));
}
private validateForm(form: FormGroup) {
Object.values(form.controls).forEach((control: AbstractControl) => {
if (control.disabled) {
return;
}
if (control instanceof FormGroup) {
this.validateForm(control);
} else {
control.markAsDirty();
control.markAsTouched();
control.updateValueAndValidity();
}
});
}
private populateForm(ssoSettings: OrganizationSsoResponse) {
this.enabled.setValue(ssoSettings.enabled);
if (ssoSettings.data != null) {
const ssoConfigView = new SsoConfigView(ssoSettings.data);
this.ssoConfigForm.patchValue(ssoConfigView);
}
}
private readOutErrors() {
const errorText = this.i18nService.t("error");
const errorCount = this.getErrorCount(this.ssoConfigForm);
const errorCountText = this.i18nService.t(
errorCount === 1 ? "formErrorSummarySingle" : "formErrorSummaryPlural",
errorCount.toString()
);
const div = document.createElement("div");
div.className = "sr-only";
div.id = "srErrorCount";
div.setAttribute("aria-live", "polite");
div.innerText = errorText + ": " + errorCountText;
const existing = document.getElementById("srErrorCount");
if (existing != null) {
existing.remove();
}
document.body.append(div);
} }
} }

View File

@@ -2,7 +2,6 @@ import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router"; import { RouterModule, Routes } from "@angular/router";
import { AuthGuardService } from "jslib-angular/services/auth-guard.service"; import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { Permissions } from "jslib-common/enums/permissions"; import { Permissions } from "jslib-common/enums/permissions";
import { OrganizationLayoutComponent } from "src/app/layouts/organization-layout.component"; import { OrganizationLayoutComponent } from "src/app/layouts/organization-layout.component";

View File

@@ -4,11 +4,23 @@ import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { OssModule } from "src/app/oss.module"; import { OssModule } from "src/app/oss.module";
import { InputCheckboxComponent } from "./components/input-checkbox.component";
import { InputTextReadOnlyComponent } from "./components/input-text-readonly.component";
import { InputTextComponent } from "./components/input-text.component";
import { SelectComponent } from "./components/select.component";
import { SsoComponent } from "./manage/sso.component"; import { SsoComponent } from "./manage/sso.component";
import { OrganizationsRoutingModule } from "./organizations-routing.module"; import { OrganizationsRoutingModule } from "./organizations-routing.module";
// Form components are for use in the SSO Configuration Form only and should not be exported for use elsewhere.
// They will be deprecated by the Component Library.
@NgModule({ @NgModule({
imports: [CommonModule, FormsModule, ReactiveFormsModule, OssModule, OrganizationsRoutingModule], imports: [CommonModule, FormsModule, ReactiveFormsModule, OssModule, OrganizationsRoutingModule],
declarations: [SsoComponent], declarations: [
InputCheckboxComponent,
InputTextComponent,
InputTextReadOnlyComponent,
SelectComponent,
SsoComponent,
],
}) })
export class OrganizationsModule {} export class OrganizationsModule {}

View File

@@ -1,12 +1,7 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PolicyType } from "jslib-common/enums/policyType"; import { PolicyType } from "jslib-common/enums/policyType";
import { PolicyRequest } from "jslib-common/models/request/policyRequest";
import { import {
BasePolicy, BasePolicy,
BasePolicyComponent, BasePolicyComponent,

View File

@@ -2,9 +2,7 @@ import { Component } from "@angular/core";
import { FormBuilder } from "@angular/forms"; import { FormBuilder } from "@angular/forms";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PolicyType } from "jslib-common/enums/policyType"; import { PolicyType } from "jslib-common/enums/policyType";
import { PolicyRequest } from "jslib-common/models/request/policyRequest"; import { PolicyRequest } from "jslib-common/models/request/policyRequest";
import { import {

View File

@@ -16,7 +16,7 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="card-body text-center" *ngIf="loading"> <div class="card-body text-center" *ngIf="loading">
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i> <i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }} {{ "loading" | i18n }}
</div> </div>
<ng-container *ngIf="!loading"> <ng-container *ngIf="!loading">

View File

@@ -1,16 +1,14 @@
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { ValidationService } from "jslib-angular/services/validation.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ProviderService } from "jslib-common/abstractions/provider.service"; import { ProviderService } from "jslib-common/abstractions/provider.service";
import { ValidationService } from "jslib-angular/services/validation.service";
import { WebProviderService } from "../services/webProvider.service";
import { Organization } from "jslib-common/models/domain/organization"; import { Organization } from "jslib-common/models/domain/organization";
import { Provider } from "jslib-common/models/domain/provider"; import { Provider } from "jslib-common/models/domain/provider";
import { WebProviderService } from "../services/webProvider.service";
@Component({ @Component({
selector: "provider-add-organization", selector: "provider-add-organization",
templateUrl: "add-organization.component.html", templateUrl: "add-organization.component.html",

View File

@@ -13,7 +13,7 @@
/> />
</div> </div>
<a class="btn btn-sm btn-outline-primary ml-3" routerLink="create" *ngIf="manageOrganizations"> <a class="btn btn-sm btn-outline-primary ml-3" routerLink="create" *ngIf="manageOrganizations">
<i class="fa fa-plus fa-fw" aria-hidden="true"></i> <i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "newClientOrganization" | i18n }} {{ "newClientOrganization" | i18n }}
</a> </a>
<button <button
@@ -21,14 +21,18 @@
(click)="addExistingOrganization()" (click)="addExistingOrganization()"
*ngIf="manageOrganizations && showAddExisting" *ngIf="manageOrganizations && showAddExisting"
> >
<i class="fa fa-plus fa-fw" aria-hidden="true"></i> <i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "addExistingOrganization" | i18n }} {{ "addExistingOrganization" | i18n }}
</button> </button>
</div> </div>
</div> </div>
<ng-container *ngIf="loading"> <ng-container *ngIf="loading">
<i class="fa fa-spinner fa-spin text-muted" title="{{ 'loading' | i18n }}" aria-hidden="true"></i> <i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span> <span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container> </ng-container>
@@ -67,11 +71,11 @@
aria-expanded="false" aria-expanded="false"
appA11yTitle="{{ 'options' | i18n }}" appA11yTitle="{{ 'options' | i18n }}"
> >
<i class="fa fa-cog fa-lg" aria-hidden="true"></i> <i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
</button> </button>
<div class="dropdown-menu dropdown-menu-right"> <div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(o)"> <a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(o)">
<i class="fa fa-fw fa-remove" aria-hidden="true"></i> <i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
{{ "remove" | i18n }} {{ "remove" | i18n }}
</a> </a>
</div> </div>

View File

@@ -1,8 +1,9 @@
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
import { first } from "rxjs/operators"; import { first } from "rxjs/operators";
import { ModalService } from "jslib-angular/services/modal.service";
import { ValidationService } from "jslib-angular/services/validation.service";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service"; import { LogService } from "jslib-common/abstractions/log.service";
@@ -10,13 +11,8 @@ import { OrganizationService } from "jslib-common/abstractions/organization.serv
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ProviderService } from "jslib-common/abstractions/provider.service"; import { ProviderService } from "jslib-common/abstractions/provider.service";
import { SearchService } from "jslib-common/abstractions/search.service"; import { SearchService } from "jslib-common/abstractions/search.service";
import { ModalService } from "jslib-angular/services/modal.service";
import { ValidationService } from "jslib-angular/services/validation.service";
import { PlanType } from "jslib-common/enums/planType"; import { PlanType } from "jslib-common/enums/planType";
import { ProviderUserType } from "jslib-common/enums/providerUserType"; import { ProviderUserType } from "jslib-common/enums/providerUserType";
import { Organization } from "jslib-common/models/domain/organization"; import { Organization } from "jslib-common/models/domain/organization";
import { ProviderOrganizationOrganizationDetailsResponse } from "jslib-common/models/response/provider/providerOrganizationResponse"; import { ProviderOrganizationOrganizationDetailsResponse } from "jslib-common/models/response/provider/providerOrganizationResponse";

View File

@@ -3,7 +3,7 @@
<img class="mb-4 logo logo-themed" alt="Bitwarden" /> <img class="mb-4 logo logo-themed" alt="Bitwarden" />
<p class="text-center"> <p class="text-center">
<i <i
class="fa fa-spinner fa-spin fa-2x text-muted" class="bwi bwi-spinner bwi-spin bwi-2x text-muted"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>

View File

@@ -1,15 +1,14 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { BaseAcceptComponent } from "src/app/common/base.accept.component";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service"; import { StateService } from "jslib-common/abstractions/state.service";
import { ProviderUserAcceptRequest } from "jslib-common/models/request/provider/providerUserAcceptRequest"; import { ProviderUserAcceptRequest } from "jslib-common/models/request/provider/providerUserAcceptRequest";
import { BaseAcceptComponent } from "src/app/common/base.accept.component";
@Component({ @Component({
selector: "app-accept-provider", selector: "app-accept-provider",
templateUrl: "accept-provider.component.html", templateUrl: "accept-provider.component.html",

View File

@@ -1,10 +1,9 @@
import { Component, Input } from "@angular/core"; import { Component, Input } from "@angular/core";
import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType";
import { ProviderUserBulkConfirmRequest } from "jslib-common/models/request/provider/providerUserBulkConfirmRequest"; import { ProviderUserBulkConfirmRequest } from "jslib-common/models/request/provider/providerUserBulkConfirmRequest";
import { ProviderUserBulkRequest } from "jslib-common/models/request/provider/providerUserBulkRequest"; import { ProviderUserBulkRequest } from "jslib-common/models/request/provider/providerUserBulkRequest";
import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType";
import { BulkConfirmComponent as OrganizationBulkConfirmComponent } from "src/app/organizations/manage/bulk/bulk-confirm.component"; import { BulkConfirmComponent as OrganizationBulkConfirmComponent } from "src/app/organizations/manage/bulk/bulk-confirm.component";
import { BulkUserDetails } from "src/app/organizations/manage/bulk/bulk-status.component"; import { BulkUserDetails } from "src/app/organizations/manage/bulk/bulk-status.component";

View File

@@ -32,9 +32,9 @@
[disabled]="loaded && refreshForm.loading" [disabled]="loaded && refreshForm.loading"
> >
<i <i
class="fa fa-refresh fa-fw" class="bwi bwi-refresh bwi-fw"
aria-hidden="true" aria-hidden="true"
[ngClass]="{ 'fa-spin': loaded && refreshForm.loading }" [ngClass]="{ 'bwi-spin': loaded && refreshForm.loading }"
></i> ></i>
{{ "refresh" | i18n }} {{ "refresh" | i18n }}
</button> </button>
@@ -47,14 +47,18 @@
(click)="exportEvents()" (click)="exportEvents()"
[disabled]="(loaded && exportForm.loading) || dirtyDates" [disabled]="(loaded && exportForm.loading) || dirtyDates"
> >
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i> <i class="bwi bwi-spinner bwi-spin" aria-hidden="true"></i>
<span>{{ "export" | i18n }}</span> <span>{{ "export" | i18n }}</span>
</button> </button>
</form> </form>
</div> </div>
</div> </div>
<ng-container *ngIf="!loaded"> <ng-container *ngIf="!loaded">
<i class="fa fa-spinner fa-spin text-muted" title="{{ 'loading' | i18n }}" aria-hidden="true"></i> <i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span> <span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container> </ng-container>
<ng-container *ngIf="loaded"> <ng-container *ngIf="loaded">
@@ -75,7 +79,7 @@
<td>{{ e.date | date: "medium" }}</td> <td>{{ e.date | date: "medium" }}</td>
<td> <td>
<i <i
class="text-muted fa fa-lg {{ e.appIcon }}" class="text-muted bwi bwi-lg {{ e.appIcon }}"
title="{{ e.appName }}, {{ e.ip }}" title="{{ e.appName }}, {{ e.ip }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>
@@ -97,7 +101,7 @@
[disabled]="loaded && moreBtn.loading" [disabled]="loaded && moreBtn.loading"
*ngIf="continuationToken" *ngIf="continuationToken"
> >
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i> <i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "loadMore" | i18n }}</span> <span>{{ "loadMore" | i18n }}</span>
</button> </button>
</ng-container> </ng-container>

View File

@@ -1,27 +1,24 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
import { ExportService } from "jslib-common/abstractions/export.service"; import { ExportService } from "jslib-common/abstractions/export.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service"; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ProviderService } from "jslib-common/abstractions/provider.service"; import { ProviderService } from "jslib-common/abstractions/provider.service";
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
import { EventResponse } from "jslib-common/models/response/eventResponse"; import { EventResponse } from "jslib-common/models/response/eventResponse";
import { EventService } from "src/app/services/event.service";
import { BaseEventsComponent } from "src/app/common/base.events.component"; import { BaseEventsComponent } from "src/app/common/base.events.component";
import { EventService } from "src/app/services/event.service";
@Component({ @Component({
selector: "provider-events", selector: "provider-events",
templateUrl: "events.component.html", templateUrl: "events.component.html",
}) })
export class EventsComponent extends BaseEventsComponent implements OnInit { export class EventsComponent extends BaseEventsComponent implements OnInit {
exportFileName: string = "provider-events"; exportFileName = "provider-events";
providerId: string; providerId: string;
private providerUsersUserIdMap = new Map<string, any>(); private providerUsersUserIdMap = new Map<string, any>();

View File

@@ -2,7 +2,6 @@ import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
import { ProviderService } from "jslib-common/abstractions/provider.service"; import { ProviderService } from "jslib-common/abstractions/provider.service";
import { Provider } from "jslib-common/models/domain/provider"; import { Provider } from "jslib-common/models/domain/provider";
@Component({ @Component({

View File

@@ -52,11 +52,11 @@
aria-expanded="false" aria-expanded="false"
appA11yTitle="{{ 'options' | i18n }}" appA11yTitle="{{ 'options' | i18n }}"
> >
<i class="fa fa-cog" aria-hidden="true"></i> <i class="bwi bwi-cog" aria-hidden="true"></i>
</button> </button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="bulkActionsButton"> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="bulkActionsButton">
<button class="dropdown-item" appStopClick (click)="bulkReinvite()"> <button class="dropdown-item" appStopClick (click)="bulkReinvite()">
<i class="fa fa-fw fa-envelope-o" aria-hidden="true"></i> <i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
{{ "reinviteSelected" | i18n }} {{ "reinviteSelected" | i18n }}
</button> </button>
<button <button
@@ -65,32 +65,36 @@
(click)="bulkConfirm()" (click)="bulkConfirm()"
*ngIf="showBulkConfirmUsers" *ngIf="showBulkConfirmUsers"
> >
<i class="fa fa-fw fa-check" aria-hidden="true"></i> <i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
{{ "confirmSelected" | i18n }} {{ "confirmSelected" | i18n }}
</button> </button>
<button class="dropdown-item text-danger" appStopClick (click)="bulkRemove()"> <button class="dropdown-item text-danger" appStopClick (click)="bulkRemove()">
<i class="fa fa-fw fa-remove" aria-hidden="true"></i> <i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
{{ "remove" | i18n }} {{ "remove" | i18n }}
</button> </button>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<button class="dropdown-item" appStopClick (click)="selectAll(true)"> <button class="dropdown-item" appStopClick (click)="selectAll(true)">
<i class="fa fa-fw fa-check-square-o" aria-hidden="true"></i> <i class="bwi bwi-fw bwi-check-square" aria-hidden="true"></i>
{{ "selectAll" | i18n }} {{ "selectAll" | i18n }}
</button> </button>
<button class="dropdown-item" appStopClick (click)="selectAll(false)"> <button class="dropdown-item" appStopClick (click)="selectAll(false)">
<i class="fa fa-fw fa-minus-square-o" aria-hidden="true"></i> <i class="bwi bwi-fw bwi-minus-square" aria-hidden="true"></i>
{{ "unselectAll" | i18n }} {{ "unselectAll" | i18n }}
</button> </button>
</div> </div>
</div> </div>
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="invite()"> <button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="invite()">
<i class="fa fa-plus fa-fw" aria-hidden="true"></i> <i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "inviteUser" | i18n }} {{ "inviteUser" | i18n }}
</button> </button>
</div> </div>
</div> </div>
<ng-container *ngIf="loading"> <ng-container *ngIf="loading">
<i class="fa fa-spinner fa-spin text-muted" title="{{ 'loading' | i18n }}" aria-hidden="true"></i> <i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span> <span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container> </ng-container>
<ng-container <ng-container
@@ -104,7 +108,7 @@
<app-callout <app-callout
type="info" type="info"
title="{{ 'confirmUsers' | i18n }}" title="{{ 'confirmUsers' | i18n }}"
icon="fa-check-circle" icon="bwi bwi-check-circle"
*ngIf="showConfirmUsers" *ngIf="showConfirmUsers"
> >
{{ "providerUsersNeedConfirmed" | i18n }} {{ "providerUsersNeedConfirmed" | i18n }}
@@ -128,7 +132,8 @@
size="25" size="25"
[circle]="true" [circle]="true"
[fontSize]="14" [fontSize]="14"
></app-avatar> >
</app-avatar>
</td> </td>
<td> <td>
<a href="#" appStopClick (click)="edit(u)">{{ u.email }}</a> <a href="#" appStopClick (click)="edit(u)">{{ u.email }}</a>
@@ -142,7 +147,11 @@
</td> </td>
<td> <td>
<ng-container *ngIf="u.twoFactorEnabled"> <ng-container *ngIf="u.twoFactorEnabled">
<i class="fa fa-lock" title="{{ 'userUsingTwoStep' | i18n }}" aria-hidden="true"></i> <i
class="bwi bwi-lock"
title="{{ 'userUsingTwoStep' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "userUsingTwoStep" | i18n }}</span> <span class="sr-only">{{ "userUsingTwoStep" | i18n }}</span>
</ng-container> </ng-container>
</td> </td>
@@ -161,7 +170,7 @@
aria-expanded="false" aria-expanded="false"
appA11yTitle="{{ 'options' | i18n }}" appA11yTitle="{{ 'options' | i18n }}"
> >
<i class="fa fa-cog fa-lg" aria-hidden="true"></i> <i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
</button> </button>
<div class="dropdown-menu dropdown-menu-right"> <div class="dropdown-menu dropdown-menu-right">
<a <a
@@ -171,7 +180,7 @@
(click)="reinvite(u)" (click)="reinvite(u)"
*ngIf="u.status === userStatusType.Invited" *ngIf="u.status === userStatusType.Invited"
> >
<i class="fa fa-fw fa-envelope-o" aria-hidden="true"></i> <i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
{{ "resendInvitation" | i18n }} {{ "resendInvitation" | i18n }}
</a> </a>
<a <a
@@ -181,7 +190,7 @@
(click)="confirm(u)" (click)="confirm(u)"
*ngIf="u.status === userStatusType.Accepted" *ngIf="u.status === userStatusType.Accepted"
> >
<i class="fa fa-fw fa-check" aria-hidden="true"></i> <i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
{{ "confirm" | i18n }} {{ "confirm" | i18n }}
</a> </a>
<a <a
@@ -191,7 +200,7 @@
(click)="groups(u)" (click)="groups(u)"
*ngIf="accessGroups" *ngIf="accessGroups"
> >
<i class="fa fa-fw fa-sitemap" aria-hidden="true"></i> <i class="bwi bwi-fw bwi-sitemap" aria-hidden="true"></i>
{{ "groups" | i18n }} {{ "groups" | i18n }}
</a> </a>
<a <a
@@ -201,11 +210,11 @@
(click)="events(u)" (click)="events(u)"
*ngIf="accessEvents && u.status === userStatusType.Confirmed" *ngIf="accessEvents && u.status === userStatusType.Confirmed"
> >
<i class="fa fa-fw fa-file-text-o" aria-hidden="true"></i> <i class="bwi bwi-fw bwi-file-text" aria-hidden="true"></i>
{{ "eventLogs" | i18n }} {{ "eventLogs" | i18n }}
</a> </a>
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(u)"> <a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(u)">
<i class="fa fa-fw fa-remove" aria-hidden="true"></i> <i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
{{ "remove" | i18n }} {{ "remove" | i18n }}
</a> </a>
</div> </div>

View File

@@ -1,8 +1,11 @@
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators"; import { first } from "rxjs/operators";
import { SearchPipe } from "jslib-angular/pipes/search.pipe";
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
import { ModalService } from "jslib-angular/services/modal.service";
import { ValidationService } from "jslib-angular/services/validation.service";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service"; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
@@ -11,26 +14,18 @@ import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.se
import { ProviderService } from "jslib-common/abstractions/provider.service"; import { ProviderService } from "jslib-common/abstractions/provider.service";
import { SearchService } from "jslib-common/abstractions/search.service"; import { SearchService } from "jslib-common/abstractions/search.service";
import { StateService } from "jslib-common/abstractions/state.service"; import { StateService } from "jslib-common/abstractions/state.service";
import { ModalService } from "jslib-angular/services/modal.service";
import { ValidationService } from "jslib-angular/services/validation.service";
import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType"; import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType";
import { ProviderUserType } from "jslib-common/enums/providerUserType"; import { ProviderUserType } from "jslib-common/enums/providerUserType";
import { SearchPipe } from "jslib-angular/pipes/search.pipe";
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
import { ListResponse } from "jslib-common/models/response/listResponse";
import { ProviderUserUserDetailsResponse } from "jslib-common/models/response/provider/providerUserResponse";
import { ProviderUserBulkRequest } from "jslib-common/models/request/provider/providerUserBulkRequest"; import { ProviderUserBulkRequest } from "jslib-common/models/request/provider/providerUserBulkRequest";
import { ProviderUserConfirmRequest } from "jslib-common/models/request/provider/providerUserConfirmRequest"; import { ProviderUserConfirmRequest } from "jslib-common/models/request/provider/providerUserConfirmRequest";
import { ListResponse } from "jslib-common/models/response/listResponse";
import { ProviderUserBulkResponse } from "jslib-common/models/response/provider/providerUserBulkResponse"; import { ProviderUserBulkResponse } from "jslib-common/models/response/provider/providerUserBulkResponse";
import { ProviderUserUserDetailsResponse } from "jslib-common/models/response/provider/providerUserResponse";
import { BasePeopleComponent } from "src/app/common/base.people.component"; import { BasePeopleComponent } from "src/app/common/base.people.component";
import { BulkStatusComponent } from "src/app/organizations/manage/bulk/bulk-status.component"; import { BulkStatusComponent } from "src/app/organizations/manage/bulk/bulk-status.component";
import { EntityEventsComponent } from "src/app/organizations/manage/entity-events.component"; import { EntityEventsComponent } from "src/app/organizations/manage/entity-events.component";
import { BulkConfirmComponent } from "./bulk/bulk-confirm.component"; import { BulkConfirmComponent } from "./bulk/bulk-confirm.component";
import { BulkRemoveComponent } from "./bulk/bulk-remove.component"; import { BulkRemoveComponent } from "./bulk/bulk-remove.component";
import { UserAddEditComponent } from "./user-add-edit.component"; import { UserAddEditComponent } from "./user-add-edit.component";
@@ -158,17 +153,13 @@ export class PeopleComponent
} }
async events(user: ProviderUserUserDetailsResponse) { async events(user: ProviderUserUserDetailsResponse) {
const [modal] = await this.modalService.openViewRef( await this.modalService.openViewRef(EntityEventsComponent, this.eventsModalRef, (comp) => {
EntityEventsComponent,
this.eventsModalRef,
(comp) => {
comp.name = this.userNamePipe.transform(user); comp.name = this.userNamePipe.transform(user);
comp.providerId = this.providerId; comp.providerId = this.providerId;
comp.entityId = user.id; comp.entityId = user.id;
comp.showUser = false; comp.showUser = false;
comp.entity = "user"; comp.entity = "user";
} });
);
} }
async bulkRemove() { async bulkRemove() {
@@ -272,13 +263,14 @@ export class PeopleComponent
childComponent.users = users.map((user) => { childComponent.users = users.map((user) => {
let message = keyedErrors[user.id] ?? successfullMessage; let message = keyedErrors[user.id] ?? successfullMessage;
// eslint-disable-next-line
if (!keyedFilteredUsers.hasOwnProperty(user.id)) { if (!keyedFilteredUsers.hasOwnProperty(user.id)) {
message = this.i18nService.t("bulkFilteredMessage"); message = this.i18nService.t("bulkFilteredMessage");
} }
return { return {
user: user, user: user,
error: keyedErrors.hasOwnProperty(user.id), error: keyedErrors.hasOwnProperty(user.id), // eslint-disable-line
message: message, message: message,
}; };
}); });

View File

@@ -23,7 +23,7 @@
</div> </div>
<div class="modal-body" *ngIf="loading"> <div class="modal-body" *ngIf="loading">
<i <i
class="fa fa-spinner fa-spin text-muted" class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>
@@ -52,9 +52,9 @@
target="_blank" target="_blank"
rel="noopener" rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}" appA11yTitle="{{ 'learnMore' | i18n }}"
href="https://bitwarden.com/help/article/user-types-access-control/#user-types" href="https://bitwarden.com/help/provider-users/"
> >
<i class="fa fa-question-circle-o" aria-hidden="true"></i> <i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a> </a>
</h3> </h3>
<div class="form-check mt-2 form-check-block"> <div class="form-check mt-2 form-check-block">
@@ -88,7 +88,7 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading"> <button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i> <i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "save" | i18n }}</span> <span>{{ "save" | i18n }}</span>
</button> </button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal"> <button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
@@ -106,12 +106,12 @@
[appApiAction]="deletePromise" [appApiAction]="deletePromise"
> >
<i <i
class="fa fa-trash-o fa-lg fa-fw" class="bwi bwi-trash bwi-lg bwi-fw"
[hidden]="deleteBtn.loading" [hidden]="deleteBtn.loading"
aria-hidden="true" aria-hidden="true"
></i> ></i>
<i <i
class="fa fa-spinner fa-spin fa-lg fa-fw" class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!deleteBtn.loading" [hidden]="!deleteBtn.loading"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"

View File

@@ -4,12 +4,9 @@ import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service"; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ProviderUserInviteRequest } from "jslib-common/models/request/provider/providerUserInviteRequest";
import { PermissionsApi } from "jslib-common/models/api/permissionsApi";
import { ProviderUserType } from "jslib-common/enums/providerUserType"; import { ProviderUserType } from "jslib-common/enums/providerUserType";
import { PermissionsApi } from "jslib-common/models/api/permissionsApi";
import { ProviderUserInviteRequest } from "jslib-common/models/request/provider/providerUserInviteRequest";
import { ProviderUserUpdateRequest } from "jslib-common/models/request/provider/providerUserUpdateRequest"; import { ProviderUserUpdateRequest } from "jslib-common/models/request/provider/providerUserUpdateRequest";
@Component({ @Component({
@@ -24,7 +21,7 @@ export class UserAddEditComponent implements OnInit {
@Output() onDeletedUser = new EventEmitter(); @Output() onDeletedUser = new EventEmitter();
loading = true; loading = true;
editMode: boolean = false; editMode = false;
title: string; title: string;
emails: string; emails: string;
type: ProviderUserType = ProviderUserType.ServiceUser; type: ProviderUserType = ProviderUserType.ServiceUser;

View File

@@ -10,7 +10,7 @@
</div> </div>
<div class="ml-3 card border-danger text-danger bg-transparent" *ngIf="!provider.enabled"> <div class="ml-3 card border-danger text-danger bg-transparent" *ngIf="!provider.enabled">
<div class="card-body py-2"> <div class="card-body py-2">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i> <i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
{{ "providerIsDisabled" | i18n }} {{ "providerIsDisabled" | i18n }}
</div> </div>
</div> </div>
@@ -18,19 +18,19 @@
<ul class="nav nav-tabs" *ngIf="showMenuBar"> <ul class="nav nav-tabs" *ngIf="showMenuBar">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" routerLink="clients" routerLinkActive="active"> <a class="nav-link" routerLink="clients" routerLinkActive="active">
<i class="fa fa-university" aria-hidden="true"></i> <i class="bwi bwi-bank" aria-hidden="true"></i>
{{ "clients" | i18n }} {{ "clients" | i18n }}
</a> </a>
</li> </li>
<li class="nav-item" *ngIf="showManageTab"> <li class="nav-item" *ngIf="showManageTab">
<a class="nav-link" [routerLink]="manageRoute" routerLinkActive="active"> <a class="nav-link" [routerLink]="manageRoute" routerLinkActive="active">
<i class="fa fa-sliders" aria-hidden="true"></i> <i class="bwi bwi-sliders" aria-hidden="true"></i>
{{ "manage" | i18n }} {{ "manage" | i18n }}
</a> </a>
</li> </li>
<li class="nav-item" *ngIf="showSettingsTab"> <li class="nav-item" *ngIf="showSettingsTab">
<a class="nav-link" routerLink="settings" routerLinkActive="active"> <a class="nav-link" routerLink="settings" routerLinkActive="active">
<i class="fa fa-cogs" aria-hidden="true"></i> <i class="bwi bwi-cogs" aria-hidden="true"></i>
{{ "settings" | i18n }} {{ "settings" | i18n }}
</a> </a>
</li> </li>

View File

@@ -2,7 +2,6 @@ import { Component } from "@angular/core";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
import { ProviderService } from "jslib-common/abstractions/provider.service"; import { ProviderService } from "jslib-common/abstractions/provider.service";
import { Provider } from "jslib-common/models/domain/provider"; import { Provider } from "jslib-common/models/domain/provider";
@Component({ @Component({

View File

@@ -4,7 +4,9 @@ import { RouterModule, Routes } from "@angular/router";
import { AuthGuardService } from "jslib-angular/services/auth-guard.service"; import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { Permissions } from "jslib-common/enums/permissions"; import { Permissions } from "jslib-common/enums/permissions";
import { AddOrganizationComponent } from "./clients/add-organization.component"; import { FrontendLayoutComponent } from "src/app/layouts/frontend-layout.component";
import { ProvidersComponent } from "src/app/providers/providers.component";
import { ClientsComponent } from "./clients/clients.component"; import { ClientsComponent } from "./clients/clients.component";
import { CreateOrganizationComponent } from "./clients/create-organization.component"; import { CreateOrganizationComponent } from "./clients/create-organization.component";
import { AcceptProviderComponent } from "./manage/accept-provider.component"; import { AcceptProviderComponent } from "./manage/accept-provider.component";
@@ -12,16 +14,12 @@ import { EventsComponent } from "./manage/events.component";
import { ManageComponent } from "./manage/manage.component"; import { ManageComponent } from "./manage/manage.component";
import { PeopleComponent } from "./manage/people.component"; import { PeopleComponent } from "./manage/people.component";
import { ProvidersLayoutComponent } from "./providers-layout.component"; import { ProvidersLayoutComponent } from "./providers-layout.component";
import { SettingsComponent } from "./settings/settings.component";
import { SetupProviderComponent } from "./setup/setup-provider.component";
import { SetupComponent } from "./setup/setup.component";
import { FrontendLayoutComponent } from "src/app/layouts/frontend-layout.component";
import { ProvidersComponent } from "src/app/providers/providers.component";
import { ProviderGuardService } from "./services/provider-guard.service"; import { ProviderGuardService } from "./services/provider-guard.service";
import { ProviderTypeGuardService } from "./services/provider-type-guard.service"; import { ProviderTypeGuardService } from "./services/provider-type-guard.service";
import { AccountComponent } from "./settings/account.component"; import { AccountComponent } from "./settings/account.component";
import { SettingsComponent } from "./settings/settings.component";
import { SetupProviderComponent } from "./setup/setup-provider.component";
import { SetupComponent } from "./setup/setup.component";
const routes: Routes = [ const routes: Routes = [
{ {

View File

@@ -1,21 +1,14 @@
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { ComponentFactoryResolver } from "@angular/core"; import { ComponentFactoryResolver, NgModule } from "@angular/core";
import { NgModule } from "@angular/core";
import { FormsModule } from "@angular/forms"; import { FormsModule } from "@angular/forms";
import { ModalService } from "jslib-angular/services/modal.service"; import { ModalService } from "jslib-angular/services/modal.service";
import { ProviderGuardService } from "./services/provider-guard.service"; import { OssModule } from "src/app/oss.module";
import { ProviderTypeGuardService } from "./services/provider-type-guard.service";
import { WebProviderService } from "./services/webProvider.service";
import { ProvidersLayoutComponent } from "./providers-layout.component";
import { ProvidersRoutingModule } from "./providers-routing.module";
import { AddOrganizationComponent } from "./clients/add-organization.component"; import { AddOrganizationComponent } from "./clients/add-organization.component";
import { ClientsComponent } from "./clients/clients.component"; import { ClientsComponent } from "./clients/clients.component";
import { CreateOrganizationComponent } from "./clients/create-organization.component"; import { CreateOrganizationComponent } from "./clients/create-organization.component";
import { AcceptProviderComponent } from "./manage/accept-provider.component"; import { AcceptProviderComponent } from "./manage/accept-provider.component";
import { BulkConfirmComponent } from "./manage/bulk/bulk-confirm.component"; import { BulkConfirmComponent } from "./manage/bulk/bulk-confirm.component";
import { BulkRemoveComponent } from "./manage/bulk/bulk-remove.component"; import { BulkRemoveComponent } from "./manage/bulk/bulk-remove.component";
@@ -23,15 +16,16 @@ import { EventsComponent } from "./manage/events.component";
import { ManageComponent } from "./manage/manage.component"; import { ManageComponent } from "./manage/manage.component";
import { PeopleComponent } from "./manage/people.component"; import { PeopleComponent } from "./manage/people.component";
import { UserAddEditComponent } from "./manage/user-add-edit.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 { AccountComponent } from "./settings/account.component";
import { SettingsComponent } from "./settings/settings.component"; import { SettingsComponent } from "./settings/settings.component";
import { SetupProviderComponent } from "./setup/setup-provider.component"; import { SetupProviderComponent } from "./setup/setup-provider.component";
import { SetupComponent } from "./setup/setup.component"; import { SetupComponent } from "./setup/setup.component";
import { OssModule } from "src/app/oss.module";
@NgModule({ @NgModule({
imports: [CommonModule, FormsModule, OssModule, ProvidersRoutingModule], imports: [CommonModule, FormsModule, OssModule, ProvidersRoutingModule],
declarations: [ declarations: [

View File

@@ -2,7 +2,6 @@ import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router"; import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router";
import { ProviderService } from "jslib-common/abstractions/provider.service"; import { ProviderService } from "jslib-common/abstractions/provider.service";
import { Permissions } from "jslib-common/enums/permissions"; import { Permissions } from "jslib-common/enums/permissions";
@Injectable() @Injectable()

View File

@@ -3,7 +3,6 @@ import { Injectable } from "@angular/core";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service"; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { SyncService } from "jslib-common/abstractions/sync.service"; import { SyncService } from "jslib-common/abstractions/sync.service";
import { ProviderAddOrganizationRequest } from "jslib-common/models/request/provider/providerAddOrganizationRequest"; import { ProviderAddOrganizationRequest } from "jslib-common/models/request/provider/providerAddOrganizationRequest";
@Injectable() @Injectable()

View File

@@ -2,7 +2,11 @@
<h1>{{ "myProvider" | i18n }}</h1> <h1>{{ "myProvider" | i18n }}</h1>
</div> </div>
<div *ngIf="loading"> <div *ngIf="loading">
<i class="fa fa-spinner fa-spin text-muted" title="{{ 'loading' | i18n }}" aria-hidden="true"></i> <i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span> <span class="sr-only">{{ "loading" | i18n }}</span>
</div> </div>
<form <form
@@ -42,7 +46,7 @@
</div> </div>
</div> </div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading"> <button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i> <i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "save" | i18n }}</span> <span>{{ "save" | i18n }}</span>
</button> </button>
</form> </form>

View File

@@ -6,9 +6,7 @@ import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service"; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SyncService } from "jslib-common/abstractions/sync.service"; import { SyncService } from "jslib-common/abstractions/sync.service";
import { ProviderUpdateRequest } from "jslib-common/models/request/provider/providerUpdateRequest"; import { ProviderUpdateRequest } from "jslib-common/models/request/provider/providerUpdateRequest";
import { ProviderResponse } from "jslib-common/models/response/provider/providerResponse"; import { ProviderResponse } from "jslib-common/models/response/provider/providerResponse";
@Component({ @Component({

View File

@@ -1,7 +1,6 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ProviderService } from "jslib-common/abstractions/provider.service"; import { ProviderService } from "jslib-common/abstractions/provider.service";
@Component({ @Component({
@@ -9,15 +8,11 @@ import { ProviderService } from "jslib-common/abstractions/provider.service";
templateUrl: "settings.component.html", templateUrl: "settings.component.html",
}) })
export class SettingsComponent { export class SettingsComponent {
constructor( constructor(private route: ActivatedRoute, private providerService: ProviderService) {}
private route: ActivatedRoute,
private providerService: ProviderService,
private platformUtilsService: PlatformUtilsService
) {}
ngOnInit() { ngOnInit() {
this.route.parent.params.subscribe(async (params) => { this.route.parent.params.subscribe(async (params) => {
const provider = await this.providerService.get(params.providerId); await this.providerService.get(params.providerId);
}); });
} }
} }

View File

@@ -3,7 +3,7 @@
<img class="mb-4 logo logo-themed" alt="Bitwarden" /> <img class="mb-4 logo logo-themed" alt="Bitwarden" />
<p class="text-center"> <p class="text-center">
<i <i
class="fa fa-spinner fa-spin fa-2x text-muted" class="bwi bwi-spinner bwi-spin bwi-2x text-muted"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>

View File

@@ -16,6 +16,7 @@ export class SetupProviderComponent extends BaseAcceptComponent {
this.router.navigate(["/providers/setup"], { queryParams: qParams }); this.router.navigate(["/providers/setup"], { queryParams: qParams });
} }
// tslint:disable-next-line async unauthedHandler(qParams: any) {
async unauthedHandler(qParams: any) {} // Empty
}
} }

View File

@@ -27,7 +27,7 @@
<div class="mt-4"> <div class="mt-4">
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading"> <button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i> <i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "submit" | i18n }}</span> <span>{{ "submit" | i18n }}</span>
</button> </button>
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" *ngIf="showCancel"> <button type="button" class="btn btn-outline-secondary" (click)="cancel()" *ngIf="showCancel">

View File

@@ -1,12 +1,10 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators"; import { first } from "rxjs/operators";
import { ValidationService } from "jslib-angular/services/validation.service";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service"; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { ValidationService } from "jslib-angular/services/validation.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SyncService } from "jslib-common/abstractions/sync.service"; import { SyncService } from "jslib-common/abstractions/sync.service";
@@ -48,9 +46,7 @@ export class SetupComponent implements OnInit {
"error", "error",
null, null,
this.i18nService.t("emergencyInviteAcceptFailed"), this.i18nService.t("emergencyInviteAcceptFailed"),
{ { timeout: 10000 }
timeout: 10000,
}
); );
this.router.navigate(["/"]); this.router.navigate(["/"]);
return; return;

2
jslib

Submodule jslib updated: e4cd0af2f9...3ec0f6977a

7339
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "bitwarden-web", "name": "@bitwarden/web-vault",
"version": "2.25.1", "version": "2.27.0",
"license": "GPL-3.0", "license": "GPL-3.0",
"repository": "https://github.com/bitwarden/web", "repository": "https://github.com/bitwarden/web",
"scripts": { "scripts": {
@@ -29,8 +29,8 @@
"dist:bit:selfhost": "npm run build:bit:selfhost:prod", "dist:bit:selfhost": "npm run build:bit:selfhost:prod",
"deploy": "npm run dist:bit && gh-pages -d build", "deploy": "npm run dist:bit && gh-pages -d build",
"deploy:dev": "npm run dist:bit && gh-pages -d build -r git@github.com:kspearrin/bitwarden-web-dev.git", "deploy:dev": "npm run dist:bit && gh-pages -d build -r git@github.com:kspearrin/bitwarden-web-dev.git",
"lint": "tslint 'src/**/*.ts' 'bitwarden_license/src/**/*.ts' && prettier --check .", "lint": "eslint . && prettier --check .",
"lint:fix": "tslint 'src/**/*.ts' 'bitwarden_license/src/**/*.ts' --fix", "lint:fix": "eslint . --fix",
"prettier": "prettier --write .", "prettier": "prettier --write .",
"prepare": "husky install" "prepare": "husky install"
}, },
@@ -41,11 +41,18 @@
"@types/node": "^16.11.12", "@types/node": "^16.11.12",
"@types/webcrypto": "^0.0.28", "@types/webcrypto": "^0.0.28",
"@types/webpack": "^5.28.0", "@types/webpack": "^5.28.0",
"@typescript-eslint/eslint-plugin": "^5.10.1",
"@typescript-eslint/parser": "^5.10.1",
"autoprefixer": "^10.4.2",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^10.0.0", "copy-webpack-plugin": "^10.0.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"css-loader": "^6.5.1", "css-loader": "^6.5.1",
"eslint": "^8.7.0",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-typescript": "^2.5.0",
"eslint-plugin-import": "^2.25.4",
"gh-pages": "^3.1.0", "gh-pages": "^3.1.0",
"html-loader": "^3.0.1", "html-loader": "^3.0.1",
"html-webpack-injector": "1.1.4", "html-webpack-injector": "1.1.4",
@@ -53,15 +60,17 @@
"husky": "^7.0.4", "husky": "^7.0.4",
"lint-staged": "^12.1.2", "lint-staged": "^12.1.2",
"mini-css-extract-plugin": "^2.4.5", "mini-css-extract-plugin": "^2.4.5",
"postcss": "^8.4.6",
"postcss-loader": "^6.2.1",
"prettier": "2.5.1", "prettier": "2.5.1",
"process": "^0.11.10", "process": "^0.11.10",
"rimraf": "^3.0.2",
"sass": "^1.32.10", "sass": "^1.32.10",
"sass-loader": "^12.4.0", "sass-loader": "^12.4.0",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"tailwindcss": "^3.0.18",
"terser-webpack-plugin": "^5.2.5", "terser-webpack-plugin": "^5.2.5",
"ts-loader": "^9.2.5", "ts-loader": "^9.2.5",
"tslint": "^6.1.3",
"tslint-loader": "^3.5.4",
"typescript": "4.3.5", "typescript": "4.3.5",
"util": "^0.12.4", "util": "^0.12.4",
"webpack": "^5.64.4", "webpack": "^5.64.4",
@@ -85,23 +94,26 @@
"browser-hrtime": "^1.1.8", "browser-hrtime": "^1.1.8",
"core-js": "^3.11.0", "core-js": "^3.11.0",
"date-input-polyfill": "^2.14.0", "date-input-polyfill": "^2.14.0",
"font-awesome": "4.7.0",
"jquery": "3.6.0", "jquery": "3.6.0",
"jszip": "^3.7.1",
"ngx-infinite-scroll": "^10.0.1", "ngx-infinite-scroll": "^10.0.1",
"ngx-toastr": "14.1.4", "ngx-toastr": "14.1.4",
"node-forge": "^1.3.0",
"popper.js": "1.16.1", "popper.js": "1.16.1",
"qrious": "4.0.2", "qrious": "4.0.2",
"rxjs": "^7.4.0", "rxjs": "^7.4.0",
"sweetalert2": "^10.16.6", "sweetalert2": "^10.16.6",
"webcrypto-shim": "0.1.7", "webcrypto-shim": "0.1.7",
"whatwg-fetch": "3.6.2" "whatwg-fetch": "3.6.2",
"zone.js": "0.11.4"
}, },
"engines": { "engines": {
"node": "~16", "node": "~16",
"npm": "~8" "npm": "~8"
}, },
"lint-staged": { "lint-staged": {
"*": "prettier --ignore-unknown --write", "./!(jslib)**": "prettier --ignore-unknown --write",
"*.ts": "eslint --fix",
"*.png": "node scripts/optimize.js" "*.png": "node scripts/optimize.js"
} }
} }

4
postcss.config.js Normal file
View File

@@ -0,0 +1,4 @@
/* eslint-disable no-undef */
module.exports = {
plugins: [require("tailwindcss"), require("autoprefixer"), require("postcss-nested")],
};

View File

@@ -9,13 +9,7 @@
href="/404/bootstrap.min.css" href="/404/bootstrap.min.css"
rel="stylesheet" rel="stylesheet"
type="text/css" type="text/css"
integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" integrity="sha384-hA/ESrxp2b05ywLtD9YwM6m+pNyLRY4+ruk6dWK00SM4k6SQs0bfrITJVSf6uZyH"
/>
<link
href="/404/font-awesome.min.css"
rel="stylesheet"
type="text/css"
integrity="sha512-SfTiTlX6kk+qitfevl/7LibUOeJWlt9rbyDn92a1DqWOw9vWG2MFoays0sgObmWazO5BQPiFucnnEAjpAB+/Sw=="
/> />
<link href="/404/styles.css" rel="stylesheet" type="text/css" /> <link href="/404/styles.css" rel="stylesheet" type="text/css" />
@@ -33,7 +27,9 @@
<div class="banner"> <div class="banner">
<div class="container inner banner"> <div class="container inner banner">
<div class="row align-items-center"> <div class="row align-items-center">
<div class="col brand"><i class="fa fa-shield"></i>&nbsp; <strong>bit</strong>warden</div> <div class="col brand">
<i class="bwi bwi-shield"></i>&nbsp; <strong>bit</strong>warden
</div>
</div> </div>
</div> </div>
</div> </div>

File diff suppressed because one or more lines are too long

View File

@@ -119,3 +119,33 @@ h2 {
padding: 40px 0 40px 0; padding: 40px 0 40px 0;
border-top: 1px solid #dee2e6; border-top: 1px solid #dee2e6;
} }
/* Bitwarden icons, manually copied */
@font-face {
font-family: "bwi-font";
src: url(../images/bwi-font.svg) format("svg"), url(../fonts/bwi-font.ttf) format("truetype"),
url(../fonts/bwi-font.woff) format("woff"), url(../fonts/bwi-font.woff2) format("woff2");
font-weight: normal;
font-style: normal;
font-display: block;
}
.bwi {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: "bwi-font" !important;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
display: inline-block;
/* Better Font Rendering */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.bwi-shield:before {
content: "\e932";
}

View File

@@ -0,0 +1,9 @@
import { StateService as BaseStateService } from "jslib-common/abstractions/state.service";
import { StorageOptions } from "jslib-common/models/domain/storageOptions";
import { Account } from "src/models/account";
export abstract class StateService extends BaseStateService<Account> {
getRememberEmail: (options?: StorageOptions) => Promise<boolean>;
setRememberEmail: (value: boolean, options?: StorageOptions) => Promise<void>;
}

View File

@@ -3,7 +3,7 @@
<img class="mb-4 logo logo-themed" alt="Bitwarden" /> <img class="mb-4 logo logo-themed" alt="Bitwarden" />
<p class="text-center"> <p class="text-center">
<i <i
class="fa fa-spinner fa-spin fa-2x text-muted" class="bwi bwi-spinner bwi-spin bwi-2x text-muted"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>

View File

@@ -6,6 +6,7 @@ import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service"; import { StateService } from "jslib-common/abstractions/state.service";
import { EmergencyAccessAcceptRequest } from "jslib-common/models/request/emergencyAccessAcceptRequest"; import { EmergencyAccessAcceptRequest } from "jslib-common/models/request/emergencyAccessAcceptRequest";
import { BaseAcceptComponent } from "../common/base.accept.component"; import { BaseAcceptComponent } from "../common/base.accept.component";
@Component({ @Component({

View File

@@ -3,7 +3,7 @@
<img src="../../images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden" /> <img src="../../images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden" />
<p class="text-center"> <p class="text-center">
<i <i
class="fa fa-spinner fa-spin fa-2x text-muted" class="bwi bwi-spinner bwi-spin bwi-2x text-muted"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>

View File

@@ -8,12 +8,11 @@ import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service"; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service"; import { StateService } from "jslib-common/abstractions/state.service";
import { Utils } from "jslib-common/misc/utils";
import { Policy } from "jslib-common/models/domain/policy";
import { OrganizationUserAcceptRequest } from "jslib-common/models/request/organizationUserAcceptRequest"; import { OrganizationUserAcceptRequest } from "jslib-common/models/request/organizationUserAcceptRequest";
import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest"; import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest";
import { Utils } from "jslib-common/misc/utils";
import { Policy } from "jslib-common/models/domain/policy";
import { BaseAcceptComponent } from "../common/base.accept.component"; import { BaseAcceptComponent } from "../common/base.accept.component";
@Component({ @Component({

View File

@@ -28,7 +28,7 @@
> >
<span [hidden]="form.loading">{{ "submit" | i18n }}</span> <span [hidden]="form.loading">{{ "submit" | i18n }}</span>
<i <i
class="fa fa-spinner fa-spin" class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>

View File

@@ -1,13 +1,12 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { HintComponent as BaseHintComponent } from "jslib-angular/components/hint.component";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service"; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { HintComponent as BaseHintComponent } from "jslib-angular/components/hint.component";
@Component({ @Component({
selector: "app-hint", selector: "app-hint",
templateUrl: "hint.component.html", templateUrl: "hint.component.html",

View File

@@ -2,7 +2,7 @@
<div class="row justify-content-md-center mt-5"> <div class="row justify-content-md-center mt-5">
<div class="col-5"> <div class="col-5">
<p class="text-center mb-4"> <p class="text-center mb-4">
<i class="fa fa-lock fa-4x text-muted" aria-hidden="true"></i> <i class="bwi bwi-lock bwi-4x text-muted" aria-hidden="true"></i>
</p> </p>
<p class="lead text-center mx-4 mb-4">{{ "yourVaultIsLocked" | i18n }}</p> <p class="lead text-center mx-4 mb-4">{{ "yourVaultIsLocked" | i18n }}</p>
<div class="card d-block"> <div class="card d-block">
@@ -27,9 +27,9 @@
(click)="togglePassword()" (click)="togglePassword()"
> >
<i <i
class="fa fa-lg" class="bwi bwi-lg"
aria-hidden="true" aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }" [ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i> ></i>
</button> </button>
</div> </div>
@@ -44,11 +44,9 @@
class="btn btn-primary btn-block btn-submit" class="btn btn-primary btn-block btn-submit"
[disabled]="form.loading" [disabled]="form.loading"
> >
<span> <span> <i class="bwi bwi-unlock" aria-hidden="true"></i> {{ "unlock" | i18n }} </span>
<i class="fa fa-unlock-alt" aria-hidden="true"></i> {{ "unlock" | i18n }}
</span>
<i <i
class="fa fa-spinner fa-spin" class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>

View File

@@ -1,6 +1,7 @@
import { Component, NgZone } from "@angular/core"; import { Component, NgZone } from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { LockComponent as BaseLockComponent } from "jslib-angular/components/lock.component";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service"; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service"; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
@@ -14,8 +15,6 @@ import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.serv
import { RouterService } from "../services/router.service"; import { RouterService } from "../services/router.service";
import { LockComponent as BaseLockComponent } from "jslib-angular/components/lock.component";
@Component({ @Component({
selector: "app-lock", selector: "app-lock",
templateUrl: "lock.component.html", templateUrl: "lock.component.html",

View File

@@ -44,9 +44,9 @@
(click)="togglePassword()" (click)="togglePassword()"
> >
<i <i
class="fa fa-lg" class="bwi bwi-lg"
aria-hidden="true" aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }" [ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i> ></i>
</button> </button>
</div> </div>
@@ -74,9 +74,9 @@
class="btn btn-primary btn-block btn-submit" class="btn btn-primary btn-block btn-submit"
[disabled]="form.loading" [disabled]="form.loading"
> >
<span> <i class="fa fa-sign-in" aria-hidden="true"></i> {{ "logIn" | i18n }} </span> <span> <i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ "logIn" | i18n }} </span>
<i <i
class="fa fa-spinner fa-spin" class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>
@@ -86,12 +86,13 @@
[queryParams]="{ email: email }" [queryParams]="{ email: email }"
class="btn btn-outline-secondary btn-block ml-2 mt-0" class="btn btn-outline-secondary btn-block ml-2 mt-0"
> >
<i class="fa fa-pencil-square-o" aria-hidden="true"></i> {{ "createAccount" | i18n }} <i class="bwi bwi-pencil-square" aria-hidden="true"></i>
{{ "createAccount" | i18n }}
</a> </a>
</div> </div>
<div class="d-flex"> <div class="d-flex">
<a routerLink="/sso" class="btn btn-outline-secondary btn-block mt-2"> <a routerLink="/sso" class="btn btn-outline-secondary btn-block mt-2">
<i class="fa fa-bank" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | i18n }} <i class="bwi bwi-bank" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | i18n }}
</a> </a>
</div> </div>
</div> </div>

View File

@@ -1,22 +1,25 @@
import { Component, NgZone } from "@angular/core"; import { Component, NgZone } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators"; import { first } from "rxjs/operators";
import { LoginComponent as BaseLoginComponent } from "jslib-angular/components/login.component";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service"; import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service"; import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service"; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service"; import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service"; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service"; import { PolicyData } from "jslib-common/models/data/policyData";
import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions";
import { LoginComponent as BaseLoginComponent } from "jslib-angular/components/login.component";
import { Policy } from "jslib-common/models/domain/policy"; import { Policy } from "jslib-common/models/domain/policy";
import { ListResponse } from "jslib-common/models/response/listResponse";
import { PolicyResponse } from "jslib-common/models/response/policyResponse";
import { StateService } from "../../abstractions/state.service";
@Component({ @Component({
selector: "app-login", selector: "app-login",
@@ -24,13 +27,14 @@ import { Policy } from "jslib-common/models/domain/policy";
}) })
export class LoginComponent extends BaseLoginComponent { export class LoginComponent extends BaseLoginComponent {
showResetPasswordAutoEnrollWarning = false; showResetPasswordAutoEnrollWarning = false;
enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions;
policies: ListResponse<PolicyResponse>;
constructor( constructor(
authService: AuthService, authService: AuthService,
router: Router, router: Router,
i18nService: I18nService, i18nService: I18nService,
private route: ActivatedRoute, private route: ActivatedRoute,
stateService: StateService,
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService, environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService, passwordGenerationService: PasswordGenerationService,
@@ -38,7 +42,9 @@ export class LoginComponent extends BaseLoginComponent {
private apiService: ApiService, private apiService: ApiService,
private policyService: PolicyService, private policyService: PolicyService,
logService: LogService, logService: LogService,
ngZone: NgZone ngZone: NgZone,
protected stateService: StateService,
private messagingService: MessagingService
) { ) {
super( super(
authService, authService,
@@ -52,6 +58,9 @@ export class LoginComponent extends BaseLoginComponent {
logService, logService,
ngZone ngZone
); );
this.onSuccessfulLogin = async () => {
this.messagingService.send("setFullWidth");
};
this.onSuccessfulLoginNavigate = this.goAfterLogIn; this.onSuccessfulLoginNavigate = this.goAfterLogIn;
} }
@@ -78,35 +87,64 @@ export class LoginComponent extends BaseLoginComponent {
}); });
} }
await super.ngOnInit(); await super.ngOnInit();
this.rememberEmail = await this.stateService.getRememberEmail();
}); });
const invite = await this.stateService.getOrganizationInvitation(); const invite = await this.stateService.getOrganizationInvitation();
if (invite != null) { if (invite != null) {
let policyList: Policy[] = null; let policyList: Policy[] = null;
try { try {
const policies = await this.apiService.getPoliciesByToken( this.policies = await this.apiService.getPoliciesByToken(
invite.organizationId, invite.organizationId,
invite.token, invite.token,
invite.email, invite.email,
invite.organizationUserId invite.organizationUserId
); );
policyList = this.policyService.mapPoliciesFromToken(policies); policyList = this.policyService.mapPoliciesFromToken(this.policies);
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }
if (policyList != null) { if (policyList != null) {
const result = this.policyService.getResetPasswordPolicyOptions( const resetPasswordPolicy = this.policyService.getResetPasswordPolicyOptions(
policyList, policyList,
invite.organizationId invite.organizationId
); );
// Set to true if policy enabled and auto-enroll enabled // Set to true if policy enabled and auto-enroll enabled
this.showResetPasswordAutoEnrollWarning = result[1] && result[0].autoEnrollEnabled; this.showResetPasswordAutoEnrollWarning =
resetPasswordPolicy[1] && resetPasswordPolicy[0].autoEnrollEnabled;
this.enforcedPasswordPolicyOptions =
await this.policyService.getMasterPasswordPolicyOptions(policyList);
} }
} }
} }
async goAfterLogIn() { async goAfterLogIn() {
// Check master password against policy
if (this.enforcedPasswordPolicyOptions != null) {
const strengthResult = this.passwordGenerationService.passwordStrength(
this.masterPassword,
this.getPasswordStrengthUserInput()
);
const masterPasswordScore = strengthResult == null ? null : strengthResult.score;
// If invalid, save policies and require update
if (
!this.policyService.evaluateMasterPassword(
masterPasswordScore,
this.masterPassword,
this.enforcedPasswordPolicyOptions
)
) {
const policiesData: { [id: string]: PolicyData } = {};
this.policies.data.map((p) => (policiesData[p.id] = new PolicyData(p)));
await this.policyService.replace(policiesData);
this.router.navigate(["update-password"]);
return;
}
}
const loginRedirect = await this.stateService.getLoginRedirect(); const loginRedirect = await this.stateService.getLoginRedirect();
if (loginRedirect != null) { if (loginRedirect != null) {
this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams }); this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams });
@@ -115,4 +153,27 @@ export class LoginComponent extends BaseLoginComponent {
this.router.navigate([this.successRoute]); this.router.navigate([this.successRoute]);
} }
} }
async submit() {
await this.stateService.setRememberEmail(this.rememberEmail);
if (!this.rememberEmail) {
await this.stateService.setRememberedEmail(null);
}
await super.submit();
}
private getPasswordStrengthUserInput() {
let userInput: string[] = [];
const atPosition = this.email.indexOf("@");
if (atPosition > -1) {
userInput = userInput.concat(
this.email
.substr(0, atPosition)
.trim()
.toLowerCase()
.split(/[^A-Za-z0-9]/)
);
}
return userInput;
}
} }

View File

@@ -28,7 +28,7 @@
> >
<span>{{ "submit" | i18n }}</span> <span>{{ "submit" | i18n }}</span>
<i <i
class="fa fa-spinner fa-spin" class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>

View File

@@ -5,7 +5,6 @@ import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service"; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { DeleteRecoverRequest } from "jslib-common/models/request/deleteRecoverRequest"; import { DeleteRecoverRequest } from "jslib-common/models/request/deleteRecoverRequest";
@Component({ @Component({

View File

@@ -7,7 +7,7 @@
<p> <p>
{{ "recoverAccountTwoStepDesc" | i18n }} {{ "recoverAccountTwoStepDesc" | i18n }}
<a <a
href="https://help.bitwarden.com/article/lost-two-step-device/" href="https://bitwarden.com/help/lost-two-step-device/"
target="_blank" target="_blank"
rel="noopener" rel="noopener"
>{{ "learnMore" | i18n }}</a >{{ "learnMore" | i18n }}</a
@@ -60,7 +60,7 @@
> >
<span>{{ "submit" | i18n }}</span> <span>{{ "submit" | i18n }}</span>
<i <i
class="fa fa-spinner fa-spin" class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>

View File

@@ -7,7 +7,6 @@ import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service"; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { TwoFactorRecoveryRequest } from "jslib-common/models/request/twoFactorRecoveryRequest"; import { TwoFactorRecoveryRequest } from "jslib-common/models/request/twoFactorRecoveryRequest";
@Component({ @Component({

View File

@@ -1,44 +1,100 @@
<div class="layout" [ngClass]="['layout', layout]"> <div class="layout" [ngClass]="['layout', layout]">
<header class="header" *ngIf="layout === 'enterprise2'"> <!-- TEAMS 1 Header -->
<header
class="header"
*ngIf="
layout === 'default' ||
layout === 'teams' ||
layout === 'teams1' ||
layout === 'teams2' ||
layout === 'enterprise' ||
layout === 'enterprise1' ||
layout === 'enterprise2' ||
layout === 'cnetcmpgnent' ||
layout === 'cnetcmpgnteams' ||
layout === 'cnetcmpgnind'
"
>
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-7"> <div class="col-7">
<img <img
alt="Bitwarden" alt="Bitwarden"
class="logo mb-2" class="logo mb-2"
src="../../images/register-layout/logo-horizontal-white.png" src="../../images/register-layout/logo-horizontal-white.svg"
/> />
</div> </div>
</div> </div>
</div> </div>
</header> </header>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row"> <div class="row">
<div class="col-7" *ngIf="layout"> <div class="col-7" *ngIf="layout">
<div class="mt-5"> <div class="mt-5">
<div *ngIf="layout === 'enterprise2'"> <!-- Default Body -->
<h2>Companies globally trust Bitwarden for password management.</h2> <div
<p>Start your 7-day free trial!</p> *ngIf="
<p class="highlight">Quickly deploy your <b>organization</b></p> layout === 'teams' ||
<p>Use Bitwarden across all platforms</p> layout === 'enterprise' ||
layout === 'enterprise1' ||
layout === 'default'
"
>
<h1>The Bitwarden Password Manager</h1>
<h2>
Trusted by millions of individuals, teams, and organizations worldwide for secure
password storage and sharing.
</h2>
<p>Store logins, secure notes, and more</p>
<p>Collaborate and share securely</p> <p>Collaborate and share securely</p>
<figure> <p>Access anywhere on any device</p>
<figcaption> <p>Create your account to get started</p>
<cite>
<img src="../../images/register-layout/wired-logo.png" alt="Wired" />
</cite>
</figcaption>
<blockquote>
"Bitwarden has become a popular choice among open-source software advocates. After
using it for a few months, I can see why." - February 2020
</blockquote>
</figure>
</div> </div>
<div *ngIf="layout === 'enterprise3'">
<p>Enterprise 3 layout</p> <!-- Teams & Enterprise Body -->
<div *ngIf="layout === 'teams1' || layout === 'teams2' || layout === 'enterprise2'">
<h1>
Start Your <span *ngIf="layout === 'teams1' || layout === 'teams1'">Teams<br /></span
><span *ngIf="layout === 'enterprise2'">Enterprise</span> Free Trial Now
</h1>
<h2>
Millions of individuals, teams, and organizations worldwide trust Bitwarden for secure
password storage and sharing.
</h2>
<p>Collaborate and share securely</p>
<p>Deploy and manage quickly and easily</p>
<p>Access anywhere on any device</p>
<p>Create your account to get started</p>
</div> </div>
<div *ngIf="layout === 'enterprise4'">
<p>Enterprise 4 layout</p> <!-- CNET Campaign Teams & Enterprise Body -->
<div *ngIf="layout === 'cnetcmpgnteams' || layout === 'cnetcmpgnent'">
<h1>
Start Your <span *ngIf="layout === 'cnetcmpgnteams'">Teams<br /></span
><span *ngIf="layout === 'cnetcmpgnent'">Enterprise</span> Free Trial Now
</h1>
<h2>
Millions of individuals, teams, and organizations worldwide trust Bitwarden for secure
password storage and sharing.
</h2>
<p>Collaborate and share securely</p>
<p>Deploy and manage quickly and easily</p>
<p>Access anywhere on any device</p>
<p>Create your account to get started</p>
</div>
<!-- CNET Campaign Premium Body -->
<div *ngIf="layout === 'cnetcmpgnind'">
<h1>Start Your Premium Account Now</h1>
<h2>
Millions of individuals, teams, and organizations worldwide trust Bitwarden for secure
password storage and sharing.
</h2>
<p>Store logins, secure notes, and more</p>
<p>Secure your account with advanced two-step login</p>
<p>Access anywhere on any device</p>
<p>Create your account to get started</p>
</div> </div>
</div> </div>
</div> </div>
@@ -51,7 +107,7 @@
<app-callout <app-callout
title="{{ 'createOrganizationStep1' | i18n }}" title="{{ 'createOrganizationStep1' | i18n }}"
type="info" type="info"
icon="fa-thumb-tack" icon="bwi bwi-thumb-tack"
*ngIf="showCreateOrgMessage" *ngIf="showCreateOrgMessage"
> >
{{ "createOrganizationCreatePersonalAccount" | i18n }} {{ "createOrganizationCreatePersonalAccount" | i18n }}
@@ -114,11 +170,11 @@
(click)="togglePassword(false)" (click)="togglePassword(false)"
> >
<i <i
class="fa fa-lg" class="bwi bwi-lg"
aria-hidden="true" aria-hidden="true"
[ngClass]="{ [ngClass]="{
'fa-eye': !showPassword, 'bwi-eye': !showPassword,
'fa-eye-slash': showPassword 'bwi-eye-slash': showPassword
}" }"
></i> ></i>
</button> </button>
@@ -146,9 +202,9 @@
(click)="togglePassword(true)" (click)="togglePassword(true)"
> >
<i <i
class="fa fa-lg" class="bwi bwi-lg"
aria-hidden="true" aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }" [ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i> ></i>
</button> </button>
</div> </div>
@@ -197,7 +253,7 @@
> >
<span>{{ "submit" | i18n }}</span> <span>{{ "submit" | i18n }}</span>
<i <i
class="fa fa-spinner fa-spin" class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>
@@ -212,5 +268,88 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row">
<div class="col-7 d-flex align-items-center">
<div
*ngIf="
layout === 'cnetcmpgnent' || layout === 'cnetcmpgnteams' || layout === 'cnetcmpgnind'
"
>
<figure>
<figcaption>
<cite>
<img
src="../../images/register-layout/cnet-logo.svg"
class="w-25 d-block mx-auto"
alt="cnet logo"
/>
</cite>
</figcaption>
<blockquote class="mx-auto text-center px-4">
"No more excuses; start using Bitwarden today. The identity you save could be your
own. The money definitely will be."
</blockquote>
</figure>
</div>
<div
*ngIf="
layout === 'teams' ||
layout === 'teams1' ||
layout === 'teams2' ||
layout === 'enterprise' ||
layout === 'enterprise1' ||
layout === 'enterprise2' ||
layout === 'default'
"
>
<figure>
<figcaption>
<cite>
<img
src="../../images/register-layout/forbes-logo.svg"
class="w-25 d-block mx-auto"
alt="Forbes Logo"
/>
</cite>
</figcaption>
<blockquote class="mx-auto text-center px-4">
“Bitwarden boasts the backing of some of the world's best security experts and an
attractive, easy-to-use interface”
</blockquote>
</figure>
</div>
</div>
<div
*ngIf="
layout === 'cnetcmpgnent' || layout === 'cnetcmpgnteams' || layout === 'cnetcmpgnind'
"
class="col-5 d-flex align-items-center justify-content-center"
>
<img
src="../../images/register-layout/usnews-360-badge.svg"
class="w-50 d-block"
alt="US News 360 Reviews Best Password Manager"
/>
</div>
<div
*ngIf="
layout === 'teams' ||
layout === 'teams1' ||
layout === 'teams2' ||
layout === 'enterprise' ||
layout === 'enterprise1' ||
layout === 'enterprise2' ||
layout === 'default'
"
class="col-5 d-flex align-items-center justify-content-center"
>
<img
src="../../images/register-layout/usnews-360-badge.svg"
class="w-50 d-block"
alt="US News 360 Reviews Best Password Manager"
/>
</div>
</div>
</form> </form>
</div> </div>

View File

@@ -1,8 +1,8 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators"; import { first } from "rxjs/operators";
import { RegisterComponent as BaseRegisterComponent } from "jslib-angular/components/register.component";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service"; import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service"; import { CryptoService } from "jslib-common/abstractions/crypto.service";
@@ -13,13 +13,9 @@ import { PasswordGenerationService } from "jslib-common/abstractions/passwordGen
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service"; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service"; import { StateService } from "jslib-common/abstractions/state.service";
import { PolicyData } from "jslib-common/models/data/policyData";
import { RegisterComponent as BaseRegisterComponent } from "jslib-angular/components/register.component";
import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions"; import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions";
import { Policy } from "jslib-common/models/domain/policy"; import { Policy } from "jslib-common/models/domain/policy";
import { PolicyData } from "jslib-common/models/data/policyData";
import { ReferenceEventRequest } from "jslib-common/models/request/referenceEventRequest"; import { ReferenceEventRequest } from "jslib-common/models/request/referenceEventRequest";
@Component({ @Component({

View File

@@ -3,7 +3,7 @@
<img class="mb-4 logo logo-themed" alt="Bitwarden" /> <img class="mb-4 logo logo-themed" alt="Bitwarden" />
<p class="text-center"> <p class="text-center">
<i <i
class="fa fa-spinner fa-spin fa-2x text-muted" class="bwi bwi-spinner bwi-spin bwi-2x text-muted"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>
@@ -27,7 +27,7 @@
[disabled]="actionPromise" [disabled]="actionPromise"
> >
<i <i
class="fa fa-spinner fa-spin" class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
*ngIf="continuing" *ngIf="continuing"
@@ -41,7 +41,7 @@
[disabled]="actionPromise" [disabled]="actionPromise"
> >
<i <i
class="fa fa-spinner fa-spin" class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
*ngIf="leaving" *ngIf="leaving"

View File

@@ -4,7 +4,7 @@
<p class="lead text-center mb-4">{{ "setMasterPassword" | i18n }}</p> <p class="lead text-center mb-4">{{ "setMasterPassword" | i18n }}</p>
<div class="card d-block"> <div class="card d-block">
<div class="card-body text-center" *ngIf="syncLoading"> <div class="card-body text-center" *ngIf="syncLoading">
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i> <i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }} {{ "loading" | i18n }}
</div> </div>
<div class="card-body" *ngIf="!syncLoading"> <div class="card-body" *ngIf="!syncLoading">
@@ -47,9 +47,9 @@
(click)="togglePassword(false)" (click)="togglePassword(false)"
> >
<i <i
class="fa fa-lg" class="bwi bwi-lg"
aria-hidden="true" aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }" [ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i> ></i>
</button> </button>
<div class="progress-bar invisible"></div> <div class="progress-bar invisible"></div>
@@ -76,9 +76,9 @@
(click)="togglePassword(true)" (click)="togglePassword(true)"
> >
<i <i
class="fa fa-lg" class="bwi bwi-lg"
aria-hidden="true" aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }" [ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i> ></i>
</button> </button>
</div> </div>
@@ -96,7 +96,7 @@
[disabled]="form.loading" [disabled]="form.loading"
> >
<i <i
class="fa fa-spinner fa-spin" class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>

View File

@@ -1,6 +1,7 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { SetPasswordComponent as BaseSetPasswordComponent } from "jslib-angular/components/set-password.component";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service"; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
@@ -11,8 +12,6 @@ import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service"; import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service"; import { SyncService } from "jslib-common/abstractions/sync.service";
import { SetPasswordComponent as BaseSetPasswordComponent } from "jslib-angular/components/set-password.component";
@Component({ @Component({
selector: "app-set-password", selector: "app-set-password",
templateUrl: "set-password.component.html", templateUrl: "set-password.component.html",

View File

@@ -10,7 +10,7 @@
<img class="logo mb-2 logo-themed" alt="Bitwarden" /> <img class="logo mb-2 logo-themed" alt="Bitwarden" />
<div class="card d-block mt-4"> <div class="card d-block mt-4">
<div class="card-body" *ngIf="loggingIn"> <div class="card-body" *ngIf="loggingIn">
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i> <i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }} {{ "loading" | i18n }}
</div> </div>
<div class="card-body" *ngIf="!loggingIn"> <div class="card-body" *ngIf="!loggingIn">
@@ -34,9 +34,9 @@
class="btn btn-primary btn-block btn-submit" class="btn btn-primary btn-block btn-submit"
[disabled]="form.loading" [disabled]="form.loading"
> >
<span> <i class="fa fa-sign-in" aria-hidden="true"></i> {{ "logIn" | i18n }} </span> <span> <i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ "logIn" | i18n }} </span>
<i <i
class="fa fa-spinner fa-spin" class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>

View File

@@ -1,8 +1,8 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators"; import { first } from "rxjs/operators";
import { SsoComponent as BaseSsoComponent } from "jslib-angular/components/sso.component";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service"; import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service"; import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
@@ -13,8 +13,6 @@ import { PasswordGenerationService } from "jslib-common/abstractions/passwordGen
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service"; import { StateService } from "jslib-common/abstractions/state.service";
import { SsoComponent as BaseSsoComponent } from "jslib-angular/components/sso.component";
@Component({ @Component({
selector: "app-sso", selector: "app-sso",
templateUrl: "sso.component.html", templateUrl: "sso.component.html",

View File

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

View File

@@ -94,7 +94,7 @@
</div> </div>
</ng-container> </ng-container>
<i <i
class="fa fa-spinner text-muted fa-spin pull-right" class="bwi bwi-spinner text-muted bwi-spin pull-right"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
*ngIf="form.loading && selectedProviderType === providerType.WebAuthn" *ngIf="form.loading && selectedProviderType === providerType.WebAuthn"
aria-hidden="true" aria-hidden="true"
@@ -114,6 +114,9 @@
<p>{{ "noTwoStepProviders2" | i18n }}</p> <p>{{ "noTwoStepProviders2" | i18n }}</p>
</ng-container> </ng-container>
<hr /> <hr />
<div [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
<div class="d-flex mb-3"> <div class="d-flex mb-3">
<button <button
type="submit" type="submit"
@@ -127,10 +130,10 @@
" "
> >
<span> <span>
<i class="fa fa-sign-in" aria-hidden="true"></i> {{ "continue" | i18n }} <i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ "continue" | i18n }}
</span> </span>
<i <i
class="fa fa-spinner fa-spin" class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>

View File

@@ -1,7 +1,8 @@
import { Component, ViewChild, ViewContainerRef } from "@angular/core"; import { Component, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { TwoFactorComponent as BaseTwoFactorComponent } from "jslib-angular/components/two-factor.component";
import { ModalService } from "jslib-angular/services/modal.service";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service"; import { AuthService } from "jslib-common/abstractions/auth.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service"; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
@@ -9,13 +10,9 @@ import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service"; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service"; import { StateService } from "jslib-common/abstractions/state.service";
import { TwoFactorService } from "jslib-common/abstractions/twoFactor.service";
import { ModalService } from "jslib-angular/services/modal.service";
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType"; import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
import { TwoFactorComponent as BaseTwoFactorComponent } from "jslib-angular/components/two-factor.component";
import { TwoFactorOptionsComponent } from "./two-factor-options.component"; import { TwoFactorOptionsComponent } from "./two-factor-options.component";
@Component({ @Component({
@@ -36,7 +33,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
environmentService: EnvironmentService, environmentService: EnvironmentService,
private modalService: ModalService, private modalService: ModalService,
route: ActivatedRoute, route: ActivatedRoute,
logService: LogService logService: LogService,
twoFactorService: TwoFactorService
) { ) {
super( super(
authService, authService,
@@ -48,7 +46,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
environmentService, environmentService,
stateService, stateService,
route, route,
logService logService,
twoFactorService
); );
this.onSuccessfulLoginNavigate = this.goAfterLogIn; this.onSuccessfulLoginNavigate = this.goAfterLogIn;
} }

View File

@@ -0,0 +1,90 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<div class="row justify-content-md-center mt-5">
<div class="col-4">
<p class="lead text-center mb-4">{{ "updateMasterPassword" | i18n }}</p>
<div class="card d-block">
<div class="card-body">
<app-callout type="warning">{{ "masterPasswordInvalidWarning" | i18n }} </app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
></app-callout>
<form
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
autocomplete="off"
>
<div class="row">
<div class="col-6">
<div class="form-group">
<label for="currentMasterPassword">{{ "currentMasterPass" | i18n }}</label>
<input
id="currentMasterPassword"
type="password"
name="MasterPasswordHash"
class="form-control"
[(ngModel)]="currentMasterPassword"
required
appInputVerbatim
/>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="form-group">
<label for="newMasterPassword">{{ "newMasterPass" | i18n }}</label>
<input
id="newMasterPassword"
type="password"
name="NewMasterPasswordHash"
class="form-control mb-1"
[(ngModel)]="masterPassword"
(input)="updatePasswordStrength()"
required
appInputVerbatim
autocomplete="new-password"
/>
<app-password-strength
[score]="masterPasswordScore"
[showText]="true"
></app-password-strength>
</div>
</div>
<div class="col-6">
<div class="form-group">
<label for="masterPasswordRetype">{{ "confirmNewMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="password"
name="MasterPasswordRetype"
class="form-control"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
autocomplete="new-password"
/>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span>{{ "changeMasterPassword" | i18n }}</span>
</button>
<button (click)="cancel()" type="button" class="btn btn-outline-secondary">
<span>{{ "cancel" | i18n }}</span>
</button>
</form>
</div>
</div>
</div>
</div>
</form>

View File

@@ -0,0 +1,48 @@
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { UpdatePasswordComponent as BaseUpdatePasswordComponent } from "jslib-angular/components/update-password.component";
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { UserVerificationService } from "jslib-common/abstractions/userVerification.service";
@Component({
selector: "app-update-password",
templateUrl: "update-password.component.html",
})
export class UpdatePasswordComponent extends BaseUpdatePasswordComponent {
constructor(
router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService,
policyService: PolicyService,
cryptoService: CryptoService,
messagingService: MessagingService,
apiService: ApiService,
logService: LogService,
stateService: StateService,
userVerificationService: UserVerificationService
) {
super(
router,
i18nService,
platformUtilsService,
passwordGenerationService,
policyService,
cryptoService,
messagingService,
apiService,
stateService,
userVerificationService,
logService
);
}
}

View File

@@ -36,9 +36,9 @@
(click)="togglePassword(false)" (click)="togglePassword(false)"
> >
<i <i
class="fa fa-lg" class="bwi bwi-lg"
aria-hidden="true" aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }" [ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i> ></i>
</button> </button>
<div class="progress-bar invisible"></div> <div class="progress-bar invisible"></div>
@@ -64,9 +64,9 @@
(click)="togglePassword(true)" (click)="togglePassword(true)"
> >
<i <i
class="fa fa-lg" class="bwi bwi-lg"
aria-hidden="true" aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }" [ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i> ></i>
</button> </button>
</div> </div>
@@ -84,7 +84,7 @@
[disabled]="form.loading" [disabled]="form.loading"
> >
<i <i
class="fa fa-spinner fa-spin" class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>

View File

@@ -1,5 +1,6 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "jslib-angular/components/update-temp-password.component";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service"; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
@@ -8,10 +9,8 @@ import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service"; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "jslib-angular/components/update-temp-password.component";
import { StateService } from "jslib-common/abstractions/state.service"; import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
@Component({ @Component({
selector: "app-update-temp-password", selector: "app-update-temp-password",

View File

@@ -3,7 +3,7 @@
<img class="mb-4 logo logo-themed" alt="Bitwarden" /> <img class="mb-4 logo logo-themed" alt="Bitwarden" />
<p class="text-center"> <p class="text-center">
<i <i
class="fa fa-spinner fa-spin fa-2x text-muted" class="bwi bwi-spinner bwi-spin bwi-2x text-muted"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>

View File

@@ -1,6 +1,5 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators"; import { first } from "rxjs/operators";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
@@ -8,7 +7,6 @@ import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service"; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service"; import { StateService } from "jslib-common/abstractions/state.service";
import { VerifyEmailRequest } from "jslib-common/models/request/verifyEmailRequest"; import { VerifyEmailRequest } from "jslib-common/models/request/verifyEmailRequest";
@Component({ @Component({

View File

@@ -18,7 +18,7 @@
> >
<span>{{ "deleteAccount" | i18n }}</span> <span>{{ "deleteAccount" | i18n }}</span>
<i <i
class="fa fa-spinner fa-spin" class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}" title="{{ 'loading' | i18n }}"
aria-hidden="true" aria-hidden="true"
></i> ></i>

View File

@@ -1,13 +1,11 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators"; import { first } from "rxjs/operators";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service"; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { VerifyDeleteRecoverRequest } from "jslib-common/models/request/verifyDeleteRecoverRequest"; import { VerifyDeleteRecoverRequest } from "jslib-common/models/request/verifyDeleteRecoverRequest";
@Component({ @Component({

View File

@@ -21,14 +21,10 @@ import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SearchService } from "jslib-common/abstractions/search.service"; import { SearchService } from "jslib-common/abstractions/search.service";
import { SettingsService } from "jslib-common/abstractions/settings.service"; import { SettingsService } from "jslib-common/abstractions/settings.service";
import { StateService } from "jslib-common/abstractions/state.service"; import { StateService } from "jslib-common/abstractions/state.service";
import { StorageService } from "jslib-common/abstractions/storage.service";
import { SyncService } from "jslib-common/abstractions/sync.service"; import { SyncService } from "jslib-common/abstractions/sync.service";
import { TokenService } from "jslib-common/abstractions/token.service"; import { TokenService } from "jslib-common/abstractions/token.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service"; import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { PolicyListService } from "./services/policy-list.service";
import { RouterService } from "./services/router.service";
import { DisableSendPolicy } from "./organizations/policies/disable-send.component"; import { DisableSendPolicy } from "./organizations/policies/disable-send.component";
import { MasterPasswordPolicy } from "./organizations/policies/master-password.component"; import { MasterPasswordPolicy } from "./organizations/policies/master-password.component";
import { PasswordGeneratorPolicy } from "./organizations/policies/password-generator.component"; import { PasswordGeneratorPolicy } from "./organizations/policies/password-generator.component";
@@ -38,6 +34,8 @@ import { ResetPasswordPolicy } from "./organizations/policies/reset-password.com
import { SendOptionsPolicy } from "./organizations/policies/send-options.component"; import { SendOptionsPolicy } from "./organizations/policies/send-options.component";
import { SingleOrgPolicy } from "./organizations/policies/single-org.component"; import { SingleOrgPolicy } from "./organizations/policies/single-org.component";
import { TwoFactorAuthenticationPolicy } from "./organizations/policies/two-factor-authentication.component"; import { TwoFactorAuthenticationPolicy } from "./organizations/policies/two-factor-authentication.component";
import { PolicyListService } from "./services/policy-list.service";
import { RouterService } from "./services/router.service";
const BroadcasterSubscriptionId = "AppComponent"; const BroadcasterSubscriptionId = "AppComponent";
const IdleTimeout = 60000 * 10; // 10 minutes const IdleTimeout = 60000 * 10; // 10 minutes
@@ -117,7 +115,7 @@ export class AppComponent implements OnDestroy, OnInit {
break; break;
case "syncCompleted": case "syncCompleted":
break; break;
case "upgradeOrganization": case "upgradeOrganization": {
const upgradeConfirmed = await this.platformUtilsService.showDialog( const upgradeConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("upgradeOrganizationDesc"), this.i18nService.t("upgradeOrganizationDesc"),
this.i18nService.t("upgradeOrganization"), this.i18nService.t("upgradeOrganization"),
@@ -133,7 +131,8 @@ export class AppComponent implements OnDestroy, OnInit {
]); ]);
} }
break; break;
case "premiumRequired": }
case "premiumRequired": {
const premiumConfirmed = await this.platformUtilsService.showDialog( const premiumConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("premiumRequiredDesc"), this.i18nService.t("premiumRequiredDesc"),
this.i18nService.t("premiumRequired"), this.i18nService.t("premiumRequired"),
@@ -144,7 +143,8 @@ export class AppComponent implements OnDestroy, OnInit {
this.router.navigate(["settings/premium"]); this.router.navigate(["settings/premium"]);
} }
break; break;
case "emailVerificationRequired": }
case "emailVerificationRequired": {
const emailVerificationConfirmed = await this.platformUtilsService.showDialog( const emailVerificationConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("emailVerificationRequiredDesc"), this.i18nService.t("emailVerificationRequiredDesc"),
this.i18nService.t("emailVerificationRequired"), this.i18nService.t("emailVerificationRequired"),
@@ -153,10 +153,11 @@ export class AppComponent implements OnDestroy, OnInit {
); );
if (emailVerificationConfirmed) { if (emailVerificationConfirmed) {
this.platformUtilsService.launchUri( this.platformUtilsService.launchUri(
"https://bitwarden.com/help/article/create-bitwarden-account/" "https://bitwarden.com/help/create-bitwarden-account/"
); );
} }
break; break;
}
case "showToast": case "showToast":
this.showToast(message); this.showToast(message);
break; break;
@@ -164,7 +165,6 @@ export class AppComponent implements OnDestroy, OnInit {
this.setFullWidth(); this.setFullWidth();
break; break;
case "convertAccountToKeyConnector": case "convertAccountToKeyConnector":
this.keyConnectorService.setConvertAccountRequired(true);
this.router.navigate(["/remove-password"]); this.router.navigate(["/remove-password"]);
break; break;
default: default:
@@ -211,7 +211,6 @@ export class AppComponent implements OnDestroy, OnInit {
await Promise.all([ await Promise.all([
this.eventService.clearEvents(), this.eventService.clearEvents(),
this.syncService.setLastSync(new Date(0)), this.syncService.setLastSync(new Date(0)),
this.tokenService.clearToken(),
this.cryptoService.clearKeys(), this.cryptoService.clearKeys(),
this.settingsService.clear(userId), this.settingsService.clear(userId),
this.cipherService.clear(userId), this.cipherService.clear(userId),

View File

@@ -1,9 +1,8 @@
import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { DragDropModule } from "@angular/cdk/drag-drop"; import { DragDropModule } from "@angular/cdk/drag-drop";
import { NgModule } from "@angular/core"; import { NgModule } from "@angular/core";
import { FormsModule } from "@angular/forms"; import { FormsModule } from "@angular/forms";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { BitwardenToastModule } from "jslib-angular/components/toastr.component"; import { BitwardenToastModule } from "jslib-angular/components/toastr.component";

View File

@@ -1,6 +1,5 @@
import { Directive, OnInit } from "@angular/core"; import { Directive, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators"; import { first } from "rxjs/operators";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
@@ -60,9 +59,7 @@ export abstract class BaseAcceptComponent implements OnInit {
errorMessage != null errorMessage != null
? this.i18nService.t(this.failedShortMessage, errorMessage) ? this.i18nService.t(this.failedShortMessage, errorMessage)
: this.i18nService.t(this.failedMessage); : this.i18nService.t(this.failedMessage);
this.platformUtilService.showToast("error", null, message, { this.platformUtilService.showToast("error", null, message, { timeout: 10000 });
timeout: 10000,
});
this.router.navigate(["/"]); this.router.navigate(["/"]);
} }

View File

@@ -4,11 +4,9 @@ import { ExportService } from "jslib-common/abstractions/export.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service"; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { EventView } from "jslib-common/models/view/eventView";
import { EventResponse } from "jslib-common/models/response/eventResponse"; import { EventResponse } from "jslib-common/models/response/eventResponse";
import { ListResponse } from "jslib-common/models/response/listResponse"; import { ListResponse } from "jslib-common/models/response/listResponse";
import { EventView } from "jslib-common/models/view/eventView";
import { EventService } from "src/app/services/event.service"; import { EventService } from "src/app/services/event.service";
@@ -19,7 +17,7 @@ export abstract class BaseEventsComponent {
events: EventView[]; events: EventView[];
start: string; start: string;
end: string; end: string;
dirtyDates: boolean = true; dirtyDates = true;
continuationToken: string; continuationToken: string;
refreshPromise: Promise<any>; refreshPromise: Promise<any>;
exportPromise: Promise<any>; exportPromise: Promise<any>;
@@ -110,13 +108,7 @@ export abstract class BaseEventsComponent {
endDate: string, endDate: string,
continuationToken: string continuationToken: string
): Promise<ListResponse<EventResponse>>; ): Promise<ListResponse<EventResponse>>;
protected abstract getUserName( protected abstract getUserName(r: EventResponse, userId: string): { name: string; email: string };
r: EventResponse,
userId: string
): {
name: string;
email: string;
};
protected async loadAndParseEvents( protected async loadAndParseEvents(
startDate: string, startDate: string,
@@ -144,10 +136,7 @@ export abstract class BaseEventsComponent {
}); });
}) })
); );
return { return { continuationToken: response.continuationToken, events: events };
continuationToken: response.continuationToken,
events: events,
};
} }
protected parseDates() { protected parseDates() {
@@ -181,13 +170,6 @@ export abstract class BaseEventsComponent {
const data = await this.exportService.getEventExport(events); const data = await this.exportService.getEventExport(events);
const fileName = this.exportService.getFileName(this.exportFileName, "csv"); const fileName = this.exportService.getFileName(this.exportFileName, "csv");
this.platformUtilsService.saveFile( this.platformUtilsService.saveFile(window, data, { type: "text/plain" }, fileName);
window,
data,
{
type: "text/plain",
},
fileName
);
} }
} }

View File

@@ -1,5 +1,9 @@
import { Directive, ViewChild, ViewContainerRef } from "@angular/core"; import { Directive, ViewChild, ViewContainerRef } from "@angular/core";
import { SearchPipe } from "jslib-angular/pipes/search.pipe";
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
import { ModalService } from "jslib-angular/services/modal.service";
import { ValidationService } from "jslib-angular/services/validation.service";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service"; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
@@ -7,24 +11,15 @@ import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SearchService } from "jslib-common/abstractions/search.service"; import { SearchService } from "jslib-common/abstractions/search.service";
import { StateService } from "jslib-common/abstractions/state.service"; import { StateService } from "jslib-common/abstractions/state.service";
import { ModalService } from "jslib-angular/services/modal.service";
import { ValidationService } from "jslib-angular/services/validation.service";
import { SearchPipe } from "jslib-angular/pipes/search.pipe";
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
import { OrganizationUserStatusType } from "jslib-common/enums/organizationUserStatusType"; import { OrganizationUserStatusType } from "jslib-common/enums/organizationUserStatusType";
import { OrganizationUserType } from "jslib-common/enums/organizationUserType"; import { OrganizationUserType } from "jslib-common/enums/organizationUserType";
import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType"; import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType";
import { ProviderUserType } from "jslib-common/enums/providerUserType"; import { ProviderUserType } from "jslib-common/enums/providerUserType";
import { Utils } from "jslib-common/misc/utils";
import { ListResponse } from "jslib-common/models/response/listResponse"; import { ListResponse } from "jslib-common/models/response/listResponse";
import { OrganizationUserUserDetailsResponse } from "jslib-common/models/response/organizationUserResponse"; import { OrganizationUserUserDetailsResponse } from "jslib-common/models/response/organizationUserResponse";
import { ProviderUserUserDetailsResponse } from "jslib-common/models/response/provider/providerUserResponse"; import { ProviderUserUserDetailsResponse } from "jslib-common/models/response/provider/providerUserResponse";
import { Utils } from "jslib-common/misc/utils";
import { UserConfirmComponent } from "../organizations/manage/user-confirm.component"; import { UserConfirmComponent } from "../organizations/manage/user-confirm.component";
type StatusType = OrganizationUserStatusType | ProviderUserStatusType; type StatusType = OrganizationUserStatusType | ProviderUserStatusType;
@@ -35,10 +30,7 @@ const MaxCheckedCount = 500;
export abstract class BasePeopleComponent< export abstract class BasePeopleComponent<
UserType extends ProviderUserUserDetailsResponse | OrganizationUserUserDetailsResponse UserType extends ProviderUserUserDetailsResponse | OrganizationUserUserDetailsResponse
> { > {
@ViewChild("confirmTemplate", { @ViewChild("confirmTemplate", { read: ViewContainerRef, static: true })
read: ViewContainerRef,
static: true,
})
confirmModalRef: ViewContainerRef; confirmModalRef: ViewContainerRef;
get allCount() { get allCount() {

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