diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..83e7e640 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,28 @@ +## Type of change +- [ ] Bug fix +- [ ] New feature development +- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) +- [ ] Build/deploy pipeline (DevOps) +- [ ] Other + +## Objective + + + + +## Code changes + + + +* **file.ext:** Description of what was changed and why + +## Testing requirements + + + + +## Before you submit +- [ ] I have checked for **linting** errors (`npm run lint`) (required) +- [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required) +- [ ] This change requires a **documentation update** (notify the documentation team) +- [ ] This change has particular **deployment requirements** (notify the DevOps team) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 54c223fe..e58289c8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,10 +1,12 @@ +--- name: Build on: push jobs: cloc: - runs-on: ubuntu-latest + name: CLOC + runs-on: ubuntu-20.04 steps: - name: Checkout repo @@ -18,13 +20,14 @@ jobs: - name: Print lines of code run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git - build: + build: + name: Build jslib runs-on: ${{ matrix.os }} strategy: matrix: - os: [windows-latest, macos-latest, ubuntu-latest] + os: [windows-2019, macos-10.15, ubuntu-20.04] steps: - name: Set up Node @@ -65,3 +68,48 @@ jobs: with: name: test-coverage path: coverage/ + + - name: Run Node tests + run: npm run test:node + + check-failures: + name: Check for failures + if: always() + runs-on: ubuntu-20.04 + needs: + - cloc + - build + steps: + - name: Check if any job failed + if: ${{ (github.ref == 'refs/heads/master') || (github.ref == 'refs/heads/rc') }} + env: + CLOC_STATUS: ${{ needs.cloc.result }} + BUILD_STATUS: ${{ needs.build.result }} + run: | + if [ "$CLOC_STATUS" = "failure" ]; then + exit 1 + elif [ "$BUILD_STATUS" = "failure" ]; then + exit 1 + fi + + - name: Login to Azure - Prod Subscription + uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a + if: failure() + with: + creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} + + - name: Retrieve secrets + id: retrieve-secrets + uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403 + if: failure() + with: + keyvault: "bitwarden-prod-kv" + secrets: "devops-alerts-slack-webhook-url" + + - name: Notify Slack on failure + uses: act10ns/slack@e4e71685b9b239384b0f676a63c32367f59c2522 # v1.2.2 + if: failure() + env: + SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }} + with: + status: ${{ job.status }} diff --git a/angular/package-lock.json b/angular/package-lock.json index f5e83454..405539ab 100644 --- a/angular/package-lock.json +++ b/angular/package-lock.json @@ -9,40 +9,39 @@ "version": "0.0.0", "license": "GPL-3.0", "dependencies": { - "@angular/animations": "^11.2.11", - "@angular/cdk": "^11.2.10", - "@angular/common": "^11.2.11", - "@angular/compiler": "^11.2.11", - "@angular/core": "^11.2.11", - "@angular/forms": "^11.2.11", - "@angular/platform-browser": "^11.2.11", - "@angular/platform-browser-dynamic": "^11.2.11", - "@angular/router": "^11.2.11", + "@angular/animations": "^12.2.13", + "@angular/cdk": "^12.2.13", + "@angular/common": "^12.2.13", + "@angular/compiler": "^12.2.13", + "@angular/core": "^12.2.13", + "@angular/forms": "^12.2.13", + "@angular/platform-browser": "^12.2.13", + "@angular/platform-browser-dynamic": "^12.2.13", + "@angular/router": "^12.2.13", "@bitwarden/jslib-common": "file:../common", "duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git", - "rxjs": "6.6.7", + "rxjs": "^7.4.0", "tldjs": "^2.3.1", "zone.js": "0.11.4" }, "devDependencies": { "@types/duo_web_sdk": "^2.7.1", "rimraf": "^3.0.2", - "typescript": "4.1.5" + "typescript": "4.3.5" } }, "../common": { - "name": "@bitwarden/jslib-common", "version": "0.0.0", "license": "GPL-3.0", "dependencies": { - "@microsoft/signalr": "3.1.13", - "@microsoft/signalr-protocol-msgpack": "3.1.13", + "@microsoft/signalr": "5.0.10", + "@microsoft/signalr-protocol-msgpack": "5.0.10", "big-integer": "1.6.48", "browser-hrtime": "^1.1.8", "lunr": "^2.3.9", "node-forge": "^0.10.0", "papaparse": "^5.3.0", - "rxjs": "6.6.7", + "rxjs": "^7.4.0", "tldjs": "^2.3.1", "zxcvbn": "^4.4.2" }, @@ -54,92 +53,111 @@ "@types/tldjs": "^2.3.0", "@types/zxcvbn": "^4.4.1", "rimraf": "^3.0.2", - "typescript": "4.1.5" + "typescript": "4.3.5" } }, "node_modules/@angular/animations": { - "version": "11.2.14", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-11.2.14.tgz", - "integrity": "sha512-Heq/nNrCmb3jbkusu+BQszOecfFI/31Oxxj+CDQkqqYpBcswk6bOJLoEE472o+vmgxaXbgeflU9qbIiCQhpMFA==", + "version": "12.2.14", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-12.2.14.tgz", + "integrity": "sha512-1BR5u32auVePvXNNP96DB2008V+Ku0OGqeZQl2h4XA9xzES/Zk5WllIJZXqRmWMRBVARfXsfb0RdMty9gcaVjA==", "dependencies": { - "tslib": "^2.0.0" + "tslib": "^2.2.0" + }, + "engines": { + "node": "^12.14.1 || >=14.0.0" }, "peerDependencies": { - "@angular/core": "11.2.14" + "@angular/core": "12.2.14" } }, "node_modules/@angular/cdk": { - "version": "11.2.13", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-11.2.13.tgz", - "integrity": "sha512-FkE4iCwoLbQxLDUOjV1I7M/6hmpyb7erAjEdWgch7nGRNxF1hqX5Bqf1lvLFKPNCbx5NRI5K7YVAdIUQUR8vug==", + "version": "12.2.13", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.2.13.tgz", + "integrity": "sha512-zSKRhECyFqhingIeyRInIyTvYErt4gWo+x5DQr0b7YLUbU8DZSwWnG4w76Ke2s4U8T7ry1jpJBHoX/e8YBpGMg==", "dependencies": { - "tslib": "^2.0.0" + "tslib": "^2.2.0" }, "optionalDependencies": { "parse5": "^5.0.0" }, "peerDependencies": { - "@angular/common": "^11.0.0 || ^12.0.0-0", - "@angular/core": "^11.0.0 || ^12.0.0-0" + "@angular/common": "^12.0.0 || ^13.0.0-0", + "@angular/core": "^12.0.0 || ^13.0.0-0", + "rxjs": "^6.5.3 || ^7.0.0" } }, "node_modules/@angular/common": { - "version": "11.2.14", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-11.2.14.tgz", - "integrity": "sha512-ZSLV/3j7eCTyLf/8g4yBFLWySjiLz3vLJAGWscYoUpnJWMnug1VRu6zoF/COxCbtORgE+Wz6K0uhfS6MziBGVw==", + "version": "12.2.14", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-12.2.14.tgz", + "integrity": "sha512-ffYUYdwZETmFJw0AcWY30WsaWBhJxj/zSmFXWjgEGEGZH56zwbbNwfMZOYZ1jz4haAVxGu+TdXsOl2yMGzN7jQ==", "dependencies": { - "tslib": "^2.0.0" + "tslib": "^2.2.0" + }, + "engines": { + "node": "^12.14.1 || >=14.0.0" }, "peerDependencies": { - "@angular/core": "11.2.14", - "rxjs": "^6.5.3" + "@angular/core": "12.2.14", + "rxjs": "^6.5.3 || ^7.0.0" } }, "node_modules/@angular/compiler": { - "version": "11.2.14", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-11.2.14.tgz", - "integrity": "sha512-XBOK3HgA+/y6Cz7kOX4zcJYmgJ264XnfcbXUMU2cD7Ac+mbNhLPKohWrEiSWalfcjnpf5gRfufQrQP7lpAGu0A==", + "version": "12.2.14", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-12.2.14.tgz", + "integrity": "sha512-dwmZi+n66IUzRFlGWu9mjXq170ZEsaDvlNLZzaPgs6vZTa4Kt7PWvIF/Y7TMvnVv/uqNG6kOhfmOkf6rfz1Gjg==", "dependencies": { - "tslib": "^2.0.0" + "tslib": "^2.2.0" + }, + "engines": { + "node": "^12.14.1 || >=14.0.0" } }, "node_modules/@angular/core": { - "version": "11.2.14", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-11.2.14.tgz", - "integrity": "sha512-vpR4XqBGitk1Faph37CSpemwIYTmJ3pdIVNoHKP6jLonpWu+0azkchf0f7oD8/2ivj2F81opcIw0tcsy/D/5Vg==", + "version": "12.2.14", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-12.2.14.tgz", + "integrity": "sha512-dlVk7yqUHL2R/eCmM8LsWuxhEBfzg0y1zHt0UqCuFwlCoiw+IG4HFy4OlZEUw9NUEZJSv0aDv3sWqxLkvK5vvg==", "dependencies": { - "tslib": "^2.0.0" + "tslib": "^2.2.0" + }, + "engines": { + "node": "^12.14.1 || >=14.0.0" }, "peerDependencies": { - "rxjs": "^6.5.3", - "zone.js": "^0.10.2 || ^0.11.3" + "rxjs": "^6.5.3 || ^7.0.0", + "zone.js": "~0.11.4" } }, "node_modules/@angular/forms": { - "version": "11.2.14", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-11.2.14.tgz", - "integrity": "sha512-4LWqY6KEIk1AZQFnk+4PJSOCamlD4tumuVN06gO4D0dZo9Cx+GcvW6pM6N0CPubRvPs3sScCnu20WT11HNWC1w==", + "version": "12.2.14", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-12.2.14.tgz", + "integrity": "sha512-/9/gSJUBXVRVdRnzgJnALAQZYJATuGDMkFC9ms9DEMG4PMAhe9x4if1lJjN6noz5RAom3qNuVBNWaYAPUxlcBQ==", "dependencies": { - "tslib": "^2.0.0" + "tslib": "^2.2.0" + }, + "engines": { + "node": "^12.14.1 || >=14.0.0" }, "peerDependencies": { - "@angular/common": "11.2.14", - "@angular/core": "11.2.14", - "@angular/platform-browser": "11.2.14", - "rxjs": "^6.5.3" + "@angular/common": "12.2.14", + "@angular/core": "12.2.14", + "@angular/platform-browser": "12.2.14", + "rxjs": "^6.5.3 || ^7.0.0" } }, "node_modules/@angular/platform-browser": { - "version": "11.2.14", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-11.2.14.tgz", - "integrity": "sha512-fb7b7ss/gRoP8wLAN17W62leMgjynuyjEPU2eUoAAazsG9f2cgM+z3rK29GYncDVyYQxZUZYnjSqvL6GSXx86A==", + "version": "12.2.14", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-12.2.14.tgz", + "integrity": "sha512-fWcE2rnJ3ZCISa1oPfsIDV7FBZBoLFEdDuMXAiDYqDPKvF/E5U5nHrS+K4SlLAi094bMobtTOReNWl/Ienniyw==", "dependencies": { - "tslib": "^2.0.0" + "tslib": "^2.2.0" + }, + "engines": { + "node": "^12.14.1 || >=14.0.0" }, "peerDependencies": { - "@angular/animations": "11.2.14", - "@angular/common": "11.2.14", - "@angular/core": "11.2.14" + "@angular/animations": "12.2.14", + "@angular/common": "12.2.14", + "@angular/core": "12.2.14" }, "peerDependenciesMeta": { "@angular/animations": { @@ -148,31 +166,37 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "11.2.14", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-11.2.14.tgz", - "integrity": "sha512-TWTPdFs6iBBcp+/YMsgCRQwdHpWGq8KjeJDJ2tfatGgBD3Gqt2YaHOMST1zPW6RkrmupytTejuVqXzeaKWFxuw==", + "version": "12.2.14", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-12.2.14.tgz", + "integrity": "sha512-0NPF7mS91Tct8rBmOLZPmnLSuS4kbLHXo6eTgrg80OC0vlzBiQwGDVW4X3KncCoX9CpevaGJCdSMc+uPNsFOUQ==", "dependencies": { - "tslib": "^2.0.0" + "tslib": "^2.2.0" + }, + "engines": { + "node": "^12.14.1 || >=14.0.0" }, "peerDependencies": { - "@angular/common": "11.2.14", - "@angular/compiler": "11.2.14", - "@angular/core": "11.2.14", - "@angular/platform-browser": "11.2.14" + "@angular/common": "12.2.14", + "@angular/compiler": "12.2.14", + "@angular/core": "12.2.14", + "@angular/platform-browser": "12.2.14" } }, "node_modules/@angular/router": { - "version": "11.2.14", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-11.2.14.tgz", - "integrity": "sha512-3aYBmj+zrEL9yf/ntIQxHIYaWShZOBKP3U07X2mX+TPMpGlvHDnR7L6bWhQVZwewzMMz7YVR16ldg50IFuAlfA==", + "version": "12.2.14", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-12.2.14.tgz", + "integrity": "sha512-yP5grSnqBvc4vNhtYdcxDgDYIebUKs5f0xyFkUJM5030UnQ0CV45tBsSxHMkQbPZucIfOuxpRy8xy5+4GizuwQ==", "dependencies": { - "tslib": "^2.0.0" + "tslib": "^2.2.0" + }, + "engines": { + "node": "^12.14.1 || >=14.0.0" }, "peerDependencies": { - "@angular/common": "11.2.14", - "@angular/core": "11.2.14", - "@angular/platform-browser": "11.2.14", - "rxjs": "^6.5.3" + "@angular/common": "12.2.14", + "@angular/core": "12.2.14", + "@angular/platform-browser": "12.2.14", + "rxjs": "^6.5.3 || ^7.0.0" } }, "node_modules/@bitwarden/jslib-common": { @@ -219,9 +243,9 @@ "dev": true }, "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -311,20 +335,17 @@ } }, "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", + "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" + "tslib": "~2.1.0" } }, "node_modules/rxjs/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" }, "node_modules/tldjs": { "version": "2.3.1", @@ -339,14 +360,14 @@ } }, "node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "node_modules/typescript": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz", - "integrity": "sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -373,83 +394,83 @@ }, "dependencies": { "@angular/animations": { - "version": "11.2.14", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-11.2.14.tgz", - "integrity": "sha512-Heq/nNrCmb3jbkusu+BQszOecfFI/31Oxxj+CDQkqqYpBcswk6bOJLoEE472o+vmgxaXbgeflU9qbIiCQhpMFA==", + "version": "12.2.14", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-12.2.14.tgz", + "integrity": "sha512-1BR5u32auVePvXNNP96DB2008V+Ku0OGqeZQl2h4XA9xzES/Zk5WllIJZXqRmWMRBVARfXsfb0RdMty9gcaVjA==", "requires": { - "tslib": "^2.0.0" + "tslib": "^2.2.0" } }, "@angular/cdk": { - "version": "11.2.13", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-11.2.13.tgz", - "integrity": "sha512-FkE4iCwoLbQxLDUOjV1I7M/6hmpyb7erAjEdWgch7nGRNxF1hqX5Bqf1lvLFKPNCbx5NRI5K7YVAdIUQUR8vug==", + "version": "12.2.13", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.2.13.tgz", + "integrity": "sha512-zSKRhECyFqhingIeyRInIyTvYErt4gWo+x5DQr0b7YLUbU8DZSwWnG4w76Ke2s4U8T7ry1jpJBHoX/e8YBpGMg==", "requires": { "parse5": "^5.0.0", - "tslib": "^2.0.0" + "tslib": "^2.2.0" } }, "@angular/common": { - "version": "11.2.14", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-11.2.14.tgz", - "integrity": "sha512-ZSLV/3j7eCTyLf/8g4yBFLWySjiLz3vLJAGWscYoUpnJWMnug1VRu6zoF/COxCbtORgE+Wz6K0uhfS6MziBGVw==", + "version": "12.2.14", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-12.2.14.tgz", + "integrity": "sha512-ffYUYdwZETmFJw0AcWY30WsaWBhJxj/zSmFXWjgEGEGZH56zwbbNwfMZOYZ1jz4haAVxGu+TdXsOl2yMGzN7jQ==", "requires": { - "tslib": "^2.0.0" + "tslib": "^2.2.0" } }, "@angular/compiler": { - "version": "11.2.14", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-11.2.14.tgz", - "integrity": "sha512-XBOK3HgA+/y6Cz7kOX4zcJYmgJ264XnfcbXUMU2cD7Ac+mbNhLPKohWrEiSWalfcjnpf5gRfufQrQP7lpAGu0A==", + "version": "12.2.14", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-12.2.14.tgz", + "integrity": "sha512-dwmZi+n66IUzRFlGWu9mjXq170ZEsaDvlNLZzaPgs6vZTa4Kt7PWvIF/Y7TMvnVv/uqNG6kOhfmOkf6rfz1Gjg==", "requires": { - "tslib": "^2.0.0" + "tslib": "^2.2.0" } }, "@angular/core": { - "version": "11.2.14", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-11.2.14.tgz", - "integrity": "sha512-vpR4XqBGitk1Faph37CSpemwIYTmJ3pdIVNoHKP6jLonpWu+0azkchf0f7oD8/2ivj2F81opcIw0tcsy/D/5Vg==", + "version": "12.2.14", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-12.2.14.tgz", + "integrity": "sha512-dlVk7yqUHL2R/eCmM8LsWuxhEBfzg0y1zHt0UqCuFwlCoiw+IG4HFy4OlZEUw9NUEZJSv0aDv3sWqxLkvK5vvg==", "requires": { - "tslib": "^2.0.0" + "tslib": "^2.2.0" } }, "@angular/forms": { - "version": "11.2.14", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-11.2.14.tgz", - "integrity": "sha512-4LWqY6KEIk1AZQFnk+4PJSOCamlD4tumuVN06gO4D0dZo9Cx+GcvW6pM6N0CPubRvPs3sScCnu20WT11HNWC1w==", + "version": "12.2.14", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-12.2.14.tgz", + "integrity": "sha512-/9/gSJUBXVRVdRnzgJnALAQZYJATuGDMkFC9ms9DEMG4PMAhe9x4if1lJjN6noz5RAom3qNuVBNWaYAPUxlcBQ==", "requires": { - "tslib": "^2.0.0" + "tslib": "^2.2.0" } }, "@angular/platform-browser": { - "version": "11.2.14", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-11.2.14.tgz", - "integrity": "sha512-fb7b7ss/gRoP8wLAN17W62leMgjynuyjEPU2eUoAAazsG9f2cgM+z3rK29GYncDVyYQxZUZYnjSqvL6GSXx86A==", + "version": "12.2.14", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-12.2.14.tgz", + "integrity": "sha512-fWcE2rnJ3ZCISa1oPfsIDV7FBZBoLFEdDuMXAiDYqDPKvF/E5U5nHrS+K4SlLAi094bMobtTOReNWl/Ienniyw==", "requires": { - "tslib": "^2.0.0" + "tslib": "^2.2.0" } }, "@angular/platform-browser-dynamic": { - "version": "11.2.14", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-11.2.14.tgz", - "integrity": "sha512-TWTPdFs6iBBcp+/YMsgCRQwdHpWGq8KjeJDJ2tfatGgBD3Gqt2YaHOMST1zPW6RkrmupytTejuVqXzeaKWFxuw==", + "version": "12.2.14", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-12.2.14.tgz", + "integrity": "sha512-0NPF7mS91Tct8rBmOLZPmnLSuS4kbLHXo6eTgrg80OC0vlzBiQwGDVW4X3KncCoX9CpevaGJCdSMc+uPNsFOUQ==", "requires": { - "tslib": "^2.0.0" + "tslib": "^2.2.0" } }, "@angular/router": { - "version": "11.2.14", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-11.2.14.tgz", - "integrity": "sha512-3aYBmj+zrEL9yf/ntIQxHIYaWShZOBKP3U07X2mX+TPMpGlvHDnR7L6bWhQVZwewzMMz7YVR16ldg50IFuAlfA==", + "version": "12.2.14", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-12.2.14.tgz", + "integrity": "sha512-yP5grSnqBvc4vNhtYdcxDgDYIebUKs5f0xyFkUJM5030UnQ0CV45tBsSxHMkQbPZucIfOuxpRy8xy5+4GizuwQ==", "requires": { - "tslib": "^2.0.0" + "tslib": "^2.2.0" } }, "@bitwarden/jslib-common": { "version": "file:../common", "requires": { - "@microsoft/signalr": "3.1.13", - "@microsoft/signalr-protocol-msgpack": "3.1.13", + "@microsoft/signalr": "5.0.10", + "@microsoft/signalr-protocol-msgpack": "5.0.10", "@types/lunr": "^2.3.3", "@types/node": "^14.17.1", "@types/node-forge": "^0.9.7", @@ -462,9 +483,9 @@ "node-forge": "^0.10.0", "papaparse": "^5.3.0", "rimraf": "^3.0.2", - "rxjs": "6.6.7", + "rxjs": "^7.4.0", "tldjs": "^2.3.1", - "typescript": "4.1.5", + "typescript": "4.3.5", "zxcvbn": "^4.4.2" } }, @@ -507,9 +528,9 @@ "dev": true }, "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -581,17 +602,17 @@ } }, "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", + "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", "requires": { - "tslib": "^1.9.0" + "tslib": "~2.1.0" }, "dependencies": { "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" } } }, @@ -604,14 +625,14 @@ } }, "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "typescript": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz", - "integrity": "sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", "dev": true }, "wrappy": { diff --git a/angular/package.json b/angular/package.json index 3425171c..d1d5f64c 100644 --- a/angular/package.json +++ b/angular/package.json @@ -22,21 +22,21 @@ "devDependencies": { "@types/duo_web_sdk": "^2.7.1", "rimraf": "^3.0.2", - "typescript": "4.1.5" + "typescript": "4.3.5" }, "dependencies": { - "@angular/animations": "^11.2.11", - "@angular/cdk": "^11.2.10", - "@angular/common": "^11.2.11", - "@angular/compiler": "^11.2.11", - "@angular/core": "^11.2.11", - "@angular/forms": "^11.2.11", - "@angular/platform-browser": "^11.2.11", - "@angular/platform-browser-dynamic": "^11.2.11", - "@angular/router": "^11.2.11", + "@angular/animations": "^12.2.13", + "@angular/cdk": "^12.2.13", + "@angular/common": "^12.2.13", + "@angular/compiler": "^12.2.13", + "@angular/core": "^12.2.13", + "@angular/forms": "^12.2.13", + "@angular/platform-browser": "^12.2.13", + "@angular/platform-browser-dynamic": "^12.2.13", + "@angular/router": "^12.2.13", "@bitwarden/jslib-common": "file:../common", "duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git", - "rxjs": "6.6.7", + "rxjs": "^7.4.0", "tldjs": "^2.3.1", "zone.js": "0.11.4" } diff --git a/angular/src/components/add-edit-custom-fields.component.ts b/angular/src/components/add-edit-custom-fields.component.ts index adf47f17..71040ac4 100644 --- a/angular/src/components/add-edit-custom-fields.component.ts +++ b/angular/src/components/add-edit-custom-fields.component.ts @@ -1,6 +1,8 @@ import { Directive, Input, + OnChanges, + SimpleChanges, } from '@angular/core'; import { @@ -18,13 +20,17 @@ import { CipherType } from 'jslib-common/enums/cipherType'; import { EventType } from 'jslib-common/enums/eventType'; import { FieldType } from 'jslib-common/enums/fieldType'; +import { Utils } from 'jslib-common/misc/utils'; + @Directive() -export class AddEditCustomFieldsComponent { +export class AddEditCustomFieldsComponent implements OnChanges { @Input() cipher: CipherView; + @Input() thisCipherType: CipherType; @Input() editMode: boolean; addFieldType: FieldType = FieldType.Text; addFieldTypeOptions: any[]; + addFieldLinkedTypeOption: any; linkedFieldOptions: any[] = []; cipherType = CipherType; @@ -37,6 +43,17 @@ export class AddEditCustomFieldsComponent { { name: i18nService.t('cfTypeHidden'), value: FieldType.Hidden }, { name: i18nService.t('cfTypeBoolean'), value: FieldType.Boolean }, ]; + this.addFieldLinkedTypeOption = { name: this.i18nService.t('cfTypeLinked'), value: FieldType.Linked }; + } + + ngOnChanges(changes: SimpleChanges) { + if (changes.thisCipherType != null) { + this.setLinkedFieldOptions(); + + if (!changes.thisCipherType.firstChange) { + this.resetCipherLinkedFields(); + } + } } addField() { @@ -48,6 +65,10 @@ export class AddEditCustomFieldsComponent { f.type = this.addFieldType; f.newField = true; + if (f.type === FieldType.Linked) { + f.linkedId = this.linkedFieldOptions[0].value; + } + this.cipher.fields.push(f); } @@ -73,4 +94,31 @@ export class AddEditCustomFieldsComponent { drop(event: CdkDragDrop) { moveItemInArray(this.cipher.fields, event.previousIndex, event.currentIndex); } + + private setLinkedFieldOptions() { + if (this.cipher.linkedFieldOptions == null) { + return; + } + + const options: any = []; + this.cipher.linkedFieldOptions.forEach((linkedFieldOption, id) => + options.push({ name: this.i18nService.t(linkedFieldOption.i18nKey), value: id })); + this.linkedFieldOptions = options.sort(Utils.getSortFunction(this.i18nService, 'name')); + } + + private resetCipherLinkedFields() { + if (this.cipher.fields == null || this.cipher.fields.length === 0) { + return; + } + + // Delete any Linked custom fields if the item type does not support them + if (this.cipher.linkedFieldOptions == null) { + this.cipher.fields = this.cipher.fields.filter(f => f.type !== FieldType.Linked); + return; + } + + this.cipher.fields + .filter(f => f.type === FieldType.Linked) + .forEach(f => f.linkedId = this.linkedFieldOptions[0].value); + } } diff --git a/angular/src/components/add-edit.component.ts b/angular/src/components/add-edit.component.ts index 837a0279..edb0b4ae 100644 --- a/angular/src/components/add-edit.component.ts +++ b/angular/src/components/add-edit.component.ts @@ -20,7 +20,9 @@ import { CollectionService } from 'jslib-common/abstractions/collection.service' import { EventService } from 'jslib-common/abstractions/event.service'; import { FolderService } from 'jslib-common/abstractions/folder.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 { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.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'; @@ -79,6 +81,7 @@ export class AddEditComponent implements OnInit { currentDate = new Date(); allowPersonal = true; reprompt: boolean = false; + canUseReprompt: boolean = true; protected writeableCollections: CollectionView[]; private previousCipherId: string; @@ -88,7 +91,8 @@ export class AddEditComponent implements OnInit { protected auditService: AuditService, protected stateService: StateService, protected userService: UserService, protected collectionService: CollectionService, protected messagingService: MessagingService, protected eventService: EventService, - protected policyService: PolicyService) { + protected policyService: PolicyService, protected passwordRepromptService: PasswordRepromptService, + private logService: LogService) { this.typeOptions = [ { name: i18nService.t('typeLogin'), value: CipherType.Login }, { name: i18nService.t('typeCard'), value: CipherType.Card }, @@ -150,21 +154,29 @@ export class AddEditComponent implements OnInit { } async init() { - const myEmail = await this.userService.getEmail(); - this.ownershipOptions.push({ name: myEmail, value: null }); + if (this.ownershipOptions.length) { + this.ownershipOptions = []; + } + if (await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership)) { + this.allowPersonal = false; + } else { + const myEmail = await this.userService.getEmail(); + this.ownershipOptions.push({ name: myEmail, value: null }); + } + const orgs = await this.userService.getAllOrganizations(); orgs.sort(Utils.getSortFunction(this.i18nService, 'name')).forEach(o => { if (o.enabled && o.status === OrganizationUserStatusType.Confirmed) { this.ownershipOptions.push({ name: o.name, value: o.id }); } }); - - if (this.allowPersonal && await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership)) { - this.allowPersonal = false; - this.ownershipOptions.splice(0, 1); + if (!this.allowPersonal) { + this.organizationId = this.ownershipOptions[0].value; } this.writeableCollections = await this.loadCollections(); + + this.canUseReprompt = await this.passwordRepromptService.enabled(); } async load() { @@ -280,7 +292,9 @@ export class AddEditComponent implements OnInit { this.onSavedCipher.emit(this.cipher); this.messagingService.send(this.editMode && !this.cloneMode ? 'editedCipher' : 'addedCipher'); return true; - } catch { } + } catch (e) { + this.logService.error(e); + } return false; } @@ -343,7 +357,9 @@ export class AddEditComponent implements OnInit { this.i18nService.t(this.cipher.isDeleted ? 'permanentlyDeletedItem' : 'deletedItem')); this.onDeletedCipher.emit(this.cipher); this.messagingService.send(this.cipher.isDeleted ? 'permanentlyDeletedCipher' : 'deletedCipher'); - } catch { } + } catch (e) { + this.logService.error(e); + } return true; } @@ -366,7 +382,9 @@ export class AddEditComponent implements OnInit { this.platformUtilsService.showToast('success', null, this.i18nService.t('restoredItem')); this.onRestoredCipher.emit(this.cipher); this.messagingService.send('restoredCipher'); - } catch { } + } catch (e) { + this.logService.error(e); + } return true; } diff --git a/angular/src/components/attachments.component.ts b/angular/src/components/attachments.component.ts index a4f6b65c..dcbaf19b 100644 --- a/angular/src/components/attachments.component.ts +++ b/angular/src/components/attachments.component.ts @@ -10,6 +10,7 @@ import { ApiService } from 'jslib-common/abstractions/api.service'; import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { I18nService } from 'jslib-common/abstractions/i18n.service'; +import { LogService } from 'jslib-common/abstractions/log.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { UserService } from 'jslib-common/abstractions/user.service'; @@ -38,7 +39,7 @@ export class AttachmentsComponent implements OnInit { constructor(protected cipherService: CipherService, protected i18nService: I18nService, protected cryptoService: CryptoService, protected userService: UserService, protected platformUtilsService: PlatformUtilsService, protected apiService: ApiService, - protected win: Window) { } + protected win: Window, private logService: LogService) { } async ngOnInit() { await this.init(); @@ -71,7 +72,9 @@ export class AttachmentsComponent implements OnInit { this.cipher = await this.cipherDomain.decrypt(); this.platformUtilsService.showToast('success', null, this.i18nService.t('attachmentSaved')); this.onUploadedAttachment.emit(); - } catch { } + } catch (e) { + this.logService.error(e); + } // reset file input // ref: https://stackoverflow.com/a/20552042 @@ -100,7 +103,9 @@ export class AttachmentsComponent implements OnInit { if (i > -1) { this.cipher.attachments.splice(i, 1); } - } catch { } + } catch (e) { + this.logService.error(e); + } this.deletePromises[attachment.id] = null; this.onDeletedAttachment.emit(); @@ -226,7 +231,9 @@ export class AttachmentsComponent implements OnInit { a.downloading = false; }); await this.reuploadPromises[attachment.id]; - } catch { } + } catch (e) { + this.logService.error(e); + } } protected loadCipher() { diff --git a/angular/src/components/avatar.component.ts b/angular/src/components/avatar.component.ts new file mode 100644 index 00000000..1760c521 --- /dev/null +++ b/angular/src/components/avatar.component.ts @@ -0,0 +1,138 @@ +import { + Component, + Input, + OnChanges, + OnInit, +} from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; + +import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; +import { StateService } from 'jslib-common/abstractions/state.service'; + +import { Utils } from 'jslib-common/misc/utils'; + +@Component({ + selector: 'app-avatar', + template: '', +}) +export class AvatarComponent implements OnChanges, OnInit { + @Input() data: string; + @Input() email: string; + @Input() size = 45; + @Input() charCount = 2; + @Input() textColor = '#ffffff'; + @Input() fontSize = 20; + @Input() fontWeight = 300; + @Input() dynamic = false; + @Input() circle = false; + + src: string; + + constructor(public sanitizer: DomSanitizer, private cryptoFunctionService: CryptoFunctionService, + private stateService: StateService) { } + + ngOnInit() { + if (!this.dynamic) { + this.generate(); + } + } + + ngOnChanges() { + if (this.dynamic) { + this.generate(); + } + } + + private async generate() { + const enableGravatars = await this.stateService.get('enableGravatars'); + if (enableGravatars && this.email != null) { + const hashBytes = await this.cryptoFunctionService.hash(this.email.toLowerCase().trim(), 'md5'); + const hash = Utils.fromBufferToHex(hashBytes).toLowerCase(); + this.src = 'https://www.gravatar.com/avatar/' + hash + '?s=' + this.size + '&r=pg&d=retro'; + } else { + let chars: string = null; + const upperData = this.data.toUpperCase(); + + if (this.charCount > 1) { + chars = this.getFirstLetters(upperData, this.charCount); + } + if (chars == null) { + chars = this.unicodeSafeSubstring(upperData, this.charCount); + } + + // If the chars contain an emoji, only show it. + if (chars.match(Utils.regexpEmojiPresentation)) { + chars = chars.match(Utils.regexpEmojiPresentation)[0]; + } + + const charObj = this.getCharText(chars); + const color = this.stringToColor(upperData); + const svg = this.getSvg(this.size, color); + svg.appendChild(charObj); + const html = window.document.createElement('div').appendChild(svg).outerHTML; + const svgHtml = window.btoa(unescape(encodeURIComponent(html))); + this.src = 'data:image/svg+xml;base64,' + svgHtml; + } + } + + private stringToColor(str: string): string { + let hash = 0; + for (let i = 0; i < str.length; i++) { + // tslint:disable-next-line + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + let color = '#'; + for (let i = 0; i < 3; i++) { + // tslint:disable-next-line + const value = (hash >> (i * 8)) & 0xFF; + color += ('00' + value.toString(16)).substr(-2); + } + return color; + } + + private getFirstLetters(data: string, count: number): string { + const parts = data.split(' '); + if (parts.length > 1) { + let text = ''; + for (let i = 0; i < count; i++) { + text += this.unicodeSafeSubstring(parts[i], 1); + } + return text; + } + return null; + } + + private getSvg(size: number, color: string): HTMLElement { + const svgTag = window.document.createElement('svg'); + svgTag.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); + svgTag.setAttribute('pointer-events', 'none'); + svgTag.setAttribute('width', size.toString()); + svgTag.setAttribute('height', size.toString()); + svgTag.style.backgroundColor = color; + svgTag.style.width = size + 'px'; + svgTag.style.height = size + 'px'; + return svgTag; + } + + private getCharText(character: string): HTMLElement { + const textTag = window.document.createElement('text'); + textTag.setAttribute('text-anchor', 'middle'); + textTag.setAttribute('y', '50%'); + textTag.setAttribute('x', '50%'); + textTag.setAttribute('dy', '0.35em'); + textTag.setAttribute('pointer-events', 'auto'); + textTag.setAttribute('fill', this.textColor); + textTag.setAttribute('font-family', '"Open Sans","Helvetica Neue",Helvetica,Arial,' + + 'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"'); + textTag.textContent = character; + textTag.style.fontWeight = this.fontWeight.toString(); + textTag.style.fontSize = this.fontSize + 'px'; + return textTag; + } + + private unicodeSafeSubstring(str: string, count: number) { + const characters = str.match(/./ug); + return characters != null ? characters.slice(0, count).join('') : ''; + } +} diff --git a/angular/src/components/callout.component.html b/angular/src/components/callout.component.html index fc29b084..53fc6647 100644 --- a/angular/src/components/callout.component.html +++ b/angular/src/components/callout.component.html @@ -1,4 +1,5 @@ -