diff --git a/.github/workflows/release-desktop-beta.yml b/.github/workflows/release-desktop-beta.yml index 0013234faa3..74db61563e1 100644 --- a/.github/workflows/release-desktop-beta.yml +++ b/.github/workflows/release-desktop-beta.yml @@ -415,6 +415,9 @@ jobs: cache-dependency-path: '**/package-lock.json' node-version: ${{ env._NODE_VERSION }} + - name: Set up Node-gyp + run: python3 -m pip install setuptools + - name: Print environment run: | node --version @@ -546,6 +549,9 @@ jobs: cache-dependency-path: '**/package-lock.json' node-version: ${{ env._NODE_VERSION }} + - name: Set up Node-gyp + run: python3 -m pip install setuptools + - name: Print environment run: | node --version @@ -756,6 +762,9 @@ jobs: cache-dependency-path: '**/package-lock.json' node-version: ${{ env._NODE_VERSION }} + - name: Set up Node-gyp + run: python3 -m pip install setuptools + - name: Print environment run: | node --version diff --git a/apps/cli/package.json b/apps/cli/package.json index e992c3b6725..97b4dcf2ecf 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -58,7 +58,7 @@ "dependencies": { "@koa/multer": "3.0.2", "@koa/router": "12.0.1", - "argon2": "0.31.0", + "argon2": "0.40.1", "big-integer": "1.6.51", "browser-hrtime": "1.1.8", "chalk": "4.1.2", @@ -80,7 +80,7 @@ "papaparse": "5.4.1", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", - "tldts": "6.1.25", + "tldts": "6.1.29", "zxcvbn": "4.4.2" } } diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index 77f5d4f000e..85fd88657ce 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -20,9 +20,9 @@ "**/node_modules/@bitwarden/desktop-native/desktop_native.${platform}-${arch}*.node", "!**/node_modules/argon2/**/*", - "**/node_modules/argon2/argon2.js", + "**/node_modules/argon2/argon2.cjs", "**/node_modules/argon2/package.json", - "**/node_modules/argon2/lib/binding/napi-v3/argon2.node" + "**/node_modules/argon2/build/Release/argon2.node" ], "electronVersion": "30.1.2", "generateUpdatesFilesForAllChannels": true, diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index a6858632b90..5ea4ac88e99 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -10,10 +10,11 @@ "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-native": "file:../desktop_native", - "argon2": "0.31.0" + "argon2": "0.40.1" } }, "../desktop_native": { + "name": "@bitwarden/desktop-native", "version": "0.1.0", "license": "GPL-3.0", "devDependencies": { @@ -24,25 +25,6 @@ "resolved": "../desktop_native", "link": true }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", - "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", - "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" - } - }, "node_modules/@phc/format": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz", @@ -51,330 +33,20 @@ "node": ">=10" } }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" - }, - "node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/argon2": { - "version": "0.31.0", - "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.31.0.tgz", - "integrity": "sha512-r56NWwlE3tjD/FIqL1T+V4Ka+Mb5yMF35w1YWHpwpEjeONXBUbxmjhWkWqY63mse8lpcZ+ZZIGpKL+s+qXhyfg==", + "version": "0.40.1", + "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.40.1.tgz", + "integrity": "sha512-DjtHDwd7pm12qeWyfihHoM8Bn5vGcgH6sKwgPqwNYroRmxlrzadHEvMyuvQxN/V8YSyRRKD5x6ito09q1e9OyA==", "hasInstallScript": true, "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.11", "@phc/format": "^1.0.0", - "node-addon-api": "^7.0.0" + "node-addon-api": "^7.1.0", + "node-gyp-build": "^4.8.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.17.0" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "engines": { - "node": ">=10" - } - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" - }, - "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/node-addon-api": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz", @@ -383,233 +55,15 @@ "node": "^16 || ^18 || >= 20" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dependencies": { - "abbrev": "1" - }, + "node_modules/node-gyp-build": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", + "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" } - }, - "node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } } diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index e556b04afd9..19b2d63d250 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -13,6 +13,6 @@ }, "dependencies": { "@bitwarden/desktop-native": "file:../desktop_native", - "argon2": "0.31.0" + "argon2": "0.40.1" } } diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/abstractions/vault-filter.service.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/abstractions/vault-filter.service.ts index 3f8a0fb99d5..836cba22016 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/services/abstractions/vault-filter.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/abstractions/vault-filter.service.ts @@ -32,4 +32,5 @@ export abstract class VaultFilterService { ) => Observable>; // TODO: Remove this from org vault when collection admin service adopts state management reloadCollections?: (collections: CollectionAdminView[]) => void; + clearOrganizationFilter: () => void; } diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts index 6bedac5bb67..36cde762a00 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts @@ -103,6 +103,10 @@ export class VaultFilterService implements VaultFilterServiceAbstraction { return this._organizationFilter; } + clearOrganizationFilter() { + this._organizationFilter.next(null); + } + setOrganizationFilter(organization: Organization) { if (organization?.id != "AllVaults") { this._organizationFilter.next(organization); diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index ae3a0657788..4b290a1c179 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -394,6 +394,7 @@ export class VaultComponent implements OnInit, OnDestroy { this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); this.destroy$.next(); this.destroy$.complete(); + this.vaultFilterService.clearOrganizationFilter(); } async onVaultItemsEvent(event: VaultItemEvent) { diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index ce4d28223fb..d649797750e 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -7892,7 +7892,7 @@ "message": "Adjustments to seats will be reflected in the next billing cycle." }, "unassignedSeatsDescription": { - "message": "Unassigned subscription seats" + "message": "Unassigned seats" }, "purchaseSeatDescription": { "message": "Additional seats purchased" @@ -8399,10 +8399,6 @@ "exportClientReport": { "message": "Export client report" }, - "invoiceNumberHeader": { - "message": "Invoice number", - "description": "A table header for an invoice's number" - }, "memberAccessReport": { "message": "Member access" }, @@ -8450,5 +8446,29 @@ }, "smAccessRemovalSecretMessage": { "message": "This action will remove your access to this secret." + }, + "invoice": { + "message": "Invoice" + }, + "unassignedSeatsAvailable": { + "message": "You have $SEATS$ unassigned seats available.", + "placeholders": { + "seats": { + "content": "$1", + "example": "10" + } + }, + "description": "A message showing how many unassigned seats are available for a provider." + }, + "contactYourProviderForAdditionalSeats": { + "message": "Contact your provider admin to purchase additional seats." + }, + "open": { + "message": "Open", + "description": "The status of an invoice." + }, + "uncollectible": { + "message": "Uncollectible", + "description": "The status of an invoice." } } diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts index 7a96bdc7c70..2e422b81366 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts @@ -10,7 +10,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { ProviderUserType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { canAccessBilling } from "@bitwarden/common/billing/abstractions/provider-billing.service.abstraction"; +import { hasConsolidatedBilling } from "@bitwarden/common/billing/abstractions/provider-billing.service.abstraction"; import { PlanType } from "@bitwarden/common/billing/enums"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -72,9 +72,9 @@ export class ClientsComponent extends BaseClientsComponent { switchMap((params) => { this.providerId = params.providerId; return this.providerService.get$(this.providerId).pipe( - canAccessBilling(this.configService), - map((canAccessBilling) => { - if (canAccessBilling) { + hasConsolidatedBilling(this.configService), + map((hasConsolidatedBilling) => { + if (hasConsolidatedBilling) { return from( this.router.navigate(["../manage-client-organizations"], { relativeTo: this.activatedRoute, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html index 7d1d195bf95..3bd50539948 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html @@ -5,7 +5,7 @@ (); protected provider$: Observable; + + protected hasConsolidatedBilling$: Observable; protected canAccessBilling$: Observable; protected showPaymentMethodWarningBanners$ = this.configService.getFeatureFlag$( @@ -57,10 +59,15 @@ export class ProvidersLayoutComponent implements OnInit, OnDestroy { takeUntil(this.destroy$), ); - this.canAccessBilling$ = this.provider$.pipe( - filter((provider) => !!provider), - canAccessBilling(this.configService), - startWith(false), + this.hasConsolidatedBilling$ = this.provider$.pipe( + hasConsolidatedBilling(this.configService), + takeUntil(this.destroy$), + ); + + this.canAccessBilling$ = combineLatest([this.hasConsolidatedBilling$, this.provider$]).pipe( + map( + ([hasConsolidatedBilling, provider]) => hasConsolidatedBilling && provider.isProviderAdmin, + ), takeUntil(this.destroy$), ); } diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts index c7b33b5514c..f69dd0d2a81 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts @@ -9,7 +9,7 @@ import { FrontendLayoutComponent } from "@bitwarden/web-vault/app/layouts/fronte import { UserLayoutComponent } from "@bitwarden/web-vault/app/layouts/user-layout.component"; import { - ManageClientOrganizationsComponent, + ManageClientsComponent, ProviderSubscriptionComponent, hasConsolidatedBilling, ProviderPaymentMethodComponent, @@ -85,7 +85,7 @@ const routes: Routes = [ { path: "manage-client-organizations", canActivate: [hasConsolidatedBilling], - component: ManageClientOrganizationsComponent, + component: ManageClientsComponent, data: { titleId: "clients" }, }, { @@ -118,7 +118,7 @@ const routes: Routes = [ }, { path: "billing", - canActivate: [hasConsolidatedBilling], + canActivate: [ProviderPermissionsGuard, hasConsolidatedBilling], data: { providerPermissions: (provider: Provider) => provider.isProviderAdmin }, children: [ { @@ -129,6 +129,7 @@ const routes: Routes = [ { path: "subscription", component: ProviderSubscriptionComponent, + canActivate: [ProviderPermissionsGuard], data: { titleId: "subscription", }, @@ -136,6 +137,7 @@ const routes: Routes = [ { path: "payment-method", component: ProviderPaymentMethodComponent, + canActivate: [ProviderPermissionsGuard], data: { titleId: "paymentMethod", }, @@ -143,6 +145,7 @@ const routes: Routes = [ { path: "history", component: ProviderBillingHistoryComponent, + canActivate: [ProviderPermissionsGuard], data: { titleId: "billingHistory", }, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts index 728f64fb122..cd1a225a8e4 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts @@ -10,11 +10,11 @@ import { PaymentMethodWarningsModule } from "@bitwarden/web-vault/app/billing/sh import { OssModule } from "@bitwarden/web-vault/app/oss.module"; import { - CreateClientOrganizationComponent, + CreateClientDialogComponent, NoClientsComponent, - ManageClientOrganizationNameComponent, - ManageClientOrganizationsComponent, - ManageClientOrganizationSubscriptionComponent, + ManageClientNameDialogComponent, + ManageClientsComponent, + ManageClientSubscriptionDialogComponent, ProviderBillingHistoryComponent, ProviderPaymentMethodComponent, ProviderSelectPaymentMethodDialogComponent, @@ -66,11 +66,11 @@ import { SetupComponent } from "./setup/setup.component"; SetupComponent, SetupProviderComponent, UserAddEditComponent, - CreateClientOrganizationComponent, + CreateClientDialogComponent, NoClientsComponent, - ManageClientOrganizationsComponent, - ManageClientOrganizationNameComponent, - ManageClientOrganizationSubscriptionComponent, + ManageClientsComponent, + ManageClientNameDialogComponent, + ManageClientSubscriptionDialogComponent, ProviderBillingHistoryComponent, ProviderSubscriptionComponent, ProviderSelectPaymentMethodDialogComponent, diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.html b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.html similarity index 90% rename from bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.html rename to bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.html index 110990d709d..72067253011 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.html @@ -56,13 +56,15 @@ {{ unassignedSeatsForSelectedPlan }} - {{ "unassignedSeatsDescription" | i18n | lowercase }}{{ unassignedSeats }} {{ "unassignedSeatsDescription" | i18n | lowercase }} + {{ additionalSeatsPurchased }} + {{ "purchaseSeatDescription" | i18n | lowercase }} - 0 {{ "purchaseSeatDescription" | i18n | lowercase }} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts similarity index 67% rename from bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.ts rename to bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts index 13d74136cf4..8c013674bdb 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts @@ -1,6 +1,6 @@ import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, Inject, OnInit } from "@angular/core"; -import { FormBuilder, Validators } from "@angular/forms"; +import { FormControl, FormGroup, Validators } from "@angular/forms"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction"; import { PlanType } from "@bitwarden/common/billing/enums"; @@ -11,22 +11,22 @@ import { DialogService, ToastService } from "@bitwarden/components"; import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service"; -type CreateClientOrganizationParams = { +type CreateClientDialogParams = { providerId: string; plans: PlanResponse[]; }; -export enum CreateClientOrganizationResultType { +export enum CreateClientDialogResultType { Closed = "closed", Submitted = "submitted", } -export const openCreateClientOrganizationDialog = ( +export const openCreateClientDialog = ( dialogService: DialogService, - dialogConfig: DialogConfig, + dialogConfig: DialogConfig, ) => - dialogService.open( - CreateClientOrganizationComponent, + dialogService.open( + CreateClientDialogComponent, dialogConfig, ); @@ -39,26 +39,24 @@ type PlanCard = { }; @Component({ - selector: "app-create-client-organization", - templateUrl: "./create-client-organization.component.html", + templateUrl: "./create-client-dialog.component.html", }) -export class CreateClientOrganizationComponent implements OnInit { - protected formGroup = this.formBuilder.group({ - clientOwnerEmail: ["", [Validators.required, Validators.email]], - organizationName: ["", Validators.required], - seats: [null, [Validators.required, Validators.min(1)]], +export class CreateClientDialogComponent implements OnInit { + protected formGroup = new FormGroup({ + clientOwnerEmail: new FormControl("", [Validators.required, Validators.email]), + organizationName: new FormControl("", [Validators.required]), + seats: new FormControl(null, [Validators.required, Validators.min(1)]), }); protected loading = true; protected planCards: PlanCard[]; - protected ResultType = CreateClientOrganizationResultType; + protected ResultType = CreateClientDialogResultType; private providerPlans: ProviderPlanResponse[]; constructor( private billingApiService: BillingApiServiceAbstraction, - @Inject(DIALOG_DATA) private dialogParams: CreateClientOrganizationParams, - private dialogRef: DialogRef, - private formBuilder: FormBuilder, + @Inject(DIALOG_DATA) private dialogParams: CreateClientDialogParams, + private dialogRef: DialogRef, private i18nService: I18nService, private toastService: ToastService, private webProviderService: WebProviderService, @@ -111,14 +109,14 @@ export class CreateClientOrganizationComponent implements OnInit { this.planCards = [ { name: this.i18nService.t("planNameTeams"), - cost: teamsPlan.PasswordManager.seatPrice * 0.65, // 35% off for MSPs, + cost: teamsPlan.PasswordManager.providerPortalSeatPrice * 0.65, // 35% off for MSPs, type: teamsPlan.type, plan: teamsPlan, selected: true, }, { name: this.i18nService.t("planNameEnterprise"), - cost: enterprisePlan.PasswordManager.seatPrice * 0.65, // 35% off for MSPs, + cost: enterprisePlan.PasswordManager.providerPortalSeatPrice * 0.65, // 35% off for MSPs, type: enterprisePlan.type, plan: enterprisePlan, selected: false, @@ -159,14 +157,41 @@ export class CreateClientOrganizationComponent implements OnInit { this.dialogRef.close(this.ResultType.Submitted); }; - protected get unassignedSeatsForSelectedPlan(): number { - if (this.loading || !this.planCards) { + protected get openSeats(): number { + const selectedProviderPlan = this.getSelectedProviderPlan(); + + if (selectedProviderPlan === null) { return 0; } - const selectedPlan = this.planCards.find((planCard) => planCard.selected).plan; - const selectedProviderPlan = this.providerPlans.find( - (providerPlan) => providerPlan.planName === selectedPlan.name, - ); + return selectedProviderPlan.seatMinimum - selectedProviderPlan.assignedSeats; } + + protected get unassignedSeats(): number { + const unassignedSeats = this.openSeats - this.formGroup.value.seats; + + return unassignedSeats > 0 ? unassignedSeats : 0; + } + + protected get additionalSeatsPurchased(): number { + const selectedProviderPlan = this.getSelectedProviderPlan(); + + if (selectedProviderPlan === null) { + return 0; + } + + const selectedSeats = this.formGroup.value.seats ?? 0; + + const purchased = selectedSeats - this.openSeats; + + return purchased > 0 ? purchased : 0; + } + + private getSelectedProviderPlan(): ProviderPlanResponse { + if (this.loading || !this.planCards) { + return null; + } + const selectedPlan = this.planCards.find((planCard) => planCard.selected).plan; + return this.providerPlans.find((providerPlan) => providerPlan.planName === selectedPlan.name); + } } diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts index fa1bc137fc4..ae7bf487f99 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts @@ -1,5 +1,5 @@ -export * from "./create-client-organization.component"; -export * from "./manage-client-organizations.component"; -export * from "./manage-client-organization-name.component"; -export * from "./manage-client-organization-subscription.component"; +export * from "./create-client-dialog.component"; +export * from "./manage-clients.component"; +export * from "./manage-client-name-dialog.component"; +export * from "./manage-client-subscription-dialog.component"; export * from "./no-clients.component"; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-name.component.html b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.html similarity index 100% rename from bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-name.component.html rename to bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.html diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-name.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts similarity index 70% rename from bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-name.component.ts rename to bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts index 81e01a66cbd..be46308c1c4 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-name.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts @@ -7,7 +7,7 @@ import { UpdateClientOrganizationRequest } from "@bitwarden/common/billing/model import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DialogService, ToastService } from "@bitwarden/components"; -type ManageClientOrganizationNameParams = { +type ManageClientNameDialogParams = { providerId: string; organization: { id: string; @@ -16,34 +16,33 @@ type ManageClientOrganizationNameParams = { }; }; -export enum ManageClientOrganizationNameResultType { +export enum ManageClientNameDialogResultType { Closed = "closed", Submitted = "submitted", } -export const openManageClientOrganizationNameDialog = ( +export const openManageClientNameDialog = ( dialogService: DialogService, - dialogConfig: DialogConfig, + dialogConfig: DialogConfig, ) => - dialogService.open( - ManageClientOrganizationNameComponent, + dialogService.open( + ManageClientNameDialogComponent, dialogConfig, ); @Component({ - selector: "app-manage-client-organization-name", - templateUrl: "manage-client-organization-name.component.html", + templateUrl: "manage-client-name-dialog.component.html", }) -export class ManageClientOrganizationNameComponent { - protected ResultType = ManageClientOrganizationNameResultType; +export class ManageClientNameDialogComponent { + protected ResultType = ManageClientNameDialogResultType; protected formGroup = this.formBuilder.group({ name: [this.dialogParams.organization.name, Validators.required], }); constructor( - @Inject(DIALOG_DATA) protected dialogParams: ManageClientOrganizationNameParams, + @Inject(DIALOG_DATA) protected dialogParams: ManageClientNameDialogParams, private billingApiService: BillingApiServiceAbstraction, - private dialogRef: DialogRef, + private dialogRef: DialogRef, private formBuilder: FormBuilder, private i18nService: I18nService, private toastService: ToastService, diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.html b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.html deleted file mode 100644 index 8181c285c28..00000000000 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.html +++ /dev/null @@ -1,40 +0,0 @@ - - - {{ "manageSeats" | i18n }} - {{ clientName }} - -
-

- {{ "manageSeatsDescription" | i18n }} -

- - - {{ "assignedSeats" | i18n }} - - - -
- {{ unassignedSeats }} {{ "unassignedSeatsDescription" | i18n | lowercase }} - 0 {{ "purchaseSeatDescription" | i18n | lowercase }} -
-
-
-
- - - - -
diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.ts deleted file mode 100644 index 496a8b18cb4..00000000000 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; -import { Component, Inject, OnInit } from "@angular/core"; - -import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; -import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction"; -import { UpdateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/update-client-organization.request"; -import { ProviderPlanResponse } from "@bitwarden/common/billing/models/response/provider-subscription-response"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; - -type ManageClientOrganizationDialogParams = { - organization: ProviderOrganizationOrganizationDetailsResponse; -}; - -@Component({ - templateUrl: "manage-client-organization-subscription.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class ManageClientOrganizationSubscriptionComponent implements OnInit { - loading = true; - providerOrganizationId: string; - providerId: string; - - clientName: string; - assignedSeats: number; - unassignedSeats: number; - planName: string; - AdditionalSeatPurchased: number; - remainingOpenSeats: number; - - constructor( - public dialogRef: DialogRef, - @Inject(DIALOG_DATA) protected data: ManageClientOrganizationDialogParams, - private billingApiService: BillingApiService, - private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, - ) { - this.providerOrganizationId = data.organization.id; - this.providerId = data.organization.providerId; - this.clientName = data.organization.organizationName; - this.assignedSeats = data.organization.seats; - this.planName = data.organization.plan; - } - - async ngOnInit() { - try { - const response = await this.billingApiService.getProviderSubscription(this.providerId); - this.AdditionalSeatPurchased = this.getPurchasedSeatsByPlan(this.planName, response.plans); - const seatMinimum = this.getProviderSeatMinimumByPlan(this.planName, response.plans); - const assignedByPlan = this.getAssignedByPlan(this.planName, response.plans); - this.remainingOpenSeats = seatMinimum - assignedByPlan; - this.unassignedSeats = Math.abs(this.remainingOpenSeats); - } catch (error) { - this.remainingOpenSeats = 0; - this.AdditionalSeatPurchased = 0; - } - this.loading = false; - } - - async updateSubscription(assignedSeats: number) { - this.loading = true; - if (!assignedSeats) { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("assignedSeatCannotUpdate"), - ); - return; - } - - const request = new UpdateClientOrganizationRequest(); - request.assignedSeats = assignedSeats; - request.name = this.clientName; - - await this.billingApiService.updateClientOrganization( - this.providerId, - this.providerOrganizationId, - request, - ); - this.platformUtilsService.showToast("success", null, this.i18nService.t("subscriptionUpdated")); - this.loading = false; - this.dialogRef.close(); - } - - getPurchasedSeatsByPlan(planName: string, plans: ProviderPlanResponse[]): number { - const plan = plans.find((plan) => plan.planName === planName); - if (plan) { - return plan.purchasedSeats; - } else { - return 0; - } - } - - getAssignedByPlan(planName: string, plans: ProviderPlanResponse[]): number { - const plan = plans.find((plan) => plan.planName === planName); - if (plan) { - return plan.assignedSeats; - } else { - return 0; - } - } - - getProviderSeatMinimumByPlan(planName: string, plans: ProviderPlanResponse[]) { - const plan = plans.find((plan) => plan.planName === planName); - if (plan) { - return plan.seatMinimum; - } else { - return 0; - } - } - - static open(dialogService: DialogService, data: ManageClientOrganizationDialogParams) { - return dialogService.open(ManageClientOrganizationSubscriptionComponent, { data }); - } -} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.html b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.html new file mode 100644 index 00000000000..6f835eb5eb4 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.html @@ -0,0 +1,46 @@ +
+ + + {{ "manageSeats" | i18n }} + {{ dialogParams.organization.organizationName }} + +
+

{{ "manageSeatsDescription" | i18n }}

+ + + {{ "assignedSeats" | i18n }} + + + +
+ + {{ unassignedSeats }} {{ "unassignedSeatsDescription" | i18n | lowercase }} + + {{ additionalSeatsPurchased }} + {{ "purchaseSeatDescription" | i18n | lowercase }} +
+
+
+
+ + + + +
+
diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts new file mode 100644 index 00000000000..45ace513aee --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts @@ -0,0 +1,180 @@ +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { Component, Inject, OnInit } from "@angular/core"; +import { FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from "@angular/forms"; + +import { ProviderUserType } from "@bitwarden/common/admin-console/enums"; +import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; +import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; +import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; +import { UpdateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/update-client-organization.request"; +import { ProviderPlanResponse } from "@bitwarden/common/billing/models/response/provider-subscription-response"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { DialogService, ToastService } from "@bitwarden/components"; + +type ManageClientSubscriptionDialogParams = { + organization: ProviderOrganizationOrganizationDetailsResponse; + provider: Provider; +}; + +export enum ManageClientSubscriptionDialogResultType { + Closed = "closed", + Submitted = "submitted", +} + +export const openManageClientSubscriptionDialog = ( + dialogService: DialogService, + dialogConfig: DialogConfig, +) => + dialogService.open< + ManageClientSubscriptionDialogResultType, + ManageClientSubscriptionDialogParams + >(ManageClientSubscriptionDialogComponent, dialogConfig); + +@Component({ + templateUrl: "./manage-client-subscription-dialog.component.html", +}) +export class ManageClientSubscriptionDialogComponent implements OnInit { + protected loading = true; + protected providerPlan: ProviderPlanResponse; + protected openSeats: number; + protected readonly ResultType = ManageClientSubscriptionDialogResultType; + + protected formGroup = new FormGroup({ + assignedSeats: new FormControl(this.dialogParams.organization.seats, [ + Validators.required, + Validators.min(0), + ]), + }); + + constructor( + private billingApiService: BillingApiServiceAbstraction, + @Inject(DIALOG_DATA) protected dialogParams: ManageClientSubscriptionDialogParams, + private dialogRef: DialogRef, + private i18nService: I18nService, + private toastService: ToastService, + ) {} + + async ngOnInit(): Promise { + const response = await this.billingApiService.getProviderSubscription( + this.dialogParams.provider.id, + ); + + this.providerPlan = response.plans.find( + (plan) => plan.planName === this.dialogParams.organization.plan, + ); + + this.openSeats = this.providerPlan.seatMinimum - this.providerPlan.assignedSeats; + + this.formGroup.controls.assignedSeats.addValidators( + this.isServiceUserWithPurchasedSeats + ? this.createPurchasedSeatsValidator() + : this.createUnassignedSeatsValidator(), + ); + + this.loading = false; + } + + submit = async () => { + this.loading = true; + + this.formGroup.markAllAsTouched(); + + if (this.formGroup.invalid) { + return; + } + + const request = new UpdateClientOrganizationRequest(); + request.assignedSeats = this.formGroup.value.assignedSeats; + request.name = this.dialogParams.organization.organizationName; + + await this.billingApiService.updateClientOrganization( + this.dialogParams.provider.id, + this.dialogParams.organization.id, + request, + ); + + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("subscriptionUpdated"), + }); + + this.loading = false; + this.dialogRef.close(this.ResultType.Submitted); + }; + + createPurchasedSeatsValidator = + (): ValidatorFn => + (formControl: FormControl): ValidationErrors | null => { + if (this.isProviderAdmin) { + return null; + } + + const seatAdjustment = formControl.value - this.dialogParams.organization.seats; + + if (seatAdjustment <= 0) { + return null; + } + + return { + insufficientPermissions: { + message: this.i18nService.t("contactYourProviderForAdditionalSeats"), + }, + }; + }; + + createUnassignedSeatsValidator = + (): ValidatorFn => + (formControl: FormControl): ValidationErrors | null => { + if (this.isProviderAdmin) { + return null; + } + + const seatAdjustment = formControl.value - this.dialogParams.organization.seats; + + if (seatAdjustment <= this.openSeats) { + return null; + } + + const unassignedSeatsAvailableMessage = this.i18nService.t( + "unassignedSeatsAvailable", + this.openSeats, + ); + + const contactYourProviderMessage = this.i18nService.t( + "contactYourProviderForAdditionalSeats", + ); + + return { + insufficientPermissions: { + message: `${unassignedSeatsAvailableMessage} ${contactYourProviderMessage}`, + }, + }; + }; + + get unassignedSeats(): number { + const seatDifference = + this.formGroup.value.assignedSeats - this.dialogParams.organization.seats; + + const unassignedSeats = this.openSeats - seatDifference; + + return unassignedSeats >= 0 ? unassignedSeats : 0; + } + + get additionalSeatsPurchased(): number { + const seatDifference = + this.formGroup.value.assignedSeats - this.dialogParams.organization.seats; + + const purchasedSeats = seatDifference - this.openSeats; + + return purchasedSeats > 0 ? purchasedSeats : 0; + } + + get isProviderAdmin(): boolean { + return this.dialogParams.provider.type === ProviderUserType.ProviderAdmin; + } + + get isServiceUserWithPurchasedSeats(): boolean { + return !this.isProviderAdmin && this.providerPlan && this.providerPlan.purchasedSeats > 0; + } +} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.html b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.html similarity index 85% rename from bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.html rename to bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.html index 9a84b928377..2468f9df1af 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.html @@ -1,12 +1,6 @@ - + {{ "addNewOrganization" | i18n }} @@ -73,15 +67,15 @@ appA11yTitle="{{ 'options' | i18n }}" > - - -