diff --git a/.changeset/honest-mirrors-sit.md b/.changeset/honest-mirrors-sit.md new file mode 100644 index 000000000000..4e4298cb8110 --- /dev/null +++ b/.changeset/honest-mirrors-sit.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Disabled call to tags enterprise endpoint when on community license diff --git a/.changeset/loud-bees-smoke.md b/.changeset/loud-bees-smoke.md deleted file mode 100644 index 7b34a0d58af4..000000000000 --- a/.changeset/loud-bees-smoke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': minor ---- - -New helper for Apps to notify users via a Direct Message diff --git a/.changeset/nine-bottles-press.md b/.changeset/nine-bottles-press.md new file mode 100644 index 000000000000..f9a57fa676ad --- /dev/null +++ b/.changeset/nine-bottles-press.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +feat: Add flag to disable teams mention via troubleshoot page diff --git a/.changeset/slimy-cheetahs-heal.md b/.changeset/slimy-cheetahs-heal.md new file mode 100644 index 000000000000..44233bc87766 --- /dev/null +++ b/.changeset/slimy-cheetahs-heal.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixed selected departments not being displayed due to pagination diff --git a/.changeset/strong-laws-pump.md b/.changeset/strong-laws-pump.md new file mode 100644 index 000000000000..a4afefd65316 --- /dev/null +++ b/.changeset/strong-laws-pump.md @@ -0,0 +1,8 @@ +--- +'@rocket.chat/model-typings': patch +'@rocket.chat/meteor': patch +--- + +Change SAU aggregation to consider only sessions from few days ago instead of the whole past. + +This is particularly important for large workspaces in case the cron job did not run for some time, in that case the amount of sessions would accumulate and the aggregation would take a long time to run. diff --git a/.changeset/wise-walls-tan.md b/.changeset/wise-walls-tan.md new file mode 100644 index 000000000000..f558de82ec4c --- /dev/null +++ b/.changeset/wise-walls-tan.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/rest-typings': minor +'@rocket.chat/meteor': minor +--- + +fix: missing params on updateOwnBasicInfo endpoint diff --git a/.changeset/young-trains-glow.md b/.changeset/young-trains-glow.md new file mode 100644 index 000000000000..77f50812143f --- /dev/null +++ b/.changeset/young-trains-glow.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': minor +--- + +Fixed the issue of apps icon uneven alignment in case of missing icons inside message composer toolbar & message toolbar menu. diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml new file mode 100644 index 000000000000..808b8acdcbe3 --- /dev/null +++ b/.github/actions/build-docker/action.yml @@ -0,0 +1,73 @@ +name: 'Meteor Docker' + +inputs: + CR_USER: + required: true + CR_PAT: + required: true + node-version: + required: true + description: 'Node version' + type: string + platform: + required: false + description: 'Platform' + type: string + +runs: + using: composite + + steps: + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ inputs.CR_USER }} + password: ${{ inputs.CR_PAT }} + + - name: Restore build + uses: actions/download-artifact@v3 + with: + name: build + path: /tmp/build + + - name: Unpack build + shell: bash + run: | + cd /tmp/build + tar xzf Rocket.Chat.tar.gz + rm Rocket.Chat.tar.gz + + - uses: dtinth/setup-github-actions-caching-for-turbo@v1 + + - name: Setup NodeJS + uses: ./.github/actions/setup-node + with: + node-version: ${{ inputs.node-version }} + cache-modules: true + install: true + + - run: yarn build + shell: bash + + - name: Build Docker images + shell: bash + run: | + args=(rocketchat) + + if [[ '${{ inputs.platform }}' = 'alpine' ]]; then + args+=($SERVICES_PUBLISH) + fi; + + docker compose -f docker-compose-ci.yml build "${args[@]}" + + - name: Publish Docker images to GitHub Container Registry + shell: bash + run: | + args=(rocketchat) + + if [[ '${{ inputs.platform }}' = 'alpine' ]]; then + args+=($SERVICES_PUBLISH) + fi; + + docker compose -f docker-compose-ci.yml push "${args[@]}" diff --git a/.github/actions/meteor-build/action.yml b/.github/actions/meteor-build/action.yml new file mode 100644 index 000000000000..21fec059c8de --- /dev/null +++ b/.github/actions/meteor-build/action.yml @@ -0,0 +1,129 @@ +name: 'Meteor Build' + +inputs: + coverage: + required: false + description: 'Enable coverage' + type: boolean + reset-meteor: + required: false + description: 'Reset Meteor' + type: boolean + node-version: + required: true + description: 'Node version' + type: string + +runs: + using: composite + + steps: + - name: Set Swap Space + uses: pierotofy/set-swap-space@master + with: + swap-size-gb: 4 + + - name: Setup NodeJS + uses: ./.github/actions/setup-node + with: + node-version: ${{ inputs.node-version }} + cache-modules: true + install: true + + # - name: Free disk space + # run: | + # sudo apt clean + # docker rmi $(docker image ls -aq) + # df -h + + - name: Cache vite + uses: actions/cache@v3 + with: + path: ./node_modules/.vite + key: vite-local-cache-${{ runner.OS }}-${{ hashFiles('package.json') }} + restore-keys: | + vite-local-cache-${{ runner.os }}- + + - name: Cache meteor local + uses: actions/cache@v3 + with: + path: ./apps/meteor/.meteor/local + key: meteor-local-cache-${{ runner.OS }}-${{ hashFiles('apps/meteor/.meteor/versions') }} + restore-keys: | + meteor-local-cache-${{ runner.os }}- + + - name: Cache meteor + uses: actions/cache@v3 + with: + path: ~/.meteor + key: meteor-cache-${{ runner.OS }}-${{ hashFiles('apps/meteor/.meteor/release') }} + restore-keys: | + meteor-cache-${{ runner.os }}- + + - name: Install Meteor + shell: bash + run: | + # Restore bin from cache + set +e + METEOR_SYMLINK_TARGET=$(readlink ~/.meteor/meteor) + METEOR_TOOL_DIRECTORY=$(dirname "$METEOR_SYMLINK_TARGET") + set -e + LAUNCHER=$HOME/.meteor/$METEOR_TOOL_DIRECTORY/scripts/admin/launch-meteor + if [ -e $LAUNCHER ] + then + echo "Cached Meteor bin found, restoring it" + sudo cp "$LAUNCHER" "/usr/local/bin/meteor" + else + echo "No cached Meteor bin found." + fi + + # only install meteor if bin isn't found + command -v meteor >/dev/null 2>&1 || curl https://install.meteor.com | sed s/--progress-bar/-sL/g | /bin/sh + + - name: Versions + shell: bash + run: | + npm --versions + yarn -v + node -v + meteor --version + meteor npm --versions + meteor node -v + git version + + - uses: dtinth/setup-github-actions-caching-for-turbo@v1 + + - name: Translation check + shell: bash + run: yarn turbo run translation-check + + - name: Reset Meteor + shell: bash + if: ${{ inputs.reset-meteor == 'true' }} + working-directory: ./apps/meteor + run: meteor reset + + - name: Build Rocket.Chat From Pull Request + shell: bash + if: startsWith(github.ref, 'refs/pull/') == true + env: + METEOR_PROFILE: 1000 + BABEL_ENV: ${{ inputs.coverage == 'true' && 'coverage' || '' }} + run: yarn build:ci -- --directory /tmp/dist + + - name: Build Rocket.Chat + shell: bash + if: startsWith(github.ref, 'refs/pull/') != true + run: yarn build:ci -- --directory /tmp/dist + + - name: Prepare build + shell: bash + run: | + cd /tmp/dist + tar czf /tmp/Rocket.Chat.tar.gz bundle + + - name: Store build + uses: actions/upload-artifact@v3 + with: + name: build + path: /tmp/Rocket.Chat.tar.gz diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 70ec4dcba6ed..22250705ea58 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -123,7 +123,7 @@ jobs: run: yarn build build: - name: 📦 Meteor Build + name: 📦 Meteor Build - coverage needs: [release-versions, packages-build] runs-on: ubuntu-20.04 @@ -138,111 +138,38 @@ jobs: echo "github.event_name: ${{ github.event_name }}" cat $GITHUB_EVENT_PATH - - name: Set Swap Space - uses: pierotofy/set-swap-space@master - with: - swap-size-gb: 4 - - uses: actions/checkout@v3 - - name: Setup NodeJS - uses: ./.github/actions/setup-node + - uses: ./.github/actions/meteor-build with: node-version: ${{ needs.release-versions.outputs.node-version }} - cache-modules: true - install: true - - # - name: Free disk space - # run: | - # sudo apt clean - # docker rmi $(docker image ls -aq) - # df -h - - - name: Cache vite - uses: actions/cache@v3 - with: - path: ./node_modules/.vite - key: vite-local-cache-${{ runner.OS }}-${{ hashFiles('package.json') }} - restore-keys: | - vite-local-cache-${{ runner.os }}- - - - name: Cache meteor local - uses: actions/cache@v3 - with: - path: ./apps/meteor/.meteor/local - key: meteor-local-cache-${{ runner.OS }}-${{ hashFiles('apps/meteor/.meteor/versions') }} - restore-keys: | - meteor-local-cache-${{ runner.os }}- - - - name: Cache meteor - uses: actions/cache@v3 - with: - path: ~/.meteor - key: meteor-cache-${{ runner.OS }}-${{ hashFiles('apps/meteor/.meteor/release') }} - restore-keys: | - meteor-cache-${{ runner.os }}- + coverage: true - - name: Install Meteor - run: | - # Restore bin from cache - set +e - METEOR_SYMLINK_TARGET=$(readlink ~/.meteor/meteor) - METEOR_TOOL_DIRECTORY=$(dirname "$METEOR_SYMLINK_TARGET") - set -e - LAUNCHER=$HOME/.meteor/$METEOR_TOOL_DIRECTORY/scripts/admin/launch-meteor - if [ -e $LAUNCHER ] - then - echo "Cached Meteor bin found, restoring it" - sudo cp "$LAUNCHER" "/usr/local/bin/meteor" - else - echo "No cached Meteor bin found." - fi - - # only install meteor if bin isn't found - command -v meteor >/dev/null 2>&1 || curl https://install.meteor.com | sed s/--progress-bar/-sL/g | /bin/sh + build-prod: + name: 📦 Meteor Build - official + needs: [tests-done, release-versions, packages-build] + if: (github.event_name == 'release' || github.ref == 'refs/heads/develop') + runs-on: ubuntu-20.04 - - name: Versions + steps: + - name: Github Info run: | - npm --versions - yarn -v - node -v - meteor --version - meteor npm --versions - meteor node -v - git version - - - uses: dtinth/setup-github-actions-caching-for-turbo@v1 - - - name: Translation check - run: yarn turbo run translation-check - - - name: Reset Meteor - if: startsWith(github.ref, 'refs/tags/') == 'true' || github.ref == 'refs/heads/develop' - working-directory: ./apps/meteor - run: meteor reset - - - name: Build Rocket.Chat From Pull Request - if: startsWith(github.ref, 'refs/pull/') == true - env: - METEOR_PROFILE: 1000 - run: yarn build:ci -- --directory /tmp/dist - - - name: Build Rocket.Chat - if: startsWith(github.ref, 'refs/pull/') != true - run: yarn build:ci -- --directory /tmp/dist + echo "GITHUB_ACTION: $GITHUB_ACTION" + echo "GITHUB_ACTOR: $GITHUB_ACTOR" + echo "GITHUB_REF: $GITHUB_REF" + echo "GITHUB_HEAD_REF: $GITHUB_HEAD_REF" + echo "GITHUB_BASE_REF: $GITHUB_BASE_REF" + echo "github.event_name: ${{ github.event_name }}" + cat $GITHUB_EVENT_PATH - - name: Prepare build - run: | - cd /tmp/dist - tar czf /tmp/Rocket.Chat.tar.gz bundle + - uses: actions/checkout@v3 - - name: Store build - uses: actions/upload-artifact@v3 + - uses: ./.github/actions/meteor-build with: - name: build - path: /tmp/Rocket.Chat.tar.gz + node-version: ${{ needs.release-versions.outputs.node-version }} + coverage: false - build-gh-docker: + build-gh-docker-coverage: name: 🚢 Build Docker Images for Testing needs: [build, release-versions] if: (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') @@ -262,56 +189,39 @@ jobs: steps: - uses: actions/checkout@v3 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ secrets.CR_USER }} - password: ${{ secrets.CR_PAT }} - - - name: Restore build - uses: actions/download-artifact@v3 - with: - name: build - path: /tmp/build - - - name: Unpack build - run: | - cd /tmp/build - tar xzf Rocket.Chat.tar.gz - rm Rocket.Chat.tar.gz - - - uses: dtinth/setup-github-actions-caching-for-turbo@v1 - - - name: Setup NodeJS - uses: ./.github/actions/setup-node + - uses: ./.github/actions/build-docker with: + CR_USER: ${{ secrets.CR_USER }} + CR_PAT: ${{ secrets.CR_PAT }} node-version: ${{ needs.release-versions.outputs.node-version }} - cache-modules: true - install: true - - - run: yarn build + platform: ${{ matrix.platform }} - - name: Build Docker images - run: | - args=(rocketchat) - - if [[ '${{ matrix.platform }}' = 'alpine' ]]; then - args+=($SERVICES_PUBLISH) - fi; + build-gh-docker: + name: 🚢 Build Docker Images for Production + needs: [build-prod, release-versions] + runs-on: ubuntu-20.04 - docker compose -f docker-compose-ci.yml build "${args[@]}" + env: + RC_DOCKERFILE: ${{ matrix.platform == 'alpine' && needs.release-versions.outputs.rc-dockerfile-alpine || needs.release-versions.outputs.rc-dockerfile }} + RC_DOCKER_TAG: ${{ matrix.platform == 'alpine' && needs.release-versions.outputs.rc-docker-tag-alpine || needs.release-versions.outputs.rc-docker-tag }} + DOCKER_TAG: ${{ needs.release-versions.outputs.gh-docker-tag }} + LOWERCASE_REPOSITORY: ${{ needs.release-versions.outputs.lowercase-repo }} + SERVICES_PUBLISH: 'authorization-service account-service ddp-streamer-service presence-service stream-hub-service' - - name: Publish Docker images to GitHub Container Registry - run: | - args=(rocketchat) + strategy: + fail-fast: false + matrix: + platform: ['official', 'alpine'] - if [[ '${{ matrix.platform }}' = 'alpine' ]]; then - args+=($SERVICES_PUBLISH) - fi; + steps: + - uses: actions/checkout@v3 - docker compose -f docker-compose-ci.yml push "${args[@]}" + - uses: ./.github/actions/build-docker + with: + CR_USER: ${{ secrets.CR_USER }} + CR_PAT: ${{ secrets.CR_PAT }} + node-version: ${{ needs.release-versions.outputs.node-version }} + platform: ${{ matrix.platform }} - name: Rename official Docker tag to GitHub Container Registry if: matrix.platform == 'official' @@ -342,7 +252,7 @@ jobs: test-api: name: 🔨 Test API (CE) - needs: [checks, build-gh-docker, release-versions] + needs: [checks, build-gh-docker-coverage, release-versions] uses: ./.github/workflows/ci-test-e2e.yml with: @@ -361,7 +271,7 @@ jobs: test-ui: name: 🔨 Test UI (CE) - needs: [checks, build-gh-docker, release-versions] + needs: [checks, build-gh-docker-coverage, release-versions] uses: ./.github/workflows/ci-test-e2e.yml with: @@ -387,7 +297,7 @@ jobs: test-api-ee: name: 🔨 Test API (EE) - needs: [checks, build-gh-docker, release-versions] + needs: [checks, build-gh-docker-coverage, release-versions] uses: ./.github/workflows/ci-test-e2e.yml with: @@ -409,7 +319,7 @@ jobs: test-ui-ee: name: 🔨 Test UI (EE) - needs: [checks, build-gh-docker, release-versions] + needs: [checks, build-gh-docker-coverage, release-versions] uses: ./.github/workflows/ci-test-e2e.yml with: @@ -449,7 +359,7 @@ jobs: name: 🚀 Publish build and update our registry runs-on: ubuntu-20.04 if: github.event_name == 'release' || github.ref == 'refs/heads/develop' - needs: [tests-done, release-versions] + needs: [build-gh-docker, release-versions] steps: - uses: actions/checkout@v3 diff --git a/apps/meteor/.babelrc b/apps/meteor/.babelrc index a8c20b400ca5..382b93318fab 100644 --- a/apps/meteor/.babelrc +++ b/apps/meteor/.babelrc @@ -1,9 +1,15 @@ { - "presets": [ - "@babel/preset-env", - "@babel/preset-react" - ], - "plugins": [ - "babel-plugin-istanbul" - ] + "presets": ["@babel/preset-env", "@babel/preset-react"], + "env": { + "coverage": { + "plugins": [ + [ + "istanbul", + { + "exclude": ["**/*.spec.js", "**/*.test.js"] + } + ] + ] + } + } } diff --git a/apps/meteor/CHANGELOG.md b/apps/meteor/CHANGELOG.md index 182d30ddabb9..51ff6d4d1826 100644 --- a/apps/meteor/CHANGELOG.md +++ b/apps/meteor/CHANGELOG.md @@ -1,5 +1,34 @@ # @rocket.chat/meteor +## 6.3.4 + +### Patch Changes + +- db919f9b23: Bump @rocket.chat/meteor version. +- Bump @rocket.chat/meteor version. +- ebeb088441: fix: Prevent `RoomProvider.useEffect` from subscribing to room-data stream multiple times +- 8a7d5d3898: fix: agent role being removed upon user deactivation +- 759fe2472a: chore: Increase cache time from 5s to 10s on `getUnits` helpers. This should reduce the number of DB calls made by this method to fetch the unit limitations for a user. +- Updated dependencies [8a7d5d3898] + - @rocket.chat/model-typings@0.0.10 + - @rocket.chat/omnichannel-services@0.0.10 + - @rocket.chat/models@0.0.10 + - @rocket.chat/presence@0.0.10 + - @rocket.chat/core-services@0.1.4 + - @rocket.chat/cron@0.0.6 + - @rocket.chat/instance-status@0.0.10 + - @rocket.chat/core-typings@6.3.4 + - @rocket.chat/rest-typings@6.3.4 + - @rocket.chat/api-client@0.1.4 + - @rocket.chat/pdf-worker@0.0.10 + - @rocket.chat/gazzodown@1.0.4 + - @rocket.chat/ui-contexts@1.0.4 + - @rocket.chat/fuselage-ui-kit@1.0.4 + - @rocket.chat/ui-theming@0.0.1 + - @rocket.chat/ui-client@1.0.4 + - @rocket.chat/ui-video-conf@1.0.4 + - @rocket.chat/web-ui-registration@1.0.4 + ## 6.3.3 ### Patch Changes diff --git a/apps/meteor/app/api/server/v1/users.ts b/apps/meteor/app/api/server/v1/users.ts index a4e3f974ac65..b23d41255c3b 100644 --- a/apps/meteor/app/api/server/v1/users.ts +++ b/apps/meteor/app/api/server/v1/users.ts @@ -126,7 +126,9 @@ API.v1.addRoute( realname: this.bodyParams.data.name, username: this.bodyParams.data.username, nickname: this.bodyParams.data.nickname, + bio: this.bodyParams.data.bio, statusText: this.bodyParams.data.statusText, + statusType: this.bodyParams.data.statusType, newPassword: this.bodyParams.data.newPassword, typedPassword: this.bodyParams.data.currentPassword, }; diff --git a/apps/meteor/app/apps/server/bridges/messages.ts b/apps/meteor/app/apps/server/bridges/messages.ts index 47648e445939..d75a0c244674 100644 --- a/apps/meteor/app/apps/server/bridges/messages.ts +++ b/apps/meteor/app/apps/server/bridges/messages.ts @@ -1,4 +1,4 @@ -import type { IMessage, IDirectMessage } from '@rocket.chat/apps-engine/definition/messages'; +import type { IMessage } from '@rocket.chat/apps-engine/definition/messages'; import type { IRoom } from '@rocket.chat/apps-engine/definition/rooms'; import type { IUser } from '@rocket.chat/apps-engine/definition/users'; import type { ITypingDescriptor } from '@rocket.chat/apps-engine/server/bridges/MessageBridge'; @@ -17,7 +17,7 @@ export class AppMessageBridge extends MessageBridge { super(); } - protected async create(message: IMessage | IDirectMessage, appId: string): Promise { + protected async create(message: IMessage, appId: string): Promise { this.orch.debugLog(`The App ${appId} is creating a new message.`); const convertedMessage = await this.orch.getConverters()?.get('messages').convertAppMessage(message); diff --git a/apps/meteor/app/lib/server/functions/getFullUserData.ts b/apps/meteor/app/lib/server/functions/getFullUserData.ts index c04148c07ba8..0703b24d9210 100644 --- a/apps/meteor/app/lib/server/functions/getFullUserData.ts +++ b/apps/meteor/app/lib/server/functions/getFullUserData.ts @@ -21,6 +21,7 @@ const defaultFields = { avatarETag: 1, extension: 1, federated: 1, + statusLivechat: 1, } as const; const fullFields = { diff --git a/apps/meteor/app/lib/server/functions/saveUser.js b/apps/meteor/app/lib/server/functions/saveUser.js index f912626c833e..42438be4ab7a 100644 --- a/apps/meteor/app/lib/server/functions/saveUser.js +++ b/apps/meteor/app/lib/server/functions/saveUser.js @@ -15,6 +15,7 @@ import * as Mailer from '../../../mailer/server/api'; import { settings } from '../../../settings/server'; import { safeGetMeteorUser } from '../../../utils/server/functions/safeGetMeteorUser'; import { validateEmailDomain } from '../lib'; +import { generatePassword } from '../lib/generatePassword'; import { passwordPolicy } from '../lib/passwordPolicy'; import { checkEmailAvailability } from './checkEmailAvailability'; import { checkUsernameAvailability } from './checkUsernameAvailability'; @@ -344,7 +345,7 @@ export const saveUser = async function (userId, userData) { if (userData.hasOwnProperty('setRandomPassword')) { if (userData.setRandomPassword) { - userData.password = passwordPolicy.generatePassword(); + userData.password = generatePassword(); userData.requirePasswordChange = true; sendPassword = true; } diff --git a/apps/meteor/app/lib/server/lib/generatePassword.ts b/apps/meteor/app/lib/server/lib/generatePassword.ts new file mode 100644 index 000000000000..bf8d2474b7a7 --- /dev/null +++ b/apps/meteor/app/lib/server/lib/generatePassword.ts @@ -0,0 +1,31 @@ +import generator from 'generate-password'; + +import { passwordPolicy } from './passwordPolicy'; + +export const generatePassword = (): string => { + const policies = passwordPolicy.getPasswordPolicy(); + + const maxLength: number = (policies.policy.find(([key]) => key === 'get-password-policy-maxLength')?.[1]?.maxLength as number) || -1; + const minLength: number = (policies.policy.find(([key]) => key === 'get-password-policy-minLength')?.[1]?.minLength as number) || -1; + + const length = Math.min(Math.max(minLength, 12), maxLength > 0 ? maxLength : Number.MAX_SAFE_INTEGER); + + if (policies.enabled) { + for (let i = 0; i < 10; i++) { + const password = generator.generate({ + length, + ...(policies.policy && { numbers: true }), + ...(policies.policy.some(([key]) => key === 'get-password-policy-mustContainAtLeastOneSpecialCharacter') && { symbols: true }), + ...(policies.policy.some(([key]) => key === 'get-password-policy-mustContainAtLeastOneLowercase') && { lowercase: true }), + ...(policies.policy.some(([key]) => key === 'get-password-policy-mustContainAtLeastOneUppercase') && { uppercase: true }), + strict: true, + }); + + if (passwordPolicy.validate(password)) { + return password; + } + } + } + + return generator.generate({ length: 17 }); +}; diff --git a/apps/meteor/app/lib/server/lib/passwordPolicy.js b/apps/meteor/app/lib/server/lib/passwordPolicy.js deleted file mode 100644 index 57490d1712c1..000000000000 --- a/apps/meteor/app/lib/server/lib/passwordPolicy.js +++ /dev/null @@ -1,32 +0,0 @@ -import { settings } from '../../../settings/server'; -import PasswordPolicy from './PasswordPolicyClass'; - -export const passwordPolicy = new PasswordPolicy(); - -settings.watch('Accounts_Password_Policy_Enabled', (value) => { - passwordPolicy.enabled = value; -}); -settings.watch('Accounts_Password_Policy_MinLength', (value) => { - passwordPolicy.minLength = value; -}); -settings.watch('Accounts_Password_Policy_MaxLength', (value) => { - passwordPolicy.maxLength = value; -}); -settings.watch('Accounts_Password_Policy_ForbidRepeatingCharacters', (value) => { - passwordPolicy.forbidRepeatingCharacters = value; -}); -settings.watch('Accounts_Password_Policy_ForbidRepeatingCharactersCount', (value) => { - passwordPolicy.forbidRepeatingCharactersCount = value; -}); -settings.watch('Accounts_Password_Policy_AtLeastOneLowercase', (value) => { - passwordPolicy.mustContainAtLeastOneLowercase = value; -}); -settings.watch('Accounts_Password_Policy_AtLeastOneUppercase', (value) => { - passwordPolicy.mustContainAtLeastOneUppercase = value; -}); -settings.watch('Accounts_Password_Policy_AtLeastOneNumber', (value) => { - passwordPolicy.mustContainAtLeastOneNumber = value; -}); -settings.watch('Accounts_Password_Policy_AtLeastOneSpecialCharacter', (value) => { - passwordPolicy.mustContainAtLeastOneSpecialCharacter = value; -}); diff --git a/apps/meteor/app/lib/server/lib/passwordPolicy.ts b/apps/meteor/app/lib/server/lib/passwordPolicy.ts new file mode 100644 index 000000000000..b40447ca56ab --- /dev/null +++ b/apps/meteor/app/lib/server/lib/passwordPolicy.ts @@ -0,0 +1,64 @@ +import { PasswordPolicy } from '@rocket.chat/password-policies'; + +import { settings } from '../../../settings/server'; + +const enabled = false; +const minLength = -1; +const maxLength = -1; +const forbidRepeatingCharacters = false; +const forbidRepeatingCharactersCount = 3; +const mustContainAtLeastOneLowercase = false; +const mustContainAtLeastOneUppercase = false; +const mustContainAtLeastOneNumber = false; +const mustContainAtLeastOneSpecialCharacter = false; + +export let passwordPolicy = new PasswordPolicy({ + enabled, + minLength, + maxLength, + forbidRepeatingCharacters, + forbidRepeatingCharactersCount, + mustContainAtLeastOneLowercase, + mustContainAtLeastOneUppercase, + mustContainAtLeastOneNumber, + mustContainAtLeastOneSpecialCharacter, + throwError: true, +}); + +settings.watchMultiple( + [ + 'Accounts_Password_Policy_Enabled', + 'Accounts_Password_Policy_MinLength', + 'Accounts_Password_Policy_MaxLength', + 'Accounts_Password_Policy_ForbidRepeatingCharacters', + 'Accounts_Password_Policy_ForbidRepeatingCharactersCount', + 'Accounts_Password_Policy_AtLeastOneLowercase', + 'Accounts_Password_Policy_AtLeastOneUppercase', + 'Accounts_Password_Policy_AtLeastOneNumber', + 'Accounts_Password_Policy_AtLeastOneSpecialCharacter', + ], + ([ + enabled, + minLength, + maxLength, + forbidRepeatingCharacters, + forbidRepeatingCharactersCount, + mustContainAtLeastOneLowercase, + mustContainAtLeastOneUppercase, + mustContainAtLeastOneNumber, + mustContainAtLeastOneSpecialCharacter, + ]) => { + passwordPolicy = new PasswordPolicy({ + enabled: Boolean(enabled), + minLength: Number(minLength), + maxLength: Number(maxLength), + forbidRepeatingCharacters: Boolean(forbidRepeatingCharacters), + forbidRepeatingCharactersCount: Number(forbidRepeatingCharactersCount), + mustContainAtLeastOneLowercase: Boolean(mustContainAtLeastOneLowercase), + mustContainAtLeastOneUppercase: Boolean(mustContainAtLeastOneUppercase), + mustContainAtLeastOneNumber: Boolean(mustContainAtLeastOneNumber), + mustContainAtLeastOneSpecialCharacter: Boolean(mustContainAtLeastOneSpecialCharacter), + throwError: true, + }); + }, +); diff --git a/apps/meteor/app/mentions/server/getMentionedTeamMembers.ts b/apps/meteor/app/mentions/server/getMentionedTeamMembers.ts new file mode 100644 index 000000000000..b1355c90cb93 --- /dev/null +++ b/apps/meteor/app/mentions/server/getMentionedTeamMembers.ts @@ -0,0 +1,35 @@ +import { Team } from '@rocket.chat/core-services'; +import type { IMessage } from '@rocket.chat/core-typings'; + +import { callbacks } from '../../../lib/callbacks'; +import { settings } from '../../settings/server'; + +interface IExtraDataForNotification { + userMentions: any[]; + otherMentions: any[]; + message: IMessage; +} + +const beforeGetMentions = async (mentionIds: string[], extra?: IExtraDataForNotification) => { + const { otherMentions } = extra ?? {}; + + const teamIds = otherMentions?.filter(({ type }) => type === 'team').map(({ _id }) => _id); + + if (!teamIds?.length) { + return mentionIds; + } + + const members = await Team.getMembersByTeamIds(teamIds, { projection: { userId: 1 } }); + mentionIds.push(...new Set(members.map(({ userId }) => userId).filter((userId) => !mentionIds.includes(userId)))); + + return mentionIds; +}; + +settings.watch('Troubleshoot_Disable_Teams_Mention', (value) => { + if (value) { + callbacks.remove('beforeGetMentions', 'before-get-mentions-get-teams'); + return; + } + + callbacks.add('beforeGetMentions', beforeGetMentions, callbacks.priority.MEDIUM, 'before-get-mentions-get-teams'); +}); diff --git a/apps/meteor/app/mentions/server/index.ts b/apps/meteor/app/mentions/server/index.ts index 474d41a439e1..a04af05b9db1 100644 --- a/apps/meteor/app/mentions/server/index.ts +++ b/apps/meteor/app/mentions/server/index.ts @@ -1,2 +1,3 @@ -import './server'; +import './getMentionedTeamMembers'; import './methods/getUserMentionsByChannel'; +import './server'; diff --git a/apps/meteor/app/mentions/server/server.ts b/apps/meteor/app/mentions/server/server.ts index 5eb70aae4656..13765e99d856 100644 --- a/apps/meteor/app/mentions/server/server.ts +++ b/apps/meteor/app/mentions/server/server.ts @@ -1,5 +1,5 @@ -import { api } from '@rocket.chat/core-services'; -import type { IUser, IRoom } from '@rocket.chat/core-typings'; +import { api, Team } from '@rocket.chat/core-services'; +import type { IUser, IRoom, ITeam } from '@rocket.chat/core-typings'; import { Subscriptions, Users, Rooms } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; @@ -9,16 +9,32 @@ import { settings } from '../../settings/server'; import MentionsServer from './Mentions'; export class MentionQueries { - async getUsers(usernames: string[]): Promise<(Pick & { type: 'user' })[]> { + async getUsers( + usernames: string[], + ): Promise<((Pick & { type: 'user' }) | (Pick & { type: 'team' }))[]> { + const uniqueUsernames = [...new Set(usernames)]; + const teams = await Team.listByNames(uniqueUsernames, { projection: { name: 1 } }); + const users = await Users.find( - { username: { $in: [...new Set(usernames)] } }, + { username: { $in: uniqueUsernames } }, { projection: { _id: true, username: true, name: 1 } }, ).toArray(); - return users.map((user) => ({ + const taggedUsers = users.map((user) => ({ ...user, - type: 'user', + type: 'user' as const, })); + + if (settings.get('Troubleshoot_Disable_Teams_Mention')) { + return taggedUsers; + } + + const taggedTeams = teams.map((team) => ({ + ...team, + type: 'team' as const, + })); + + return [...taggedUsers, ...taggedTeams]; } async getUser(userId: string): Promise { diff --git a/apps/meteor/app/statistics/server/lib/SAUMonitor.ts b/apps/meteor/app/statistics/server/lib/SAUMonitor.ts index 79ce688cffd3..b3aa68337106 100644 --- a/apps/meteor/app/statistics/server/lib/SAUMonitor.ts +++ b/apps/meteor/app/statistics/server/lib/SAUMonitor.ts @@ -318,33 +318,19 @@ export class SAUMonitorClass { return; } - logger.info('[aggregate] - Aggregating data.'); - - const date = new Date(); - date.setDate(date.getDate() - 0); // yesterday - const yesterday = getDateObj(date); - - for await (const record of aggregates.dailySessionsOfYesterday(Sessions.col, yesterday)) { - await Sessions.updateOne( - { _id: `${record.userId}-${record.year}-${record.month}-${record.day}` }, - { $set: record }, - { upsert: true }, - ); + const today = new Date(); + + // get sessions from 3 days ago to make sure even if a few cron jobs were skipped, we still have the data + const threeDaysAgo = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 3, 0, 0, 0, 0); + + const period = { start: getDateObj(threeDaysAgo), end: getDateObj(today) }; + + logger.info({ msg: '[aggregate] - Aggregating data.', period }); + + for await (const record of aggregates.dailySessions(Sessions.col, period)) { + await Sessions.updateDailySessionById(`${record.userId}-${record.year}-${record.month}-${record.day}`, record); } - await Sessions.updateMany( - { - type: 'session', - year: { $lte: yesterday.year }, - month: { $lte: yesterday.month }, - day: { $lte: yesterday.day }, - }, - { - $set: { - type: 'computed-session', - _computedAt: new Date(), - }, - }, - ); + await Sessions.updateAllSessionsByDateToComputed(period); } } diff --git a/apps/meteor/client/components/Omnichannel/hooks/useDepartmentsList.ts b/apps/meteor/client/components/Omnichannel/hooks/useDepartmentsList.ts index 3b39ea79d42f..fd3c0a29effe 100644 --- a/apps/meteor/client/components/Omnichannel/hooks/useDepartmentsList.ts +++ b/apps/meteor/client/components/Omnichannel/hooks/useDepartmentsList.ts @@ -20,7 +20,6 @@ type DepartmentListItem = { _id: string; label: string; value: string; - _updatedAt: Date; }; export const useDepartmentsList = ( @@ -66,7 +65,6 @@ export const useDepartmentsList = ( _id, label: department.archived ? `${name} [${t('Archived')}]` : name, value: _id, - _updatedAt: new Date(_updatedAt || ''), }; }); @@ -75,7 +73,6 @@ export const useDepartmentsList = ( _id: '', label: t('All'), value: 'all', - _updatedAt: new Date(), }); options.haveNone && @@ -83,7 +80,6 @@ export const useDepartmentsList = ( _id: '', label: t('None'), value: '', - _updatedAt: new Date(), }); return { diff --git a/apps/meteor/client/components/Omnichannel/hooks/useLivechatTags.ts b/apps/meteor/client/components/Omnichannel/hooks/useLivechatTags.ts index 4bd85be40342..ce5704b66482 100644 --- a/apps/meteor/client/components/Omnichannel/hooks/useLivechatTags.ts +++ b/apps/meteor/client/components/Omnichannel/hooks/useLivechatTags.ts @@ -1,6 +1,8 @@ import { useEndpoint } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; +import { useOmnichannel } from '../../../hooks/omnichannel/useOmnichannel'; + type Props = { department?: string; text?: string; @@ -9,13 +11,19 @@ type Props = { export const useLivechatTags = (options: Props) => { const getTags = useEndpoint('GET', '/v1/livechat/tags'); + const { isEnterprise } = useOmnichannel(); const { department, text, viewAll } = options; - return useQuery(['/v1/livechat/tags', text, department], () => - getTags({ - text: text || '', - ...(department && { department }), - viewAll: viewAll ? 'true' : 'false', - }), + return useQuery( + ['/v1/livechat/tags', text, department], + () => + getTags({ + text: text || '', + ...(department && { department }), + viewAll: viewAll ? 'true' : 'false', + }), + { + enabled: isEnterprise, + }, ); }; diff --git a/apps/meteor/client/components/message/toolbox/MessageActionMenu.tsx b/apps/meteor/client/components/message/toolbox/MessageActionMenu.tsx index 4c5d442652f2..54a320ebf3d7 100644 --- a/apps/meteor/client/components/message/toolbox/MessageActionMenu.tsx +++ b/apps/meteor/client/components/message/toolbox/MessageActionMenu.tsx @@ -101,6 +101,7 @@ const MessageActionMenu = ({ options, onChangeMenuVisibility, ...props }: Messag data-qa-type='message-action' data-qa-id={option.id} role={option.role ? option.role : 'button'} + gap={!option.icon && option.type === 'apps'} /> ))} {index !== arr.length - 1 && } diff --git a/apps/meteor/client/lib/2fa/process2faReturn.ts b/apps/meteor/client/lib/2fa/process2faReturn.ts index e5ff73be77b1..95f7f1dcb361 100644 --- a/apps/meteor/client/lib/2fa/process2faReturn.ts +++ b/apps/meteor/client/lib/2fa/process2faReturn.ts @@ -1,10 +1,12 @@ import { SHA256 } from '@rocket.chat/sha256'; import { Meteor } from 'meteor/meteor'; +import { lazy } from 'react'; -import TwoFactorModal from '../../components/TwoFactorModal'; import { imperativeModal } from '../imperativeModal'; import { isTotpInvalidError, isTotpRequiredError } from './utils'; +const TwoFactorModal = lazy(() => import('../../components/TwoFactorModal')); + const twoFactorMethods = ['totp', 'email', 'password'] as const; type TwoFactorMethod = (typeof twoFactorMethods)[number]; diff --git a/apps/meteor/client/main.ts b/apps/meteor/client/main.ts index bb08f0242d4f..334515980787 100644 --- a/apps/meteor/client/main.ts +++ b/apps/meteor/client/main.ts @@ -1,13 +1,15 @@ -import '../ee/client/ecdh'; -import './polyfills'; +import { FlowRouter } from 'meteor/kadira:flow-router'; -import '../lib/oauthRedirectUriClient'; -import './lib/meteorCallWrapper'; -import './importPackages'; +FlowRouter.wait(); -import '../ee/client'; -import './methods'; -import './startup'; -import './views/admin'; -import './views/marketplace'; -import './views/account'; +FlowRouter.notFound = { + action: () => undefined, +}; + +import('./polyfills') + .then(() => Promise.all([import('./lib/meteorCallWrapper'), import('../lib/oauthRedirectUriClient')])) + .then(() => import('../ee/client/ecdh')) + .then(() => import('./importPackages')) + .then(() => Promise.all([import('./methods'), import('./startup')])) + .then(() => import('../ee/client')) + .then(() => Promise.all([import('./views/admin'), import('./views/marketplace'), import('./views/account')])); diff --git a/apps/meteor/client/polyfills/index.ts b/apps/meteor/client/polyfills/index.ts index 46f5bcb8d68d..f07d828a4602 100644 --- a/apps/meteor/client/polyfills/index.ts +++ b/apps/meteor/client/polyfills/index.ts @@ -4,4 +4,3 @@ import './childNodeRemove'; import './cssVars'; import './customEventPolyfill'; import './hoverTouchClick'; -import './objectFromEntries'; diff --git a/apps/meteor/client/polyfills/objectFromEntries.ts b/apps/meteor/client/polyfills/objectFromEntries.ts deleted file mode 100644 index d59198ebd1d3..000000000000 --- a/apps/meteor/client/polyfills/objectFromEntries.ts +++ /dev/null @@ -1,5 +0,0 @@ -Object.fromEntries = - Object.fromEntries || - function fromEntries(entries: Iterable): { [k: string]: T } { - return [...entries].reduce((obj, { 0: key, 1: val }) => Object.assign(obj, { [key]: val }), {}); - }; diff --git a/apps/meteor/client/providers/RouterProvider.tsx b/apps/meteor/client/providers/RouterProvider.tsx index 0dd7ee31deed..0f146ec83128 100644 --- a/apps/meteor/client/providers/RouterProvider.tsx +++ b/apps/meteor/client/providers/RouterProvider.tsx @@ -17,12 +17,6 @@ import React from 'react'; import { appLayout } from '../lib/appLayout'; import { queueMicrotask } from '../lib/utils/queueMicrotask'; -FlowRouter.wait(); - -FlowRouter.notFound = { - action: () => undefined, -}; - const subscribers = new Set<() => void>(); const listenToRouteChange = () => { diff --git a/apps/meteor/client/views/admin/info/DeploymentCard.tsx b/apps/meteor/client/views/admin/info/DeploymentCard.tsx index f1f3dde6aec0..6bc9ece2b850 100644 --- a/apps/meteor/client/views/admin/info/DeploymentCard.tsx +++ b/apps/meteor/client/views/admin/info/DeploymentCard.tsx @@ -66,6 +66,7 @@ const DeploymentCard = ({ info, statistics, instances }: DeploymentCardProps): R {t('Commit_details')} {t('github_HEAD')}: ({commit.hash ? commit.hash.slice(0, 9) : ''})
{t('Branch')}: {commit.branch} + {commit.subject} {t('PID')} diff --git a/apps/meteor/client/views/admin/rooms/RoomRow.tsx b/apps/meteor/client/views/admin/rooms/RoomRow.tsx new file mode 100644 index 000000000000..2c0dbb8c31a6 --- /dev/null +++ b/apps/meteor/client/views/admin/rooms/RoomRow.tsx @@ -0,0 +1,90 @@ +import { isDiscussion } from '@rocket.chat/core-typings'; +import type { IRoom, RoomAdminFieldsType } from '@rocket.chat/core-typings'; +import { Box, Icon } from '@rocket.chat/fuselage'; +import { useMediaQuery } from '@rocket.chat/fuselage-hooks'; +import { useRouter, useTranslation } from '@rocket.chat/ui-contexts'; +import React, { useCallback } from 'react'; + +import { GenericTableCell, GenericTableRow } from '../../../components/GenericTable'; +import RoomAvatar from '../../../components/avatar/RoomAvatar'; +import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; + +const roomTypeI18nMap = { + l: 'Omnichannel', + c: 'Channel', + d: 'Direct_Message', + p: 'Private_Channel', +} as const; + +const getRoomDisplayName = (room: Pick): string | undefined => + room.t === 'd' ? room.usernames?.join(' x ') : roomCoordinator.getRoomName(room.t, room); + +const RoomRow = ({ room }: { room: Pick }) => { + const t = useTranslation(); + const mediaQuery = useMediaQuery('(min-width: 1024px)'); + const router = useRouter(); + + const { _id, t: type, usersCount, msgs, default: isDefault, featured, ...args } = room; + const icon = roomCoordinator.getRoomDirectives(room.t).getIcon?.(room); + const roomName = getRoomDisplayName(room); + + const getRoomType = ( + room: Pick, + ): (typeof roomTypeI18nMap)[keyof typeof roomTypeI18nMap] | 'Teams_Public_Team' | 'Teams_Private_Team' | 'Discussion' => { + if (room.teamMain) { + return room.t === 'c' ? 'Teams_Public_Team' : 'Teams_Private_Team'; + } + if (isDiscussion(room)) { + return 'Discussion'; + } + return roomTypeI18nMap[(room as IRoom).t as keyof typeof roomTypeI18nMap]; + }; + + const onClick = useCallback( + (rid) => (): void => + router.navigate({ + name: 'admin-rooms', + params: { + context: 'edit', + id: rid, + }, + }), + [router], + ); + + return ( + + + + + + {icon && } + + {roomName} + + + + + + + {t(getRoomType(room))} + + + {usersCount} + {mediaQuery && {msgs}} + {mediaQuery && {isDefault ? t('True') : t('False')}} + {mediaQuery && {featured ? t('True') : t('False')}} + + ); +}; + +export default RoomRow; diff --git a/apps/meteor/client/views/admin/rooms/RoomsTable.tsx b/apps/meteor/client/views/admin/rooms/RoomsTable.tsx index 6b2c4435c8fa..c480fab9f657 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTable.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTable.tsx @@ -1,31 +1,23 @@ -import { type IRoom, isDiscussion, isPublicRoom } from '@rocket.chat/core-typings'; -import { Box, Icon, Pagination, States, StatesIcon, StatesTitle, StatesActions, StatesAction } from '@rocket.chat/fuselage'; +import { Pagination, States, StatesIcon, StatesTitle, StatesActions, StatesAction } from '@rocket.chat/fuselage'; import { useMediaQuery, useDebouncedValue } from '@rocket.chat/fuselage-hooks'; import type { OptionProp } from '@rocket.chat/ui-client'; -import { useEndpoint, useRouter, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; +import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; -import type { CSSProperties, ReactElement, MutableRefObject } from 'react'; -import React, { useRef, useState, useEffect, useMemo, useCallback } from 'react'; +import type { ReactElement, MutableRefObject } from 'react'; +import React, { useRef, useState, useEffect, useMemo } from 'react'; import GenericNoResults from '../../../components/GenericNoResults'; import { GenericTable, GenericTableBody, - GenericTableCell, GenericTableHeader, GenericTableHeaderCell, GenericTableLoadingTable, - GenericTableRow, } from '../../../components/GenericTable'; import { usePagination } from '../../../components/GenericTable/hooks/usePagination'; import { useSort } from '../../../components/GenericTable/hooks/useSort'; -import RoomAvatar from '../../../components/avatar/RoomAvatar'; -import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; +import RoomRow from './RoomRow'; import RoomsTableFilters from './RoomsTableFilters'; -import { useFilteredTypeRooms } from './useFilteredTypeRooms'; -import { useFilteredVisibilityRooms } from './useFilteredVisibilityRooms'; - -const style: CSSProperties = { whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }; type RoomFilters = { searchText: string; @@ -33,34 +25,9 @@ type RoomFilters = { visibility: OptionProp[]; }; -const DEFAULT_TYPES = ['d', 'p', 'c', 'l', 'discussions', 'teams']; - -const roomTypeI18nMap = { - l: 'Omnichannel', - c: 'Channel', - d: 'Direct_Message', - p: 'Private_Channel', -} as const; - -const getRoomType = ( - room: IRoom, -): (typeof roomTypeI18nMap)[keyof typeof roomTypeI18nMap] | 'Teams_Public_Team' | 'Teams_Private_Team' | 'Discussion' => { - if (room.teamMain) { - return room.t === 'c' ? 'Teams_Public_Team' : 'Teams_Private_Team'; - } - if (isDiscussion(room)) { - return 'Discussion'; - } - return roomTypeI18nMap[(room as IRoom).t as keyof typeof roomTypeI18nMap]; -}; - -const getRoomDisplayName = (room: IRoom): string | undefined => - room.t === 'd' ? room.usernames?.join(' x ') : roomCoordinator.getRoomName(room.t, room); - const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): ReactElement => { - const mediaQuery = useMediaQuery('(min-width: 1024px)'); - const t = useTranslation(); + const mediaQuery = useMediaQuery('(min-width: 1024px)'); const [roomFilters, setRoomFilters] = useState({ searchText: '', types: [], visibility: [] }); @@ -80,29 +47,15 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React sort: `{ "${sortBy}": ${sortDirection === 'asc' ? 1 : -1} }`, count: itemsPerPage, offset: searchText === prevRoomFilterText.current ? current : 0, - types: DEFAULT_TYPES, + types: [...roomFilters.types.map((roomType) => roomType.id)], }; - }, [searchText, sortBy, sortDirection, itemsPerPage, prevRoomFilterText, current, setCurrent]), + }, [searchText, sortBy, sortDirection, itemsPerPage, current, roomFilters.types, setCurrent]), 500, ); const getAdminRooms = useEndpoint('GET', '/v1/rooms.adminRooms'); - const dispatchToastMessage = useToastMessageDispatch(); - - const { data, refetch, isSuccess, isLoading, isError } = useQuery( - ['rooms', query, 'admin'], - async () => { - const adminRooms = await getAdminRooms(query); - - return { ...adminRooms, rooms: adminRooms.rooms as IRoom[] }; - }, - { - onError: (error) => { - dispatchToastMessage({ type: 'error', message: error }); - }, - }, - ); + const { data, refetch, isSuccess, isLoading, isError } = useQuery(['rooms', query, 'admin'], async () => getAdminRooms(query)); useEffect(() => { reload.current = refetch; @@ -112,48 +65,29 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React prevRoomFilterText.current = searchText; }, [searchText]); - const router = useRouter(); - - const onClick = useCallback( - (rid) => (): void => - router.navigate({ - name: 'admin-rooms', - params: { - context: 'edit', - id: rid, - }, - }), - [router], - ); - - const headers = useMemo( - () => - [ - - {t('Name')} - , - - {t('Type')} - , - - {t('Visibility')} - , - - {t('Users')} - , - mediaQuery && ( + const headers = ( + <> + + {t('Name')} + + + {t('Type')} + + + {t('Users')} + + {mediaQuery && ( + <> {t('Msgs')} - ), - mediaQuery && ( void> }): React > {t('Default')} - ), - mediaQuery && ( void> }): React > {t('Featured')} - ), - ].filter(Boolean), - [sortDirection, sortBy, setSort, t, mediaQuery], - ); - - const renderRow = useCallback( - (room: IRoom) => { - const { _id, t: type, usersCount, msgs, default: isDefault, featured, ...args } = room; - const visibility = isPublicRoom(room) ? 'Public' : 'Private'; - const icon = roomCoordinator.getRoomDirectives(room.t).getIcon?.(room); - const roomName = getRoomDisplayName(room); - - return ( - - - - - - - {icon && } - - {roomName} - - - - - - - - {t(getRoomType(room))} - - - - - - {t(visibility)} - - - - {usersCount} - {mediaQuery && {msgs}} - {mediaQuery && {isDefault ? t('True') : t('False')}} - {mediaQuery && {featured ? t('True') : t('False')}} - - ); - }, - [mediaQuery, onClick, t], + + )} + ); - function intersectArraysWithoutDuplicates(array1: IRoom[], array2: IRoom[]) { - const set2 = new Set(array2); - - return [...new Set(array1)].filter((item) => set2.has(item)); - } - - const roomsTypeList = useFilteredTypeRooms(roomFilters.types, isLoading, data?.rooms); - const roomsVisibilityList = useFilteredVisibilityRooms(roomFilters.visibility, isLoading, data?.rooms); - - const roomsList = intersectArraysWithoutDuplicates(roomsTypeList, roomsVisibilityList); - return ( <> - {isLoading && ( {headers} - + )} - {isSuccess && data && data?.rooms.length > 0 && ( + {isSuccess && data.rooms.length === 0 && } + {isSuccess && data.rooms.length > 0 && ( <> {headers} - {isSuccess && roomsList?.map((room) => renderRow(room))} + + {data.rooms?.map((room) => ( + + ))} + void> }): React /> )} - {isSuccess && data && data.rooms.length === 0 && } {isError && ( diff --git a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx index 0d8e5bd0c97e..dede0b34b918 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx @@ -12,12 +12,7 @@ const roomTypeFilterStructure = [ isGroupTitle: true, }, { - id: 'channels', - text: 'Channels', - checked: false, - }, - { - id: 'directMessages', + id: 'd', text: 'Direct_Message', checked: false, }, @@ -27,31 +22,23 @@ const roomTypeFilterStructure = [ checked: false, }, { - id: 'omnichannel', + id: 'l', text: 'Omnichannel', checked: false, }, { - id: 'teams', - text: 'Teams', + id: 'p', + text: 'Private_Channels', checked: false, }, -] as OptionProp[]; - -const roomVisibilityFilterStructure = [ { - id: 'filter_by_visibility', - text: 'Filter_by_visibility', - isGroupTitle: true, - }, - { - id: 'private', - text: 'Private', + id: 'c', + text: 'Public_Channels', checked: false, }, { - id: 'public', - text: 'Public', + id: 'teams', + text: 'Teams', checked: false, }, ] as OptionProp[]; @@ -60,13 +47,11 @@ const RoomsTableFilters = ({ setFilters }: { setFilters: Dispatch(roomTypeFilterStructure); - const [roomVisibilityOptions, setRoomVisibilityOptions] = useState(roomVisibilityFilterStructure); const [roomTypeSelectedOptions, setRoomTypeSelectedOptions] = useState([]); - const [roomVisibilitySelectedOptions, setRoomVisibilitySelectedOptions] = useState([]); useEffect(() => { - return setFilters({ searchText: text, types: roomTypeSelectedOptions, visibility: roomVisibilitySelectedOptions }); - }, [setFilters, roomTypeSelectedOptions, roomVisibilitySelectedOptions, text]); + return setFilters({ searchText: text, types: roomTypeSelectedOptions }); + }, [setFilters, roomTypeSelectedOptions, text]); const handleSearchTextChange = useCallback((event) => setText(event.currentTarget.value), []); @@ -100,17 +85,6 @@ const RoomsTableFilters = ({ setFilters }: { setFilters: Dispatch - - - - ); }; diff --git a/apps/meteor/client/views/admin/rooms/useFilteredTypeRooms.tsx b/apps/meteor/client/views/admin/rooms/useFilteredTypeRooms.tsx deleted file mode 100644 index 7114aa4f35c9..000000000000 --- a/apps/meteor/client/views/admin/rooms/useFilteredTypeRooms.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import type { IRoom } from '@rocket.chat/core-typings'; -import { isDiscussion, isTeamRoom, isDirectMessageRoom } from '@rocket.chat/core-typings'; -import type { OptionProp } from '@rocket.chat/ui-client'; - -const filterRoomsByChannels = (room: Partial): boolean => - (room.t === 'c' || room.t === 'p') && !isDiscussion(room) && !isTeamRoom(room); // can be a public channel or a private channel (group) -const filterRoomsByDirectMessages = (room: Partial): boolean => isDirectMessageRoom(room); -const filterRoomsByDiscussions = (room: Partial): boolean => isDiscussion(room); -const filterRoomsByOmnichannel = ({ t }: Partial): boolean => t === 'l'; // LiveChat -const filterRoomsByTeams = (room: Partial): boolean => isTeamRoom(room); - -const filters: Record) => boolean> = { - channels: filterRoomsByChannels, - directMessages: filterRoomsByDirectMessages, - discussions: filterRoomsByDiscussions, - omnichannel: filterRoomsByOmnichannel, - teams: filterRoomsByTeams, -}; - -export const useFilteredTypeRooms = (selectedOptions: OptionProp[], isLoading: boolean, rooms?: IRoom[]) => { - if (isLoading || !rooms) return []; - if (selectedOptions.length === 0) return rooms; - - let filtered: IRoom[] = []; - - selectedOptions.forEach((option) => { - filtered = [...new Set([...filtered, ...rooms.filter(filters[option.id])])]; - }); - - return filtered; -}; diff --git a/apps/meteor/client/views/admin/rooms/useFilteredVisibilityRooms.tsx b/apps/meteor/client/views/admin/rooms/useFilteredVisibilityRooms.tsx deleted file mode 100644 index be4064817001..000000000000 --- a/apps/meteor/client/views/admin/rooms/useFilteredVisibilityRooms.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import type { IRoom } from '@rocket.chat/core-typings'; -import { isPublicRoom } from '@rocket.chat/core-typings'; -import type { OptionProp } from '@rocket.chat/ui-client'; - -const filterRoomsByPrivate = (room: Partial): boolean => !isPublicRoom(room); -const filterRoomsByPublic = (room: Partial): boolean => isPublicRoom(room); - -const filters: Record) => boolean> = { - private: filterRoomsByPrivate, - public: filterRoomsByPublic, -}; - -export const useFilteredVisibilityRooms = (selectedOptions: OptionProp[], isLoading: boolean, rooms?: IRoom[]) => { - if (isLoading || !rooms) return []; - if (selectedOptions.length === 0) return rooms; - - let filtered: IRoom[] = []; - - selectedOptions.forEach((option) => { - filtered = [...new Set([...filtered, ...rooms.filter(filters[option.id])])]; - }); - - return filtered; -}; diff --git a/apps/meteor/client/views/omnichannel/departments/EditDepartmentWithData.tsx b/apps/meteor/client/views/omnichannel/departments/EditDepartmentWithData.tsx index 7f5389d0fbe7..64016d23db23 100644 --- a/apps/meteor/client/views/omnichannel/departments/EditDepartmentWithData.tsx +++ b/apps/meteor/client/views/omnichannel/departments/EditDepartmentWithData.tsx @@ -22,7 +22,7 @@ const EditDepartmentWithData = ({ id, title }: EditDepartmentWithDataProps) => { }); if (isInitialLoading) { - return ; + return ; } if (isError || (id && !data?.department)) { diff --git a/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/ActionsToolbarDropdown.tsx b/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/ActionsToolbarDropdown.tsx index 021f6518021a..5066ecb192e1 100644 --- a/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/ActionsToolbarDropdown.tsx +++ b/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/ActionsToolbarDropdown.tsx @@ -93,6 +93,7 @@ const ActionsToolbarDropdown = ({ isRecording, rid, tmid, actions, ...props }: A chat: chatContext, }) } + gap={!item.icon} > {item.icon && ['name']} />} {item.name} diff --git a/apps/meteor/ee/app/license/server/bundles.ts b/apps/meteor/ee/app/license/server/bundles.ts index 507283b3e60f..70f9d7b5a653 100644 --- a/apps/meteor/ee/app/license/server/bundles.ts +++ b/apps/meteor/ee/app/license/server/bundles.ts @@ -8,7 +8,6 @@ export type BundleFeature = | 'engagement-dashboard' | 'push-privacy' | 'scalability' - | 'teams-mention' | 'saml-enterprise' | 'device-management' | 'oauth-enterprise' @@ -32,7 +31,6 @@ const bundles: IBundle = { 'engagement-dashboard', 'push-privacy', 'scalability', - 'teams-mention', 'saml-enterprise', 'oauth-enterprise', 'device-management', diff --git a/apps/meteor/ee/app/teams-mention/server/EEMentionQueries.js b/apps/meteor/ee/app/teams-mention/server/EEMentionQueries.js deleted file mode 100644 index 020a90365d26..000000000000 --- a/apps/meteor/ee/app/teams-mention/server/EEMentionQueries.js +++ /dev/null @@ -1,19 +0,0 @@ -import { Team } from '@rocket.chat/core-services'; - -export const MentionQueriesEnterprise = { - async getUsers(sup, usernames) { - const uniqueUsernames = [...new Set(usernames)]; - const teams = await Team.listByNames(uniqueUsernames, { projection: { name: 1 } }); - - if (!teams?.length) { - return sup(usernames); - } - - return teams - .map((team) => ({ - ...team, - type: 'team', - })) - .concat(sup(usernames)); - }, -}; diff --git a/apps/meteor/ee/app/teams-mention/server/EESpotlight.js b/apps/meteor/ee/app/teams-mention/server/EESpotlight.js deleted file mode 100644 index 83829a759bba..000000000000 --- a/apps/meteor/ee/app/teams-mention/server/EESpotlight.js +++ /dev/null @@ -1,34 +0,0 @@ -import { Team } from '@rocket.chat/core-services'; - -export const SpotlightEnterprise = { - mapTeams(_, teams) { - return teams.map((t) => { - t.isTeam = true; - t.username = t.name; - t.status = 'online'; - return t; - }); - }, - - async _searchTeams(_, userId, { text, options, users, mentions }) { - if (!mentions) { - return users; - } - - options.limit -= users.length; - - if (options.limit <= 0) { - return users; - } - - const teamOptions = { ...options, projection: { name: 1, type: 1 } }; - const teams = await Team.search(userId, text, teamOptions); - users.push(...this.mapTeams(teams)); - - return users; - }, - - async _performExtraUserSearches(_, userId, searchParams) { - return this._searchTeams(userId, searchParams); - }, -}; diff --git a/apps/meteor/ee/app/teams-mention/server/index.ts b/apps/meteor/ee/app/teams-mention/server/index.ts deleted file mode 100644 index 631610e4c6d8..000000000000 --- a/apps/meteor/ee/app/teams-mention/server/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Team } from '@rocket.chat/core-services'; -import type { ITeamMember, IMessage } from '@rocket.chat/core-typings'; - -import { MentionQueries } from '../../../../app/mentions/server/server'; -import { callbacks } from '../../../../lib/callbacks'; -import { Spotlight } from '../../../../server/lib/spotlight'; -import { onLicense } from '../../license/server'; -import { overwriteClassOnLicense } from '../../license/server/license'; -import { MentionQueriesEnterprise } from './EEMentionQueries'; -import { SpotlightEnterprise } from './EESpotlight'; - -interface IExtraDataForNotification { - userMentions: any[]; - otherMentions: any[]; - message: IMessage; -} - -await onLicense('teams-mention', async () => { - // Override spotlight with EE version - await overwriteClassOnLicense('teams-mention', Spotlight, SpotlightEnterprise); - await overwriteClassOnLicense('teams-mention', MentionQueries, MentionQueriesEnterprise); - - callbacks.add('beforeGetMentions', async (mentionIds: string[], extra?: IExtraDataForNotification) => { - const { otherMentions } = extra ?? {}; - - const teamIds = otherMentions?.filter(({ type }) => type === 'team').map(({ _id }) => _id); - - if (!teamIds?.length) { - return mentionIds; - } - - const members: ITeamMember[] = await Team.getMembersByTeamIds(teamIds, { projection: { userId: 1 } }); - mentionIds.push( - ...new Set(members.map(({ userId }: { userId: string }) => userId).filter((userId: string) => !mentionIds.includes(userId))), - ); - - return mentionIds; - }); -}); diff --git a/apps/meteor/ee/client/omnichannel/additionalForms/DepartmentForwarding.tsx b/apps/meteor/ee/client/omnichannel/additionalForms/DepartmentForwarding.tsx index 1d93108debf7..ed9a40e4bd0e 100644 --- a/apps/meteor/ee/client/omnichannel/additionalForms/DepartmentForwarding.tsx +++ b/apps/meteor/ee/client/omnichannel/additionalForms/DepartmentForwarding.tsx @@ -28,6 +28,11 @@ export const DepartmentForwarding = ({ departmentId, value = [], handler, label const { phase: departmentsPhase, items: departmentsItems, itemCount: departmentsTotal } = useRecordList(departmentsList); + const options = useMemo(() => { + const pending = value.filter(({ value }) => !departmentsItems.find((dep) => dep.value === value)); + return [...departmentsItems, ...pending]; + }, [departmentsItems, value]); + return ( {t(label)} @@ -41,7 +46,7 @@ export const DepartmentForwarding = ({ departmentId, value = [], handler, label filter={debouncedDepartmentsFilter} setFilter={setDepartmentsFilter} onChange={handler} - options={departmentsItems} + options={options} value={value} placeholder={t('Select_an_option')} endReached={ diff --git a/apps/meteor/ee/server/index.ts b/apps/meteor/ee/server/index.ts index 2e526776c772..9b56239ad046 100644 --- a/apps/meteor/ee/server/index.ts +++ b/apps/meteor/ee/server/index.ts @@ -8,7 +8,6 @@ import '../app/livechat-enterprise/server/index'; import '../app/message-read-receipt/server/index'; import '../app/voip-enterprise/server/index'; import '../app/settings/server/index'; -import '../app/teams-mention/server/index'; import './api'; import './requestSeatsRoute'; import './configuration/index'; diff --git a/apps/meteor/ee/server/models/raw/LivechatRooms.ts b/apps/meteor/ee/server/models/raw/LivechatRooms.ts index 227da1c98e8c..70824c7d7130 100644 --- a/apps/meteor/ee/server/models/raw/LivechatRooms.ts +++ b/apps/meteor/ee/server/models/raw/LivechatRooms.ts @@ -540,6 +540,13 @@ export class LivechatRoomsRawEE extends LivechatRoomsRaw implements ILivechatRoo }, }, }, + { + $match: { + _id: { + $ne: null, + }, + }, + }, { $sort: sort || { total: 1 }, }, diff --git a/apps/meteor/ee/server/services/CHANGELOG.md b/apps/meteor/ee/server/services/CHANGELOG.md index c9759c95b0af..7966239693fd 100644 --- a/apps/meteor/ee/server/services/CHANGELOG.md +++ b/apps/meteor/ee/server/services/CHANGELOG.md @@ -1,5 +1,16 @@ # rocketchat-services +## 1.1.4 + +### Patch Changes + +- Updated dependencies [8a7d5d3898] + - @rocket.chat/model-typings@0.0.10 + - @rocket.chat/models@0.0.10 + - @rocket.chat/core-services@0.1.4 + - @rocket.chat/core-typings@6.3.4 + - @rocket.chat/rest-typings@6.3.4 + ## 1.1.3 ### Patch Changes diff --git a/apps/meteor/ee/server/services/package.json b/apps/meteor/ee/server/services/package.json index 5ef03a985d0f..a80d29269c88 100644 --- a/apps/meteor/ee/server/services/package.json +++ b/apps/meteor/ee/server/services/package.json @@ -1,7 +1,7 @@ { "name": "rocketchat-services", "private": true, - "version": "1.1.3", + "version": "1.1.4", "description": "Rocket.Chat Authorization service", "main": "index.js", "scripts": { diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 2e288c8dbd30..1e1d7e03ce02 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -225,7 +225,7 @@ "@rocket.chat/account-utils": "workspace:^", "@rocket.chat/agenda": "workspace:^", "@rocket.chat/api-client": "workspace:^", - "@rocket.chat/apps-engine": "1.41.0-alpha.325", + "@rocket.chat/apps-engine": "1.41.0-alpha.312", "@rocket.chat/base64": "workspace:^", "@rocket.chat/cas-validate": "workspace:^", "@rocket.chat/core-services": "workspace:^", @@ -257,6 +257,7 @@ "@rocket.chat/mp3-encoder": "0.24.0", "@rocket.chat/omnichannel-services": "workspace:^", "@rocket.chat/onboarding-ui": "next", + "@rocket.chat/password-policies": "workspace:^", "@rocket.chat/pdf-worker": "workspace:^", "@rocket.chat/poplib": "workspace:^", "@rocket.chat/presence": "workspace:^", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index 34d6a6c4e8ba..a0f6d0e0c6a8 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1622,7 +1622,7 @@ "Direct": "Direct", "Direction": "Direction", "Livechat_Facebook_API_Secret": "OmniChannel API Secret", - "Direct_Message": "Direct Message", + "Direct_Message": "Direct message", "Livechat_Facebook_Enabled": "Facebook integration enabled", "Direct_message_creation_description": "You are about to create a chat with multiple users. Add the ones you would like to talk, everyone in the same place, using direct messages.", "Direct_message_someone": "Direct message someone", @@ -4053,9 +4053,10 @@ "Privacy_summary": "Privacy summary", "Private": "Private", "private": "private", + "Private_channels": "Private channels", "Private_Apps": "Private Apps", "Private_Channel": "Private Channel", - "Private_Channels": "Private Channels", + "Private_Channels": "Private channels", "Private_Chats": "Private Chats", "Private_Group": "Private Group", "Private_Groups": "Private Groups", @@ -4085,7 +4086,7 @@ "Public": "Public", "public": "public", "Public_Channel": "Public Channel", - "Public_Channels": "Public Channels", + "Public_Channels": "Public channels", "Public_Community": "Public Community", "Public_URL": "Public URL", "Purchase_for_free": "Purchase for FREE", @@ -5177,6 +5178,8 @@ "Troubleshoot_Disable_Statistics_Generator_Alert": "This setting stops the processing all statistics making the info page outdated until someone clicks on the refresh button and may cause other missing information around the system!", "Troubleshoot_Disable_Workspace_Sync": "Disable Workspace Sync", "Troubleshoot_Disable_Workspace_Sync_Alert": "This setting stops the sync of this server with Rocket.Chat's cloud and may cause issues with marketplace and enteprise licenses!", + "Troubleshoot_Disable_Teams_Mention": "Disable Teams mention", + "Troubleshoot_Disable_Teams_Mention_Alert": "This setting disables the teams mention feature. User's won't be able to mention a Team by name in a message and get its members notified.", "True": "True", "Try_now": "Try now", "Try_searching_in_the_marketplace_instead": "Try searching in the Marketplace instead", diff --git a/apps/meteor/server/lib/spotlight.js b/apps/meteor/server/lib/spotlight.js index 38dc1b873878..62fcdc3a66b4 100644 --- a/apps/meteor/server/lib/spotlight.js +++ b/apps/meteor/server/lib/spotlight.js @@ -1,3 +1,4 @@ +import { Team } from '@rocket.chat/core-services'; import { Users, Subscriptions as SubscriptionsRaw, Rooms } from '@rocket.chat/models'; import { escapeRegExp } from '@rocket.chat/string-helpers'; @@ -133,8 +134,31 @@ export class Spotlight { } } - async _performExtraUserSearches(/* userId, searchParams */) { - // Overwrite this method to include extra searches + mapTeams(teams) { + return teams.map((t) => { + t.isTeam = true; + t.username = t.name; + t.status = 'online'; + return t; + }); + } + + async _searchTeams(userId, { text, options, users, mentions }) { + if (!mentions || settings.get('Troubleshoot_Disable_Teams_Mention')) { + return users; + } + + options.limit -= users.length; + + if (options.limit <= 0) { + return users; + } + + const teamOptions = { ...options, projection: { name: 1, type: 1 } }; + const teams = await Team.search(userId, text, teamOptions); + users.push(...this.mapTeams(teams)); + + return users; } async searchUsers({ userId, rid, text, usernames, mentions }) { @@ -245,7 +269,7 @@ export class Spotlight { return users; } - if (await this._performExtraUserSearches(userId, searchParams)) { + if (await this._searchTeams(userId, searchParams)) { return users; } diff --git a/apps/meteor/server/methods/getPasswordPolicy.ts b/apps/meteor/server/methods/getPasswordPolicy.ts index 7201999ff677..cc35f1cfb514 100644 --- a/apps/meteor/server/methods/getPasswordPolicy.ts +++ b/apps/meteor/server/methods/getPasswordPolicy.ts @@ -28,6 +28,9 @@ Meteor.methods({ method: 'getPasswordPolicy', }); } - return passwordPolicy.getPasswordPolicy(); + return passwordPolicy.getPasswordPolicy() as { + enabled: boolean; + policy: [name: TranslationKey, options?: Record][]; + }; }, }); diff --git a/apps/meteor/server/models/raw/Sessions.ts b/apps/meteor/server/models/raw/Sessions.ts index 68d5149232ed..c02fc8b5de99 100644 --- a/apps/meteor/server/models/raw/Sessions.ts +++ b/apps/meteor/server/models/raw/Sessions.ts @@ -167,9 +167,9 @@ const getProjectionByFullDate = (): { day: string; month: string; year: string } }); export const aggregates = { - dailySessionsOfYesterday( + dailySessions( collection: Collection, - { year, month, day }: DestructuredDate, + { start, end }: DestructuredRange, ): AggregationCursor< Pick & { time: number; @@ -178,115 +178,101 @@ export const aggregates = { _computedAt: string; } > { - return collection.aggregate< - Pick & { - time: number; - sessions: number; - devices: ISession['device'][]; - _computedAt: string; - } - >( - [ - { - $match: { - userId: { $exists: true }, - lastActivityAt: { $exists: true }, - device: { $exists: true }, - type: 'session', - $or: [ - { - year: { $lt: year }, - }, - { - year, - month: { $lt: month }, - }, - { - year, - month, - day: { $lte: day }, - }, - ], - }, - }, - { - $project: { - userId: 1, - device: 1, - day: 1, - month: 1, - year: 1, - mostImportantRole: 1, - time: { $trunc: { $divide: [{ $subtract: ['$lastActivityAt', '$loginAt'] }, 1000] } }, - }, - }, - { - $match: { - time: { $gt: 0 }, - }, + const pipeline = [ + { + $match: { + userId: { $exists: true }, + lastActivityAt: { $exists: true }, + device: { $exists: true }, + type: 'session', + ...matchBasedOnDate(start, end), }, - { - $group: { - _id: { - userId: '$userId', - device: '$device', - day: '$day', - month: '$month', - year: '$year', - }, - mostImportantRole: { $first: '$mostImportantRole' }, - time: { $sum: '$time' }, - sessions: { $sum: 1 }, - }, + }, + { + $project: { + userId: 1, + device: 1, + day: 1, + month: 1, + year: 1, + mostImportantRole: 1, + time: { $trunc: { $divide: [{ $subtract: ['$lastActivityAt', '$loginAt'] }, 1000] } }, }, - { - $sort: { - time: -1, - }, + }, + { + $match: { + time: { $gt: 0 }, }, - { - $group: { - _id: { - userId: '$_id.userId', - day: '$_id.day', - month: '$_id.month', - year: '$_id.year', - }, - mostImportantRole: { $first: '$mostImportantRole' }, - time: { $sum: '$time' }, - sessions: { $sum: '$sessions' }, - devices: { - $push: { - sessions: '$sessions', - time: '$time', - device: '$_id.device', - }, - }, + }, + { + $group: { + _id: { + userId: '$userId', + device: '$device', + day: '$day', + month: '$month', + year: '$year', }, + mostImportantRole: { $first: '$mostImportantRole' }, + time: { $sum: '$time' }, + sessions: { $sum: 1 }, }, - { - $sort: { - _id: 1, - }, + }, + { + $sort: { + time: -1, }, - { - $project: { - _id: 0, - type: { $literal: 'user_daily' }, - _computedAt: { $literal: new Date() }, + }, + { + $group: { + _id: { + userId: '$_id.userId', day: '$_id.day', month: '$_id.month', year: '$_id.year', - userId: '$_id.userId', - mostImportantRole: 1, - time: 1, - sessions: 1, - devices: 1, + }, + mostImportantRole: { $first: '$mostImportantRole' }, + time: { $sum: '$time' }, + sessions: { $sum: '$sessions' }, + devices: { + $push: { + sessions: '$sessions', + time: '$time', + device: '$_id.device', + }, }, }, - ], - { allowDiskUse: true }, - ); + }, + { + $sort: { + _id: 1, + }, + }, + { + $project: { + _id: 0, + type: { $literal: 'user_daily' }, + _computedAt: { $literal: new Date() }, + day: '$_id.day', + month: '$_id.month', + year: '$_id.year', + userId: '$_id.userId', + mostImportantRole: 1, + time: 1, + sessions: 1, + devices: 1, + }, + }, + ]; + + return collection.aggregate< + Pick & { + time: number; + sessions: number; + devices: ISession['device'][]; + _computedAt: string; + } + >(pipeline, { allowDiskUse: true }); }, async getUniqueUsersOfYesterday( @@ -1616,4 +1602,23 @@ export class SessionsRaw extends BaseRaw implements ISessionsModel { return this.col.bulkWrite(ops, { ordered: false }); } + + async updateDailySessionById(_id: ISession['_id'], record: Partial): Promise { + return this.updateOne({ _id }, { $set: record }, { upsert: true }); + } + + async updateAllSessionsByDateToComputed({ start, end }: DestructuredRange): Promise { + return this.updateMany( + { + type: 'session', + ...matchBasedOnDate(start, end), + }, + { + $set: { + type: 'computed-session', + _computedAt: new Date(), + }, + }, + ); + } } diff --git a/apps/meteor/server/settings/accounts.ts b/apps/meteor/server/settings/accounts.ts index a7592829578d..ccc87b0ffd24 100644 --- a/apps/meteor/server/settings/accounts.ts +++ b/apps/meteor/server/settings/accounts.ts @@ -745,50 +745,60 @@ export const createAccountSettings = () => await this.section('Password_Policy', async function () { await this.add('Accounts_Password_Policy_Enabled', false, { type: 'boolean', + public: true, }); const enableQuery = { _id: 'Accounts_Password_Policy_Enabled', value: true, + public: true, }; await this.add('Accounts_Password_Policy_MinLength', 7, { type: 'int', + public: true, enableQuery, }); await this.add('Accounts_Password_Policy_MaxLength', -1, { type: 'int', + public: true, enableQuery, }); await this.add('Accounts_Password_Policy_ForbidRepeatingCharacters', true, { type: 'boolean', + public: true, enableQuery, }); await this.add('Accounts_Password_Policy_ForbidRepeatingCharactersCount', 3, { type: 'int', + public: true, enableQuery, }); await this.add('Accounts_Password_Policy_AtLeastOneLowercase', true, { type: 'boolean', + public: true, enableQuery, }); await this.add('Accounts_Password_Policy_AtLeastOneUppercase', true, { type: 'boolean', + public: true, enableQuery, }); await this.add('Accounts_Password_Policy_AtLeastOneNumber', true, { type: 'boolean', + public: true, enableQuery, }); await this.add('Accounts_Password_Policy_AtLeastOneSpecialCharacter', true, { type: 'boolean', + public: true, enableQuery, }); }); diff --git a/apps/meteor/server/settings/troubleshoot.ts b/apps/meteor/server/settings/troubleshoot.ts index ecc0d37dc711..bc1cd1484301 100644 --- a/apps/meteor/server/settings/troubleshoot.ts +++ b/apps/meteor/server/settings/troubleshoot.ts @@ -44,4 +44,8 @@ export const createTroubleshootSettings = () => type: 'boolean', i18nDescription: 'Troubleshoot_Disable_Workspace_Sync_Alert', }); + await this.add('Troubleshoot_Disable_Teams_Mention', false, { + type: 'boolean', + i18nDescription: 'Troubleshoot_Disable_Teams_Mention_Alert', + }); }); diff --git a/apps/meteor/tests/data/users.helper.js b/apps/meteor/tests/data/users.helper.js index 9dac1772dcdc..92425902cb5b 100644 --- a/apps/meteor/tests/data/users.helper.js +++ b/apps/meteor/tests/data/users.helper.js @@ -77,3 +77,15 @@ export const getMe = (overrideCredential = credentials) => resolve(res.body); }); }); + +export const setUserActiveStatus = (userId, activeStatus = true) => + new Promise((resolve) => { + request + .post(api('users.setActiveStatus')) + .set(credentials) + .send({ + userId, + activeStatus, + }) + .end(resolve); + }); diff --git a/apps/meteor/tests/e2e/homepage.spec.ts b/apps/meteor/tests/e2e/homepage.spec.ts index 380fa54d2af3..4f8f9d09a2f3 100644 --- a/apps/meteor/tests/e2e/homepage.spec.ts +++ b/apps/meteor/tests/e2e/homepage.spec.ts @@ -32,7 +32,7 @@ test.describe.serial('homepage', () => { await adminPage.close(); }); - test('layout', async () => { + test('expect customize button and all cards to be visible', async () => { await test.step('expect show customize button', async () => { await expect(adminPage.locator('role=button[name="Customize"]')).toBeVisible(); }); @@ -47,7 +47,7 @@ test.describe.serial('homepage', () => { await expect((await api.post('/settings/Layout_Home_Body', { value: '' })).status()).toBe(200); }); - test('layout', async () => { + test('visibility and button functionality in custom body with empty custom content', async () => { await test.step('expect default value in custom body', async () => { await expect( adminPage.locator('role=status[name="Admins may insert content html to be rendered in this white space."]'), @@ -70,7 +70,7 @@ test.describe.serial('homepage', () => { await expect((await api.post('/settings/Layout_Home_Body', { value: 'Hello admin' })).status()).toBe(200); }); - test('layout', async () => { + test('visibility and button functionality in custom body with custom content', async () => { await test.step('expect custom body to be visible', async () => { await expect(adminPage.locator('role=status[name="Hello admin"]')).toBeVisible(); }); @@ -122,7 +122,7 @@ test.describe.serial('homepage', () => { await regularUserPage.close(); }); - test('layout', async () => { + test('the option customize is not be active', async () => { await test.step('expect to not show customize button', async () => { await expect(regularUserPage.locator('role=button[name="Customize"]')).not.toBeVisible(); }); @@ -162,7 +162,7 @@ test.describe.serial('homepage', () => { expect((await api.post('/settings/Layout_Home_Title', { value: 'Home' })).status()).toBe(200); }); - test('layout', async () => { + test('expect welcome text and header text to be correct', async () => { await test.step('expect welcome text to be NewSiteName', async () => { await expect(regularUserPage.locator('role=heading[name="Welcome to NewSiteName"]')).toBeVisible(); }); @@ -202,7 +202,7 @@ test.describe.serial('homepage', () => { expect((await api.post('/settings/Layout_Custom_Body_Only', { value: false })).status()).toBe(200); }); - test('layout', async () => { + test('expect default layout not be visible and custom body visible', async () => { await test.step('expect default layout to not be visible', async () => { await expect(regularUserPage.locator('[data-qa-id="homepage-welcome-text"]')).not.toBeVisible(); }); diff --git a/apps/meteor/tests/end-to-end/api/01-users.js b/apps/meteor/tests/end-to-end/api/01-users.js index 130f365c96c5..d99fa68a036f 100644 --- a/apps/meteor/tests/end-to-end/api/01-users.js +++ b/apps/meteor/tests/end-to-end/api/01-users.js @@ -23,7 +23,7 @@ import { imgURL } from '../../data/interactions.js'; import { updatePermission, updateSetting } from '../../data/permissions.helper'; import { createRoom } from '../../data/rooms.helper'; import { adminEmail, preferences, password, adminUsername } from '../../data/user'; -import { createUser, login, deleteUser, getUserStatus } from '../../data/users.helper.js'; +import { createUser, login, deleteUser, getUserStatus, getUserByUsername } from '../../data/users.helper.js'; async function createChannel(userCredentials, name) { const res = await request.post(api('channels.create')).set(userCredentials).send({ @@ -3447,6 +3447,24 @@ describe('[Users]', function () { .end(done); }); }); + it('users should retain their roles when they are deactivated', async () => { + const testUser = await createUser({ roles: ['user', 'livechat-agent'] }); + + await request + .post(api('users.setActiveStatus')) + .set(credentials) + .send({ + activeStatus: false, + userId: testUser._id, + }) + .expect('Content-Type', 'application/json') + .expect(200); + + const user = await getUserByUsername(testUser.username); + expect(user).to.have.property('roles'); + expect(user.roles).to.be.an('array').of.length(2); + expect(user.roles).to.include('user', 'livechat-agent'); + }); }); describe('[/users.deactivateIdle]', () => { diff --git a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts index 9085e1cd388d..0585c20bf127 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts @@ -22,12 +22,12 @@ import { getDepartmentById, deleteDepartment, } from '../../../data/livechat/department'; -import { createAgent, makeAgentAvailable } from '../../../data/livechat/rooms'; +import { createAgent, createManager, makeAgentAvailable } from '../../../data/livechat/rooms'; import { removeAgent } from '../../../data/livechat/users'; import { removePermissionFromAllRoles, restorePermissionToRoles, updateSetting, updateEESetting } from '../../../data/permissions.helper'; import type { IUserCredentialsHeader } from '../../../data/user'; import { password } from '../../../data/user'; -import { createUser, deleteUser, getMe, login } from '../../../data/users.helper'; +import { setUserActiveStatus, createUser, deleteUser, getMe, getUserByUsername, login } from '../../../data/users.helper'; import { IS_EE } from '../../../e2e/config/constants'; describe('LIVECHAT - business hours', function () { @@ -249,7 +249,7 @@ describe('LIVECHAT - business hours', function () { }); }); - (IS_EE ? describe : describe.skip)('[EE] BH operations upon creation', () => { + (IS_EE ? describe : describe.skip)('[EE][BH] On Business Hour created', () => { let defaultBusinessHour: ILivechatBusinessHour; before(async () => { @@ -288,7 +288,7 @@ describe('LIVECHAT - business hours', function () { // and "dep2" and both these depts are connected to same BH, then in this case after // archiving "dep1", we'd still need to BH within this user's cache since he's part of // "dep2" which is linked to BH - (IS_EE ? describe : describe.skip)('[EE] BH operations post department archiving', () => { + (IS_EE ? describe : describe.skip)('[EE][BH] On Department archived', () => { let defaultBusinessHour: ILivechatBusinessHour; let customBusinessHour: ILivechatBusinessHour; let deptLinkedToCustomBH: ILivechatDepartment; @@ -434,7 +434,7 @@ describe('LIVECHAT - business hours', function () { await deleteUser(agentLinkedToDept.user); }); }); - (IS_EE ? describe : describe.skip)('[EE] BH operations post department disablement', () => { + (IS_EE ? describe : describe.skip)('[EE][BH] On Department disabled', () => { let defaultBusinessHour: ILivechatBusinessHour; let customBusinessHour: ILivechatBusinessHour; let deptLinkedToCustomBH: ILivechatDepartment; @@ -578,7 +578,7 @@ describe('LIVECHAT - business hours', function () { await deleteUser(agentLinkedToDept.user); }); }); - (IS_EE ? describe : describe.skip)('[EE] BH operations post department removal', () => { + (IS_EE ? describe : describe.skip)('[EE][BH] On Department removed', () => { let defaultBusinessHour: ILivechatBusinessHour; let customBusinessHour: ILivechatBusinessHour; let deptLinkedToCustomBH: ILivechatDepartment; @@ -702,7 +702,7 @@ describe('LIVECHAT - business hours', function () { await deleteUser(agentLinkedToDept.user); }); }); - describe('BH behavior upon new agent creation/deletion', () => { + describe('[CE][BH] On Agent created/removed', () => { let defaultBH: ILivechatBusinessHour; let agent: ILivechatAgent; let agentCredentials: IUserCredentialsHeader; @@ -782,4 +782,75 @@ describe('LIVECHAT - business hours', function () { await deleteUser(agent._id); }); }); + + describe('[CE][BH] On Agent deactivated/activated', () => { + let defaultBH: ILivechatBusinessHour; + let agent: ILivechatAgent; + + before(async () => { + await updateSetting('Livechat_enable_business_hours', true); + await updateEESetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.SINGLE); + // wait for callbacks to run + await sleep(2000); + + defaultBH = await getDefaultBusinessHour(); + await openOrCloseBusinessHour(defaultBH, true); + + agent = await createUser(); + await createAgent(agent.username); + }); + + after(async () => { + await deleteUser(agent); + await updateSetting('Livechat_enable_business_hours', false); + }); + + it('should verify if agent becomes unavailable to take chats when user is deactivated', async () => { + await setUserActiveStatus(agent._id, false); + + const latestAgent = await getUserByUsername(agent.username); + + expect(latestAgent).to.be.an('object'); + expect(latestAgent.statusLivechat).to.be.equal(ILivechatAgentStatus.NOT_AVAILABLE); + }); + + it('should verify if agent becomes available to take chats when user is activated, if business hour is active', async () => { + await openOrCloseBusinessHour(defaultBH, true); + + await setUserActiveStatus(agent._id, true); + + const latestAgent = await getUserByUsername(agent.username); + + expect(latestAgent).to.be.an('object'); + expect(latestAgent.statusLivechat).to.be.equal(ILivechatAgentStatus.AVAILABLE); + }); + it('should verify if agent becomes unavailable to take chats when user is activated, if business hour is inactive', async () => { + await openOrCloseBusinessHour(defaultBH, false); + + await setUserActiveStatus(agent._id, false); + await setUserActiveStatus(agent._id, true); + + const latestAgent = await getUserByUsername(agent.username); + + expect(latestAgent).to.be.an('object'); + expect(latestAgent.statusLivechat).to.be.equal(ILivechatAgentStatus.NOT_AVAILABLE); + }); + it('should verify if managers are not able to make deactivated agents available', async () => { + await createManager(); + + await setUserActiveStatus(agent._id, false); + + const response = await request + .post(api('livechat/agent.status')) + .set(credentials) + .send({ + status: 'available', + agentId: agent._id, + }) + .expect(400); + + expect(response.body).to.have.property('success', false); + expect(response.body).to.have.property('error', 'error-user-deactivated'); + }); + }); }); diff --git a/apps/meteor/tests/unit/app/lib/server.tests.js b/apps/meteor/tests/unit/app/lib/server.tests.js deleted file mode 100644 index f77b5a4d1cb8..000000000000 --- a/apps/meteor/tests/unit/app/lib/server.tests.js +++ /dev/null @@ -1,277 +0,0 @@ -import { expect } from 'chai'; -import proxyquire from 'proxyquire'; - -const { default: PasswordPolicyClass } = proxyquire.noCallThru().load('../../../../app/lib/server/lib/PasswordPolicyClass', { - 'meteor/meteor': { - Meteor: { - absoluteUrl() { - return 'http://localhost:3000/'; - }, - }, - }, - '@rocket.chat/random': { - Random: { - id: () => 1, - }, - }, -}); - -describe('PasswordPolicyClass', () => { - describe('Default options', () => { - const passwordPolice = new PasswordPolicyClass(); - it('should be disabled', () => { - expect(passwordPolice.enabled).to.be.equal(false); - }); - it('should have minLength = -1', () => { - expect(passwordPolice.minLength).to.be.equal(-1); - }); - it('should have maxLength = -1', () => { - expect(passwordPolice.maxLength).to.be.equal(-1); - }); - it('should have forbidRepeatingCharacters = false', () => { - expect(passwordPolice.forbidRepeatingCharacters).to.be.false; - }); - it('should have forbidRepeatingCharactersCount = 3', () => { - expect(passwordPolice.forbidRepeatingCharactersCount).to.be.equal(3); - }); - it('should have mustContainAtLeastOneLowercase = false', () => { - expect(passwordPolice.mustContainAtLeastOneLowercase).to.be.false; - }); - it('should have mustContainAtLeastOneUppercase = false', () => { - expect(passwordPolice.mustContainAtLeastOneUppercase).to.be.false; - }); - it('should have mustContainAtLeastOneNumber = false', () => { - expect(passwordPolice.mustContainAtLeastOneNumber).to.be.false; - }); - it('should have mustContainAtLeastOneSpecialCharacter = false', () => { - expect(passwordPolice.mustContainAtLeastOneSpecialCharacter).to.be.false; - }); - - describe('Password tests with default options', () => { - it('should allow all passwords', () => { - const passwordPolice = new PasswordPolicyClass({ throwError: false }); - expect(passwordPolice.validate()).to.be.equal(false); - expect(passwordPolice.validate('')).to.be.equal(false); - expect(passwordPolice.validate(' ')).to.be.equal(false); - expect(passwordPolice.validate('a')).to.be.equal(true); - expect(passwordPolice.validate('aaaaaaaaa')).to.be.equal(true); - }); - }); - }); - - describe('Password tests with options', () => { - it('should not allow non string or empty', () => { - const passwordPolice = new PasswordPolicyClass({ - enabled: true, - throwError: false, - }); - expect(passwordPolice.validate()).to.be.false; - expect(passwordPolice.validate(1)).to.be.false; - expect(passwordPolice.validate(true)).to.be.false; - expect(passwordPolice.validate(new Date())).to.be.false; - expect(passwordPolice.validate(new Function())).to.be.false; - expect(passwordPolice.validate('')).to.be.false; - }); - - it('should restrict by minLength', () => { - const passwordPolice = new PasswordPolicyClass({ - enabled: true, - minLength: 5, - throwError: false, - }); - - expect(passwordPolice.validate('1')).to.be.false; - expect(passwordPolice.validate('1234')).to.be.false; - expect(passwordPolice.validate('12345')).to.be.true; - expect(passwordPolice.validate(' ')).to.be.false; - }); - - it('should restrict by maxLength', () => { - const passwordPolice = new PasswordPolicyClass({ - enabled: true, - maxLength: 5, - throwError: false, - }); - - expect(passwordPolice.validate('1')).to.be.true; - expect(passwordPolice.validate('12345')).to.be.true; - expect(passwordPolice.validate('123456')).to.be.false; - expect(passwordPolice.validate(' ')).to.be.false; - }); - - it('should allow repeated characters', () => { - const passwordPolice = new PasswordPolicyClass({ - enabled: true, - forbidRepeatingCharacters: false, - throwError: false, - }); - - expect(passwordPolice.validate('1')).to.be.true; - expect(passwordPolice.validate('12345')).to.be.true; - expect(passwordPolice.validate('123456')).to.be.true; - expect(passwordPolice.validate(' ')).to.be.false; - expect(passwordPolice.validate('11111111111111')).to.be.true; - }); - - it('should restrict repeated characters', () => { - const passwordPolice = new PasswordPolicyClass({ - enabled: true, - forbidRepeatingCharacters: true, - forbidRepeatingCharactersCount: 3, - throwError: false, - }); - - expect(passwordPolice.validate('1')).to.be.true; - expect(passwordPolice.validate('11')).to.be.true; - expect(passwordPolice.validate('111')).to.be.true; - expect(passwordPolice.validate('1111')).to.be.false; - expect(passwordPolice.validate(' ')).to.be.false; - expect(passwordPolice.validate('123456')).to.be.true; - }); - - it('should restrict repeated characters customized', () => { - const passwordPolice = new PasswordPolicyClass({ - enabled: true, - forbidRepeatingCharacters: true, - forbidRepeatingCharactersCount: 5, - throwError: false, - }); - - expect(passwordPolice.validate('1')).to.be.true; - expect(passwordPolice.validate('11')).to.be.true; - expect(passwordPolice.validate('111')).to.be.true; - expect(passwordPolice.validate('1111')).to.be.true; - expect(passwordPolice.validate('11111')).to.be.true; - expect(passwordPolice.validate('111111')).to.be.false; - expect(passwordPolice.validate(' ')).to.be.false; - expect(passwordPolice.validate('123456')).to.be.true; - }); - - it('should contain one lowercase', () => { - const passwordPolice = new PasswordPolicyClass({ - enabled: true, - mustContainAtLeastOneLowercase: true, - throwError: false, - }); - - expect(passwordPolice.validate('a')).to.be.true; - expect(passwordPolice.validate('aa')).to.be.true; - expect(passwordPolice.validate('A')).to.be.false; - expect(passwordPolice.validate(' ')).to.be.false; - expect(passwordPolice.validate('123456')).to.be.false; - expect(passwordPolice.validate('AAAAA')).to.be.false; - expect(passwordPolice.validate('AAAaAAA')).to.be.true; - }); - - it('should contain one uppercase', () => { - const passwordPolice = new PasswordPolicyClass({ - enabled: true, - mustContainAtLeastOneUppercase: true, - throwError: false, - }); - - expect(passwordPolice.validate('a')).to.be.false; - expect(passwordPolice.validate('aa')).to.be.false; - expect(passwordPolice.validate('A')).to.be.true; - expect(passwordPolice.validate(' ')).to.be.false; - expect(passwordPolice.validate('123456')).to.be.false; - expect(passwordPolice.validate('AAAAA')).to.be.true; - expect(passwordPolice.validate('AAAaAAA')).to.be.true; - }); - - it('should contain one number', () => { - const passwordPolice = new PasswordPolicyClass({ - enabled: true, - mustContainAtLeastOneNumber: true, - throwError: false, - }); - - expect(passwordPolice.validate('a')).to.be.false; - expect(passwordPolice.validate('aa')).to.be.false; - expect(passwordPolice.validate('A')).to.be.false; - expect(passwordPolice.validate(' ')).to.be.false; - expect(passwordPolice.validate('123456')).to.be.true; - expect(passwordPolice.validate('AAAAA')).to.be.false; - expect(passwordPolice.validate('AAAaAAA')).to.be.false; - expect(passwordPolice.validate('AAAa1AAA')).to.be.true; - }); - - it('should contain one special character', () => { - const passwordPolice = new PasswordPolicyClass({ - enabled: true, - mustContainAtLeastOneSpecialCharacter: true, - throwError: false, - }); - - expect(passwordPolice.validate('a')).to.be.false; - expect(passwordPolice.validate('aa')).to.be.false; - expect(passwordPolice.validate('A')).to.be.false; - expect(passwordPolice.validate(' ')).to.be.false; - expect(passwordPolice.validate('123456')).to.be.false; - expect(passwordPolice.validate('AAAAA')).to.be.false; - expect(passwordPolice.validate('AAAaAAA')).to.be.false; - expect(passwordPolice.validate('AAAa1AAA')).to.be.false; - expect(passwordPolice.validate('AAAa@AAA')).to.be.true; - }); - }); - - describe('Password generator', () => { - it('should return a random password', () => { - const passwordPolice = new PasswordPolicyClass({ - enabled: true, - throwError: false, - }); - - expect(passwordPolice.generatePassword()).to.not.be.undefined; - }); - }); - - describe('Password Policy', () => { - it('should return a correct password policy', () => { - const passwordPolice = new PasswordPolicyClass({ - enabled: true, - throwError: false, - minLength: 10, - maxLength: 20, - forbidRepeatingCharacters: true, - forbidRepeatingCharactersCount: 4, - mustContainAtLeastOneLowercase: true, - mustContainAtLeastOneUppercase: true, - mustContainAtLeastOneNumber: true, - mustContainAtLeastOneSpecialCharacter: true, - }); - - const policy = passwordPolice.getPasswordPolicy(); - - expect(policy).to.not.be.undefined; - expect(policy.enabled).to.be.true; - expect(policy.policy.length).to.be.equal(8); - expect(policy.policy[0][0]).to.be.equal('get-password-policy-minLength'); - expect(policy.policy[0][1].minLength).to.be.equal(10); - }); - - it('should return correct values if policy is disabled', () => { - const passwordPolice = new PasswordPolicyClass({ - enabled: false, - }); - - const policy = passwordPolice.getPasswordPolicy(); - - expect(policy.enabled).to.be.false; - expect(policy.policy.length).to.be.equal(0); - }); - - it('should return correct values if policy is enabled but no specifiers exists', () => { - const passwordPolice = new PasswordPolicyClass({ - enabled: true, - }); - - const policy = passwordPolice.getPasswordPolicy(); - - expect(policy.enabled).to.be.true; - // even when no policy is specified, forbidRepeatingCharactersCount is still configured - // since its default value is 3 - expect(policy.policy.length).to.be.equal(1); - }); - }); -}); diff --git a/ee/apps/account-service/CHANGELOG.md b/ee/apps/account-service/CHANGELOG.md index 67bb7a8164f5..89607251a0fd 100644 --- a/ee/apps/account-service/CHANGELOG.md +++ b/ee/apps/account-service/CHANGELOG.md @@ -1,5 +1,16 @@ # @rocket.chat/account-service +## 0.2.4 + +### Patch Changes + +- Updated dependencies [8a7d5d3898] + - @rocket.chat/model-typings@0.0.10 + - @rocket.chat/models@0.0.10 + - @rocket.chat/core-services@0.1.4 + - @rocket.chat/core-typings@6.3.4 + - @rocket.chat/rest-typings@6.3.4 + ## 0.2.3 ### Patch Changes diff --git a/ee/apps/account-service/package.json b/ee/apps/account-service/package.json index 8b6c0efc9796..39dce6f462f9 100644 --- a/ee/apps/account-service/package.json +++ b/ee/apps/account-service/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/account-service", "private": true, - "version": "0.2.3", + "version": "0.2.4", "description": "Rocket.Chat Account service", "scripts": { "build": "tsc -p tsconfig.json", diff --git a/ee/apps/authorization-service/CHANGELOG.md b/ee/apps/authorization-service/CHANGELOG.md index 882144f1602f..9873537590d3 100644 --- a/ee/apps/authorization-service/CHANGELOG.md +++ b/ee/apps/authorization-service/CHANGELOG.md @@ -1,5 +1,16 @@ # @rocket.chat/authorization-service +## 0.2.4 + +### Patch Changes + +- Updated dependencies [8a7d5d3898] + - @rocket.chat/model-typings@0.0.10 + - @rocket.chat/models@0.0.10 + - @rocket.chat/core-services@0.1.4 + - @rocket.chat/core-typings@6.3.4 + - @rocket.chat/rest-typings@6.3.4 + ## 0.2.3 ### Patch Changes diff --git a/ee/apps/authorization-service/package.json b/ee/apps/authorization-service/package.json index 02431b537db5..b2a595366474 100644 --- a/ee/apps/authorization-service/package.json +++ b/ee/apps/authorization-service/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/authorization-service", "private": true, - "version": "0.2.3", + "version": "0.2.4", "description": "Rocket.Chat Authorization service", "scripts": { "build": "tsc -p tsconfig.json", diff --git a/ee/apps/ddp-streamer/CHANGELOG.md b/ee/apps/ddp-streamer/CHANGELOG.md index 242f5b517039..b2a64786913b 100644 --- a/ee/apps/ddp-streamer/CHANGELOG.md +++ b/ee/apps/ddp-streamer/CHANGELOG.md @@ -1,5 +1,18 @@ # @rocket.chat/ddp-streamer +## 0.1.4 + +### Patch Changes + +- Updated dependencies [8a7d5d3898] + - @rocket.chat/model-typings@0.0.10 + - @rocket.chat/models@0.0.10 + - @rocket.chat/core-services@0.1.4 + - @rocket.chat/instance-status@0.0.10 + - @rocket.chat/core-typings@6.3.4 + - @rocket.chat/rest-typings@6.3.4 + - @rocket.chat/ui-contexts@1.0.4 + ## 0.1.3 ### Patch Changes diff --git a/ee/apps/ddp-streamer/Dockerfile b/ee/apps/ddp-streamer/Dockerfile index 83f1eb282c87..9a3f9ef9c582 100644 --- a/ee/apps/ddp-streamer/Dockerfile +++ b/ee/apps/ddp-streamer/Dockerfile @@ -16,6 +16,9 @@ COPY ./packages/core-typings/dist packages/core-typings/dist COPY ./packages/rest-typings/package.json packages/rest-typings/package.json COPY ./packages/rest-typings/dist packages/rest-typings/dist +COPY ./packages/password-policies/package.json packages/password-policies/package.json +COPY ./packages/password-policies/dist packages/password-policies/dist + COPY ./packages/ui-contexts/package.json packages/ui-contexts/package.json COPY ./packages/ui-contexts/dist packages/ui-contexts/dist diff --git a/ee/apps/ddp-streamer/package.json b/ee/apps/ddp-streamer/package.json index 32876be36fd8..f6eb59fedcfd 100644 --- a/ee/apps/ddp-streamer/package.json +++ b/ee/apps/ddp-streamer/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/ddp-streamer", "private": true, - "version": "0.1.3", + "version": "0.1.4", "description": "Rocket.Chat DDP-Streamer service", "scripts": { "build": "tsc -p tsconfig.json", diff --git a/ee/apps/ddp-streamer/src/DDPStreamer.ts b/ee/apps/ddp-streamer/src/DDPStreamer.ts index 868ad8fec202..bccb35d2b326 100644 --- a/ee/apps/ddp-streamer/src/DDPStreamer.ts +++ b/ee/apps/ddp-streamer/src/DDPStreamer.ts @@ -72,6 +72,15 @@ export class DDPStreamer extends ServiceClass { return; } + metrics.register({ + name: 'rocketchat_subscription', + type: 'histogram', + labelNames: ['subscription'], + description: 'Client subscriptions to Rocket.Chat', + unit: 'millisecond', + quantiles: true, + }); + metrics.register({ name: 'users_connected', type: 'gauge', @@ -86,6 +95,8 @@ export class DDPStreamer extends ServiceClass { description: 'Users logged by streamer', }); + server.setMetrics(metrics); + server.on(DDP_EVENTS.CONNECTED, () => { metrics.increment('users_connected', { nodeID }, 1); }); diff --git a/ee/apps/ddp-streamer/src/Server.ts b/ee/apps/ddp-streamer/src/Server.ts index 01c7c63511ff..af083621230d 100644 --- a/ee/apps/ddp-streamer/src/Server.ts +++ b/ee/apps/ddp-streamer/src/Server.ts @@ -1,5 +1,6 @@ import { EventEmitter } from 'events'; +import type { IServiceMetrics } from '@rocket.chat/core-services'; import { MeteorService, isMeteorError, MeteorError } from '@rocket.chat/core-services'; import { Logger } from '@rocket.chat/logger'; import ejson from 'ejson'; @@ -38,6 +39,8 @@ export class Server extends EventEmitter { private _methods = new Map(); + private metrics?: IServiceMetrics; + public readonly id = uuidv1(); serialize = ejson.stringify; @@ -52,6 +55,10 @@ export class Server extends EventEmitter { return ejson.parse(payload); }; + setMetrics(metrics: IServiceMetrics): void { + this.metrics = metrics; + } + async call(client: Client, packet: IPacket): Promise { // if client is not connected we don't need to do anything if (client.ws.readyState !== WebSocket.OPEN) { @@ -103,9 +110,13 @@ export class Server extends EventEmitter { throw new MeteorError(404, `Subscription '${packet.name}' not found`); } + const end = this.metrics?.timer('rocketchat_subscription', { subscription: packet.name }); + const publication = new Publication(client, packet, this); const [eventName, options] = packet.params; await fn.call(publication, eventName, options); + + end?.(); } catch (err: unknown) { return this.nosub(client, packet, handleInternalException(err, 'Subscription error')); } diff --git a/ee/apps/omnichannel-transcript/CHANGELOG.md b/ee/apps/omnichannel-transcript/CHANGELOG.md index f7de2c547ab0..78829550a937 100644 --- a/ee/apps/omnichannel-transcript/CHANGELOG.md +++ b/ee/apps/omnichannel-transcript/CHANGELOG.md @@ -1,5 +1,17 @@ # @rocket.chat/omnichannel-transcript +## 0.2.4 + +### Patch Changes + +- Updated dependencies [8a7d5d3898] + - @rocket.chat/model-typings@0.0.10 + - @rocket.chat/omnichannel-services@0.0.10 + - @rocket.chat/models@0.0.10 + - @rocket.chat/core-services@0.1.4 + - @rocket.chat/core-typings@6.3.4 + - @rocket.chat/pdf-worker@0.0.10 + ## 0.2.3 ### Patch Changes diff --git a/ee/apps/omnichannel-transcript/package.json b/ee/apps/omnichannel-transcript/package.json index 4ba11422a2bd..94faf82d74e7 100644 --- a/ee/apps/omnichannel-transcript/package.json +++ b/ee/apps/omnichannel-transcript/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/omnichannel-transcript", "private": true, - "version": "0.2.3", + "version": "0.2.4", "description": "Rocket.Chat service", "scripts": { "build": "tsc -p tsconfig.json", diff --git a/ee/apps/presence-service/CHANGELOG.md b/ee/apps/presence-service/CHANGELOG.md index 5bb3ef39b101..08a27bb77ced 100644 --- a/ee/apps/presence-service/CHANGELOG.md +++ b/ee/apps/presence-service/CHANGELOG.md @@ -1,5 +1,16 @@ # @rocket.chat/presence-service +## 0.2.4 + +### Patch Changes + +- Updated dependencies [8a7d5d3898] + - @rocket.chat/model-typings@0.0.10 + - @rocket.chat/models@0.0.10 + - @rocket.chat/presence@0.0.10 + - @rocket.chat/core-services@0.1.4 + - @rocket.chat/core-typings@6.3.4 + ## 0.2.3 ### Patch Changes diff --git a/ee/apps/presence-service/package.json b/ee/apps/presence-service/package.json index dfd9a8902aac..a79ea33b6406 100644 --- a/ee/apps/presence-service/package.json +++ b/ee/apps/presence-service/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/presence-service", "private": true, - "version": "0.2.3", + "version": "0.2.4", "description": "Rocket.Chat Presence service", "scripts": { "build": "tsc -p tsconfig.json", diff --git a/ee/apps/queue-worker/CHANGELOG.md b/ee/apps/queue-worker/CHANGELOG.md index 1490afbb40ab..2bd7874719e4 100644 --- a/ee/apps/queue-worker/CHANGELOG.md +++ b/ee/apps/queue-worker/CHANGELOG.md @@ -1,5 +1,16 @@ # @rocket.chat/queue-worker +## 0.2.4 + +### Patch Changes + +- Updated dependencies [8a7d5d3898] + - @rocket.chat/model-typings@0.0.10 + - @rocket.chat/omnichannel-services@0.0.10 + - @rocket.chat/models@0.0.10 + - @rocket.chat/core-services@0.1.4 + - @rocket.chat/core-typings@6.3.4 + ## 0.2.3 ### Patch Changes diff --git a/ee/apps/queue-worker/package.json b/ee/apps/queue-worker/package.json index 6907bfa478a8..8eb8b299105b 100644 --- a/ee/apps/queue-worker/package.json +++ b/ee/apps/queue-worker/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/queue-worker", "private": true, - "version": "0.2.3", + "version": "0.2.4", "description": "Rocket.Chat service", "scripts": { "build": "tsc -p tsconfig.json", diff --git a/ee/apps/stream-hub-service/CHANGELOG.md b/ee/apps/stream-hub-service/CHANGELOG.md index 6af9dfb7b0e0..9f5da36d15f5 100644 --- a/ee/apps/stream-hub-service/CHANGELOG.md +++ b/ee/apps/stream-hub-service/CHANGELOG.md @@ -1,5 +1,15 @@ # @rocket.chat/stream-hub-service +## 0.2.4 + +### Patch Changes + +- Updated dependencies [8a7d5d3898] + - @rocket.chat/model-typings@0.0.10 + - @rocket.chat/models@0.0.10 + - @rocket.chat/core-services@0.1.4 + - @rocket.chat/core-typings@6.3.4 + ## 0.2.3 ### Patch Changes diff --git a/ee/apps/stream-hub-service/package.json b/ee/apps/stream-hub-service/package.json index 325a8a75f336..469b6bc8feda 100644 --- a/ee/apps/stream-hub-service/package.json +++ b/ee/apps/stream-hub-service/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/stream-hub-service", "private": true, - "version": "0.2.3", + "version": "0.2.4", "description": "Rocket.Chat Stream Hub service", "scripts": { "build": "tsc -p tsconfig.json", diff --git a/ee/packages/api-client/CHANGELOG.md b/ee/packages/api-client/CHANGELOG.md index 030d8cae3b99..5ecdabd24205 100644 --- a/ee/packages/api-client/CHANGELOG.md +++ b/ee/packages/api-client/CHANGELOG.md @@ -1,5 +1,12 @@ # @rocket.chat/api-client +## 0.1.4 + +### Patch Changes + +- @rocket.chat/core-typings@6.3.4 +- @rocket.chat/rest-typings@6.3.4 + ## 0.1.3 ### Patch Changes diff --git a/ee/packages/api-client/package.json b/ee/packages/api-client/package.json index 488811d73155..987659f45ddf 100644 --- a/ee/packages/api-client/package.json +++ b/ee/packages/api-client/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/api-client", - "version": "0.1.3", + "version": "0.1.4", "devDependencies": { "@swc/core": "^1.3.66", "@swc/jest": "^0.2.26", diff --git a/ee/packages/ddp-client/CHANGELOG.md b/ee/packages/ddp-client/CHANGELOG.md index 6a656d38a488..a4af639fe331 100644 --- a/ee/packages/ddp-client/CHANGELOG.md +++ b/ee/packages/ddp-client/CHANGELOG.md @@ -1,5 +1,12 @@ # @rocket.chat/ddp-client +## 0.1.4 + +### Patch Changes + +- @rocket.chat/rest-typings@6.3.4 +- @rocket.chat/api-client@0.1.4 + ## 0.1.3 ### Patch Changes diff --git a/ee/packages/ddp-client/package.json b/ee/packages/ddp-client/package.json index 9aeb554005d9..350937874a54 100644 --- a/ee/packages/ddp-client/package.json +++ b/ee/packages/ddp-client/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/ddp-client", - "version": "0.1.3", + "version": "0.1.4", "devDependencies": { "@swc/core": "^1.3.66", "@swc/jest": "^0.2.26", diff --git a/ee/packages/omnichannel-services/CHANGELOG.md b/ee/packages/omnichannel-services/CHANGELOG.md index c141b4205af5..1d7bc3c6daaa 100644 --- a/ee/packages/omnichannel-services/CHANGELOG.md +++ b/ee/packages/omnichannel-services/CHANGELOG.md @@ -1,5 +1,17 @@ # @rocket.chat/omnichannel-services +## 0.0.10 + +### Patch Changes + +- Updated dependencies [8a7d5d3898] + - @rocket.chat/model-typings@0.0.10 + - @rocket.chat/models@0.0.10 + - @rocket.chat/core-services@0.1.4 + - @rocket.chat/core-typings@6.3.4 + - @rocket.chat/rest-typings@6.3.4 + - @rocket.chat/pdf-worker@0.0.10 + ## 0.0.9 ### Patch Changes diff --git a/ee/packages/omnichannel-services/package.json b/ee/packages/omnichannel-services/package.json index 27c04e99e7ea..3b85bb71988b 100644 --- a/ee/packages/omnichannel-services/package.json +++ b/ee/packages/omnichannel-services/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/omnichannel-services", - "version": "0.0.9", + "version": "0.0.10", "private": true, "devDependencies": { "@rocket.chat/eslint-config": "workspace:^", diff --git a/ee/packages/pdf-worker/CHANGELOG.md b/ee/packages/pdf-worker/CHANGELOG.md index bf50d15ae71b..7dee5be2c4df 100644 --- a/ee/packages/pdf-worker/CHANGELOG.md +++ b/ee/packages/pdf-worker/CHANGELOG.md @@ -1,5 +1,11 @@ # @rocket.chat/pdf-worker +## 0.0.10 + +### Patch Changes + +- @rocket.chat/core-typings@6.3.4 + ## 0.0.9 ### Patch Changes diff --git a/ee/packages/pdf-worker/package.json b/ee/packages/pdf-worker/package.json index 5e8324613ad1..a089448057a6 100644 --- a/ee/packages/pdf-worker/package.json +++ b/ee/packages/pdf-worker/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/pdf-worker", - "version": "0.0.9", + "version": "0.0.10", "private": true, "devDependencies": { "@storybook/addon-essentials": "~6.5.16", diff --git a/ee/packages/presence/CHANGELOG.md b/ee/packages/presence/CHANGELOG.md index 1ed57117b68e..c5c3dbaf9adc 100644 --- a/ee/packages/presence/CHANGELOG.md +++ b/ee/packages/presence/CHANGELOG.md @@ -1,5 +1,13 @@ # @rocket.chat/presence +## 0.0.10 + +### Patch Changes + +- @rocket.chat/models@0.0.10 +- @rocket.chat/core-services@0.1.4 +- @rocket.chat/core-typings@6.3.4 + ## 0.0.9 ### Patch Changes diff --git a/ee/packages/presence/package.json b/ee/packages/presence/package.json index 5f96bf0fde98..18bc8371997b 100644 --- a/ee/packages/presence/package.json +++ b/ee/packages/presence/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/presence", - "version": "0.0.9", + "version": "0.0.10", "private": true, "devDependencies": { "@babel/core": "~7.22.9", diff --git a/ee/packages/ui-theming/src/paletteDark.ts b/ee/packages/ui-theming/src/paletteDark.ts index 545fc9f098b8..9898c7fe95f9 100644 --- a/ee/packages/ui-theming/src/paletteDark.ts +++ b/ee/packages/ui-theming/src/paletteDark.ts @@ -120,12 +120,12 @@ export const palette = [ category: 'Button', description: 'Primary Background', list: [ - { name: 'button-background-primary-default', token: '', color: '#3976D1' }, - { name: 'button-background-primary-hover', token: '', color: '#295EAE' }, - { name: 'button-background-primary-press', token: '', color: '#245399' }, - { name: 'button-background-primary-focus', token: '', color: '#3976D1' }, - { name: 'button-background-primary-keyfocus', token: '', color: '#3976D1' }, - { name: 'button-background-primary-disabled', token: '', color: '#1D3963' }, + { name: 'button-background-primary-default', token: '', color: '#095AD2' }, + { name: 'button-background-primary-hover', token: '', color: '#10529E' }, + { name: 'button-background-primary-press', token: '', color: '#01336B' }, + { name: 'button-background-primary-focus', token: '', color: '#095AD2' }, + { name: 'button-background-primary-keyfocus', token: '', color: '#095AD2' }, + { name: 'button-background-primary-disabled', token: '', color: '#012247' }, ], }, { @@ -177,7 +177,7 @@ export const palette = [ list: [ { name: 'button-font-on-primary', token: 'white', color: '#FFFFFF' }, { name: 'button-font-on-secondary', token: 'N400', color: '#E4E7EA' }, - { name: 'button-font-on-secondary-danger', token: '', color: '#C14454' }, + { name: 'button-font-on-secondary-danger', token: '', color: '#FFC1C9' }, { name: 'button-font-on-danger', token: 'white', color: '#FFFFFF' }, { name: 'button-font-on-success', token: 'white', color: '#FFFFFF' }, { name: 'button-font-on-primary-disabled', token: 'N700', color: '#6C727A' }, @@ -185,7 +185,7 @@ export const palette = [ { name: 'button-font-on-secondary-danger-disabled', token: '', - color: '#613339', + color: '#6B0513', }, { name: 'button-font-on-danger-disabled', token: '', color: '#757575' }, { name: 'button-font-on-success-disabled', token: '', color: '#757575' }, diff --git a/packages/core-services/CHANGELOG.md b/packages/core-services/CHANGELOG.md index 07a137608d92..f432cc857e7f 100644 --- a/packages/core-services/CHANGELOG.md +++ b/packages/core-services/CHANGELOG.md @@ -1,5 +1,13 @@ # @rocket.chat/core-services +## 0.1.4 + +### Patch Changes + +- @rocket.chat/models@0.0.10 +- @rocket.chat/core-typings@6.3.4 +- @rocket.chat/rest-typings@6.3.4 + ## 0.1.3 ### Patch Changes diff --git a/packages/core-services/package.json b/packages/core-services/package.json index d63c525a2782..f9468b62edb3 100644 --- a/packages/core-services/package.json +++ b/packages/core-services/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/core-services", - "version": "0.1.3", + "version": "0.1.4", "private": true, "devDependencies": { "@babel/core": "~7.22.9", diff --git a/packages/core-services/src/types/IBroker.ts b/packages/core-services/src/types/IBroker.ts index 8647d04a56dc..4bd48afef0ff 100644 --- a/packages/core-services/src/types/IBroker.ts +++ b/packages/core-services/src/types/IBroker.ts @@ -27,6 +27,7 @@ export type BaseMetricOptions = { labelNames?: Array; unit?: string; aggregator?: string; + [key: string]: unknown; }; export interface IServiceMetrics { diff --git a/packages/core-typings/CHANGELOG.md b/packages/core-typings/CHANGELOG.md index 5e6880b649a4..832aceb8ac06 100644 --- a/packages/core-typings/CHANGELOG.md +++ b/packages/core-typings/CHANGELOG.md @@ -1,5 +1,7 @@ # @rocket.chat/core-typings +## 6.3.4 + ## 6.3.3 ## 6.3.2 diff --git a/packages/core-typings/package.json b/packages/core-typings/package.json index 33c93c28d8a8..b727aa01dcdc 100644 --- a/packages/core-typings/package.json +++ b/packages/core-typings/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/core-typings", - "version": "6.3.3", + "version": "6.3.4", "devDependencies": { "@rocket.chat/eslint-config": "workspace:^", "eslint": "~8.45.0", diff --git a/packages/cron/CHANGELOG.md b/packages/cron/CHANGELOG.md index 44b205dda1e7..9699a8f01631 100644 --- a/packages/cron/CHANGELOG.md +++ b/packages/cron/CHANGELOG.md @@ -1,5 +1,12 @@ # @rocket.chat/cron +## 0.0.6 + +### Patch Changes + +- @rocket.chat/models@0.0.10 +- @rocket.chat/core-typings@6.3.4 + ## 0.0.5 ### Patch Changes diff --git a/packages/cron/package.json b/packages/cron/package.json index 396047b3147c..2ef55db12b8a 100644 --- a/packages/cron/package.json +++ b/packages/cron/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/cron", - "version": "0.0.5", + "version": "0.0.6", "private": true, "devDependencies": { "@types/jest": "~29.5.3", diff --git a/packages/fuselage-ui-kit/CHANGELOG.md b/packages/fuselage-ui-kit/CHANGELOG.md index 6d52e9064b16..0ec8fb25ca1f 100644 --- a/packages/fuselage-ui-kit/CHANGELOG.md +++ b/packages/fuselage-ui-kit/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log +## 1.0.4 + +### Patch Changes + +- @rocket.chat/gazzodown@1.0.4 +- @rocket.chat/ui-contexts@1.0.4 +- @rocket.chat/ui-video-conf@1.0.4 + ## 1.0.3 ### Patch Changes diff --git a/packages/fuselage-ui-kit/package.json b/packages/fuselage-ui-kit/package.json index 0cc50b819e9e..70e9f21727d9 100644 --- a/packages/fuselage-ui-kit/package.json +++ b/packages/fuselage-ui-kit/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/fuselage-ui-kit", "private": true, - "version": "1.0.3", + "version": "1.0.4", "description": "UiKit elements for Rocket.Chat Apps built under Fuselage design system", "homepage": "https://rocketchat.github.io/Rocket.Chat.Fuselage/", "author": { @@ -46,9 +46,9 @@ "@rocket.chat/icons": "*", "@rocket.chat/prettier-config": "*", "@rocket.chat/styled": "*", - "@rocket.chat/ui-contexts": "1.0.3", + "@rocket.chat/ui-contexts": "1.0.4", "@rocket.chat/ui-kit": "*", - "@rocket.chat/ui-video-conf": "1.0.3", + "@rocket.chat/ui-video-conf": "1.0.4", "@tanstack/react-query": "*", "react": "*", "react-dom": "*" diff --git a/packages/gazzodown/CHANGELOG.md b/packages/gazzodown/CHANGELOG.md index 88c0f11d0cdb..3e208b86242d 100644 --- a/packages/gazzodown/CHANGELOG.md +++ b/packages/gazzodown/CHANGELOG.md @@ -1,5 +1,13 @@ # @rocket.chat/gazzodown +## 1.0.4 + +### Patch Changes + +- @rocket.chat/core-typings@6.3.4 +- @rocket.chat/ui-contexts@1.0.4 +- @rocket.chat/ui-client@1.0.4 + ## 1.0.3 ### Patch Changes diff --git a/packages/gazzodown/package.json b/packages/gazzodown/package.json index f71325b37a1c..3e8988200c13 100644 --- a/packages/gazzodown/package.json +++ b/packages/gazzodown/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/gazzodown", - "version": "1.0.3", + "version": "1.0.4", "private": true, "devDependencies": { "@babel/core": "~7.22.9", @@ -65,7 +65,7 @@ "/dist" ], "peerDependencies": { - "@rocket.chat/core-typings": "6.3.3", + "@rocket.chat/core-typings": "6.3.4", "@rocket.chat/css-in-js": "*", "@rocket.chat/fuselage": "*", "@rocket.chat/fuselage-tokens": "*", diff --git a/packages/instance-status/CHANGELOG.md b/packages/instance-status/CHANGELOG.md index 1b5dfc94f624..20b229fbc24c 100644 --- a/packages/instance-status/CHANGELOG.md +++ b/packages/instance-status/CHANGELOG.md @@ -1,5 +1,11 @@ # @rocket.chat/instance-status +## 0.0.10 + +### Patch Changes + +- @rocket.chat/models@0.0.10 + ## 0.0.9 ### Patch Changes diff --git a/packages/instance-status/package.json b/packages/instance-status/package.json index c36f4e9834e3..d7d8bbb50d46 100644 --- a/packages/instance-status/package.json +++ b/packages/instance-status/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/instance-status", - "version": "0.0.9", + "version": "0.0.10", "private": true, "devDependencies": { "@rocket.chat/eslint-config": "workspace:^", diff --git a/packages/model-typings/CHANGELOG.md b/packages/model-typings/CHANGELOG.md index 1d89b9145bc4..836eb3a1dcc8 100644 --- a/packages/model-typings/CHANGELOG.md +++ b/packages/model-typings/CHANGELOG.md @@ -1,5 +1,12 @@ # @rocket.chat/model-typings +## 0.0.10 + +### Patch Changes + +- 8a7d5d3898: fix: agent role being removed upon user deactivation + - @rocket.chat/core-typings@6.3.4 + ## 0.0.9 ### Patch Changes diff --git a/packages/model-typings/package.json b/packages/model-typings/package.json index 89ee934005cd..bacc3023e5f4 100644 --- a/packages/model-typings/package.json +++ b/packages/model-typings/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/model-typings", - "version": "0.0.9", + "version": "0.0.10", "private": true, "devDependencies": { "@types/jest": "~29.5.3", diff --git a/packages/model-typings/src/models/ISessionsModel.ts b/packages/model-typings/src/models/ISessionsModel.ts index cebe0c861d3f..1e6a36fd6f78 100644 --- a/packages/model-typings/src/models/ISessionsModel.ts +++ b/packages/model-typings/src/models/ISessionsModel.ts @@ -145,4 +145,8 @@ export interface ISessionsModel extends IBaseModel { }): Promise; createBatch(sessions: OptionalId[]): Promise; + + updateDailySessionById(_id: ISession['_id'], record: Partial): Promise; + + updateAllSessionsByDateToComputed({ start, end }: DestructuredRange): Promise; } diff --git a/packages/models/CHANGELOG.md b/packages/models/CHANGELOG.md index c8249a587d15..230aa3158fbd 100644 --- a/packages/models/CHANGELOG.md +++ b/packages/models/CHANGELOG.md @@ -1,5 +1,12 @@ # @rocket.chat/models +## 0.0.10 + +### Patch Changes + +- Updated dependencies [8a7d5d3898] + - @rocket.chat/model-typings@0.0.10 + ## 0.0.9 ### Patch Changes diff --git a/packages/models/package.json b/packages/models/package.json index 0a80a2975ffc..c075c8132582 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/models", - "version": "0.0.9", + "version": "0.0.10", "private": true, "devDependencies": { "@types/jest": "~29.5.3", diff --git a/packages/password-policies/.eslintrc.json b/packages/password-policies/.eslintrc.json new file mode 100644 index 000000000000..15f2cd4817e1 --- /dev/null +++ b/packages/password-policies/.eslintrc.json @@ -0,0 +1,8 @@ +{ + "extends": ["@rocket.chat/eslint-config"], + "plugins": ["jest"], + "env": { + "jest/globals": true + }, + "ignorePatterns": ["**/dist"] +} diff --git a/packages/password-policies/jest.config.ts b/packages/password-policies/jest.config.ts new file mode 100644 index 000000000000..959a31a7c6bf --- /dev/null +++ b/packages/password-policies/jest.config.ts @@ -0,0 +1,3 @@ +export default { + preset: 'ts-jest', +}; diff --git a/packages/password-policies/package.json b/packages/password-policies/package.json new file mode 100644 index 000000000000..52fa766671db --- /dev/null +++ b/packages/password-policies/package.json @@ -0,0 +1,26 @@ +{ + "name": "@rocket.chat/password-policies", + "version": "0.0.1", + "private": true, + "devDependencies": { + "@types/chai": "^4.3.5", + "@types/jest": "~29.5.3", + "chai": "^4.3.7", + "eslint": "~8.45.0", + "jest": "~29.6.1", + "ts-jest": "~29.0.5", + "typescript": "~5.2.2" + }, + "scripts": { + "lint": "eslint --ext .js,.jsx,.ts,.tsx .", + "lint:fix": "eslint --ext .js,.jsx,.ts,.tsx . --fix", + "testunit": "jest", + "build": "rm -rf dist && tsc -p tsconfig.json", + "dev": "tsc -p tsconfig.json --watch --preserveWatchOutput" + }, + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", + "files": [ + "/dist" + ] +} diff --git a/apps/meteor/app/lib/server/lib/PasswordPolicyClass.js b/packages/password-policies/src/PasswordPolicyClass.ts similarity index 61% rename from apps/meteor/app/lib/server/lib/PasswordPolicyClass.js rename to packages/password-policies/src/PasswordPolicyClass.ts index 99dc0d6fabf5..8212df18002c 100644 --- a/apps/meteor/app/lib/server/lib/PasswordPolicyClass.js +++ b/packages/password-policies/src/PasswordPolicyClass.ts @@ -1,8 +1,45 @@ -import { Random } from '@rocket.chat/random'; -import generator from 'generate-password'; -import { Meteor } from 'meteor/meteor'; +import { PasswordPolicyError } from './PasswordPolicyError'; + +type PasswordPolicyType = { + enabled: boolean; + policy: [name: string, options?: Record][]; +}; + +type ValidationMessageType = { + name: string; + isValid: boolean; + limit?: number; +}; + +export class PasswordPolicy { + private regex: { + forbiddingRepeatingCharacters: RegExp; + mustContainAtLeastOneLowercase: RegExp; + mustContainAtLeastOneUppercase: RegExp; + mustContainAtLeastOneNumber: RegExp; + mustContainAtLeastOneSpecialCharacter: RegExp; + }; + + private enabled: boolean; + + private minLength: number; + + private maxLength: number; + + private forbidRepeatingCharacters: boolean; + + private mustContainAtLeastOneLowercase: boolean; + + private mustContainAtLeastOneUppercase: boolean; + + private mustContainAtLeastOneNumber: boolean; + + private mustContainAtLeastOneSpecialCharacter: boolean; + + private throwError: boolean; + + private forbidRepeatingCharactersCount: number; -class PasswordPolicy { constructor({ enabled = false, minLength = -1, @@ -14,14 +51,7 @@ class PasswordPolicy { mustContainAtLeastOneNumber = false, mustContainAtLeastOneSpecialCharacter = false, throwError = true, - } = {}) { - this.regex = { - mustContainAtLeastOneLowercase: new RegExp('[a-z]'), - mustContainAtLeastOneUppercase: new RegExp('[A-Z]'), - mustContainAtLeastOneNumber: new RegExp('[0-9]'), - mustContainAtLeastOneSpecialCharacter: new RegExp('[^A-Za-z0-9 ]'), - }; - + }) { this.enabled = enabled; this.minLength = minLength; this.maxLength = maxLength; @@ -32,27 +62,103 @@ class PasswordPolicy { this.mustContainAtLeastOneNumber = mustContainAtLeastOneNumber; this.mustContainAtLeastOneSpecialCharacter = mustContainAtLeastOneSpecialCharacter; this.throwError = throwError; - } - set forbidRepeatingCharactersCount(value) { - this._forbidRepeatingCharactersCount = value; - this.regex.forbiddingRepeatingCharacters = new RegExp(`(.)\\1{${this.forbidRepeatingCharactersCount},}`); - } - - get forbidRepeatingCharactersCount() { - return this._forbidRepeatingCharactersCount; + this.regex = { + forbiddingRepeatingCharacters: new RegExp(`(.)\\1{${forbidRepeatingCharactersCount},}`), + mustContainAtLeastOneLowercase: new RegExp('[a-z]'), + mustContainAtLeastOneUppercase: new RegExp('[A-Z]'), + mustContainAtLeastOneNumber: new RegExp('[0-9]'), + mustContainAtLeastOneSpecialCharacter: new RegExp('[^A-Za-z0-9 ]'), + }; } - error(error, message, reasons) { + error( + error: string, + message: string, + reasons?: { + error: string; + message: string; + }[], + ) { if (this.throwError) { - throw new Meteor.Error(error, message, reasons); + throw new PasswordPolicyError(message, error, reasons); } return false; } - validate(password) { - const reasons = []; + sendValidationMessage(password: string): { + name: string; + isValid: boolean; + limit?: number; + }[] { + const validationReturn: ValidationMessageType[] = []; + + if (!this.enabled) { + return []; + } + + if (this.minLength >= 1) { + validationReturn.push({ + name: 'get-password-policy-minLength', + isValid: !(password.length < this.minLength), + limit: this.minLength, + }); + } + + if (this.maxLength >= 1) { + validationReturn.push({ + name: 'get-password-policy-maxLength', + isValid: !(password.length > this.maxLength), + limit: this.maxLength, + }); + } + + if (this.forbidRepeatingCharacters) { + validationReturn.push({ + name: 'get-password-policy-forbidRepeatingCharactersCount', + isValid: !this.regex.forbiddingRepeatingCharacters.test(password), + limit: this.forbidRepeatingCharactersCount, + }); + } + + if (this.mustContainAtLeastOneLowercase) { + validationReturn.push({ + name: 'get-password-policy-mustContainAtLeastOneLowercase', + isValid: this.regex.mustContainAtLeastOneLowercase.test(password), + }); + } + + if (this.mustContainAtLeastOneUppercase) { + validationReturn.push({ + name: 'get-password-policy-mustContainAtLeastOneUppercase', + isValid: this.regex.mustContainAtLeastOneUppercase.test(password), + }); + } + + if (this.mustContainAtLeastOneNumber) { + validationReturn.push({ + name: 'get-password-policy-mustContainAtLeastOneNumber', + isValid: this.regex.mustContainAtLeastOneNumber.test(password), + }); + } + + if (this.mustContainAtLeastOneSpecialCharacter) { + validationReturn.push({ + name: 'get-password-policy-mustContainAtLeastOneSpecialCharacter', + isValid: this.regex.mustContainAtLeastOneSpecialCharacter.test(password), + }); + } + + return validationReturn; + } + + validate(password: string) { + const reasons: { + error: string; + message: string; + }[] = []; + if (typeof password !== 'string' || !password.trim().length) { return this.error('error-password-policy-not-met', "The password provided does not meet the server's password policy."); } @@ -117,11 +223,12 @@ class PasswordPolicy { return true; } - getPasswordPolicy() { - const data = { + getPasswordPolicy(): PasswordPolicyType { + const data: PasswordPolicyType = { enabled: false, policy: [], }; + if (this.enabled) { data.enabled = true; if (this.minLength >= 1) { @@ -154,31 +261,4 @@ class PasswordPolicy { } return data; } - - generatePassword() { - if (this.enabled) { - for (let i = 0; i < 10; i++) { - const password = this._generatePassword(); - if (this.validate(password)) { - return password; - } - } - } - - return Random.id(); - } - - _generatePassword() { - const length = Math.min(Math.max(this.minLength, 12), this.maxLength > 0 ? this.maxLength : Number.MAX_SAFE_INTEGER); - return generator.generate({ - length, - ...(this.mustContainAtLeastOneNumber && { numbers: true }), - ...(this.mustContainAtLeastOneSpecialCharacter && { symbols: true }), - ...(this.mustContainAtLeastOneLowercase && { lowercase: true }), - ...(this.mustContainAtLeastOneUppercase && { uppercase: true }), - strict: true, - }); - } } - -export default PasswordPolicy; diff --git a/packages/password-policies/src/PasswordPolicyError.ts b/packages/password-policies/src/PasswordPolicyError.ts new file mode 100644 index 000000000000..ea58d0e83b07 --- /dev/null +++ b/packages/password-policies/src/PasswordPolicyError.ts @@ -0,0 +1,11 @@ +export class PasswordPolicyError extends Error { + public error: string; + + public details?: { error: string; message: string }[] | undefined; + + constructor(message: string, error: string, details?: { error: string; message: string }[]) { + super(message); + this.error = error; + this.details = details; + } +} diff --git a/packages/password-policies/src/index.ts b/packages/password-policies/src/index.ts new file mode 100644 index 000000000000..ce94042e029a --- /dev/null +++ b/packages/password-policies/src/index.ts @@ -0,0 +1 @@ +export { PasswordPolicy } from './PasswordPolicyClass'; diff --git a/packages/password-policies/tests/passwordPolicyClass.test.ts b/packages/password-policies/tests/passwordPolicyClass.test.ts new file mode 100644 index 000000000000..4cda16e3dd27 --- /dev/null +++ b/packages/password-policies/tests/passwordPolicyClass.test.ts @@ -0,0 +1,223 @@ +import { expect } from 'chai'; + +import { PasswordPolicy } from '../src/PasswordPolicyClass'; + +describe('PasswordPolicy', () => { + describe('Password tests with default options', () => { + it('should allow all passwords', () => { + const passwordPolicy = new PasswordPolicy({ throwError: false }); + expect(passwordPolicy.validate(null as any)).to.be.equal(false); + expect(passwordPolicy.validate(undefined as any)).to.be.equal(false); + expect(passwordPolicy.validate('')).to.be.equal(false); + expect(passwordPolicy.validate(' ')).to.be.equal(false); + expect(passwordPolicy.validate('a')).to.be.equal(true); + expect(passwordPolicy.validate('aaaaaaaaa')).to.be.equal(true); + }); + }); + + describe('Password tests with options', () => { + it('should not allow non string or empty', () => { + const passwordPolicy = new PasswordPolicy({ + enabled: true, + throwError: false, + }); + expect(passwordPolicy.validate(null as any)).to.be.equal(false); + expect(passwordPolicy.validate(undefined as any)).to.be.false; + expect(passwordPolicy.validate(1 as any)).to.be.false; + expect(passwordPolicy.validate(true as any)).to.be.false; + expect(passwordPolicy.validate(new Date() as any)).to.be.false; + expect(passwordPolicy.validate(new Function() as any)).to.be.false; + expect(passwordPolicy.validate('')).to.be.false; + }); + + it('should restrict by minLength', () => { + const passwordPolicy = new PasswordPolicy({ + enabled: true, + minLength: 5, + throwError: false, + }); + + expect(passwordPolicy.validate('1')).to.be.false; + expect(passwordPolicy.validate('1234')).to.be.false; + expect(passwordPolicy.validate('12345')).to.be.true; + expect(passwordPolicy.validate(' ')).to.be.false; + }); + + it('should restrict by maxLength', () => { + const passwordPolicy = new PasswordPolicy({ + enabled: true, + maxLength: 5, + throwError: false, + }); + + expect(passwordPolicy.validate('1')).to.be.true; + expect(passwordPolicy.validate('12345')).to.be.true; + expect(passwordPolicy.validate('123456')).to.be.false; + expect(passwordPolicy.validate(' ')).to.be.false; + }); + + it('should allow repeated characters', () => { + const passwordPolicy = new PasswordPolicy({ + enabled: true, + forbidRepeatingCharacters: false, + throwError: false, + }); + + expect(passwordPolicy.validate('1')).to.be.true; + expect(passwordPolicy.validate('12345')).to.be.true; + expect(passwordPolicy.validate('123456')).to.be.true; + expect(passwordPolicy.validate(' ')).to.be.false; + expect(passwordPolicy.validate('11111111111111')).to.be.true; + }); + + it('should restrict repeated characters', () => { + const passwordPolicy = new PasswordPolicy({ + enabled: true, + forbidRepeatingCharacters: true, + forbidRepeatingCharactersCount: 3, + throwError: false, + }); + + expect(passwordPolicy.validate('1')).to.be.true; + expect(passwordPolicy.validate('11')).to.be.true; + expect(passwordPolicy.validate('111')).to.be.true; + expect(passwordPolicy.validate('1111')).to.be.false; + expect(passwordPolicy.validate(' ')).to.be.false; + expect(passwordPolicy.validate('123456')).to.be.true; + }); + + it('should restrict repeated characters customized', () => { + const passwordPolicy = new PasswordPolicy({ + enabled: true, + forbidRepeatingCharacters: true, + forbidRepeatingCharactersCount: 5, + throwError: false, + }); + + expect(passwordPolicy.validate('1')).to.be.true; + expect(passwordPolicy.validate('11')).to.be.true; + expect(passwordPolicy.validate('111')).to.be.true; + expect(passwordPolicy.validate('1111')).to.be.true; + expect(passwordPolicy.validate('11111')).to.be.true; + expect(passwordPolicy.validate('111111')).to.be.false; + expect(passwordPolicy.validate(' ')).to.be.false; + expect(passwordPolicy.validate('123456')).to.be.true; + }); + + it('should contain one lowercase', () => { + const passwordPolicy = new PasswordPolicy({ + enabled: true, + mustContainAtLeastOneLowercase: true, + throwError: false, + }); + + expect(passwordPolicy.validate('a')).to.be.true; + expect(passwordPolicy.validate('aa')).to.be.true; + expect(passwordPolicy.validate('A')).to.be.false; + expect(passwordPolicy.validate(' ')).to.be.false; + expect(passwordPolicy.validate('123456')).to.be.false; + expect(passwordPolicy.validate('AAAAA')).to.be.false; + expect(passwordPolicy.validate('AAAaAAA')).to.be.true; + }); + + it('should contain one uppercase', () => { + const passwordPolicy = new PasswordPolicy({ + enabled: true, + mustContainAtLeastOneUppercase: true, + throwError: false, + }); + + expect(passwordPolicy.validate('a')).to.be.false; + expect(passwordPolicy.validate('aa')).to.be.false; + expect(passwordPolicy.validate('A')).to.be.true; + expect(passwordPolicy.validate(' ')).to.be.false; + expect(passwordPolicy.validate('123456')).to.be.false; + expect(passwordPolicy.validate('AAAAA')).to.be.true; + expect(passwordPolicy.validate('AAAaAAA')).to.be.true; + }); + + it('should contain one number', () => { + const passwordPolicy = new PasswordPolicy({ + enabled: true, + mustContainAtLeastOneNumber: true, + throwError: false, + }); + + expect(passwordPolicy.validate('a')).to.be.false; + expect(passwordPolicy.validate('aa')).to.be.false; + expect(passwordPolicy.validate('A')).to.be.false; + expect(passwordPolicy.validate(' ')).to.be.false; + expect(passwordPolicy.validate('123456')).to.be.true; + expect(passwordPolicy.validate('AAAAA')).to.be.false; + expect(passwordPolicy.validate('AAAaAAA')).to.be.false; + expect(passwordPolicy.validate('AAAa1AAA')).to.be.true; + }); + + it('should contain one special character', () => { + const passwordPolicy = new PasswordPolicy({ + enabled: true, + mustContainAtLeastOneSpecialCharacter: true, + throwError: false, + }); + + expect(passwordPolicy.validate('a')).to.be.false; + expect(passwordPolicy.validate('aa')).to.be.false; + expect(passwordPolicy.validate('A')).to.be.false; + expect(passwordPolicy.validate(' ')).to.be.false; + expect(passwordPolicy.validate('123456')).to.be.false; + expect(passwordPolicy.validate('AAAAA')).to.be.false; + expect(passwordPolicy.validate('AAAaAAA')).to.be.false; + expect(passwordPolicy.validate('AAAa1AAA')).to.be.false; + expect(passwordPolicy.validate('AAAa@AAA')).to.be.true; + }); + }); + + describe('Password Policy', () => { + it('should return a correct password policy', () => { + const passwordPolicy = new PasswordPolicy({ + enabled: true, + throwError: false, + minLength: 10, + maxLength: 20, + forbidRepeatingCharacters: true, + forbidRepeatingCharactersCount: 4, + mustContainAtLeastOneLowercase: true, + mustContainAtLeastOneUppercase: true, + mustContainAtLeastOneNumber: true, + mustContainAtLeastOneSpecialCharacter: true, + }); + + const policy = passwordPolicy.getPasswordPolicy(); + + expect(policy).to.not.be.undefined; + expect(policy.enabled).to.be.true; + expect(policy.policy.length).to.be.equal(8); + expect(policy.policy[0][0]).to.be.equal('get-password-policy-minLength'); + expect(policy.policy[0][1]?.minLength).to.be.equal(10); + }); + + it('should return correct values if policy is disabled', () => { + const passwordPolicy = new PasswordPolicy({ + enabled: false, + }); + + const policy = passwordPolicy.getPasswordPolicy(); + + expect(policy.enabled).to.be.false; + expect(policy.policy.length).to.be.equal(0); + }); + + it('should return correct values if policy is enabled but no specifiers exists', () => { + const passwordPolicy = new PasswordPolicy({ + enabled: true, + }); + + const policy = passwordPolicy.getPasswordPolicy(); + + expect(policy.enabled).to.be.true; + // even when no policy is specified, forbidRepeatingCharactersCount is still configured + // since its default value is 3 + expect(policy.policy.length).to.be.equal(1); + }); + }); +}); diff --git a/packages/password-policies/tsconfig.json b/packages/password-policies/tsconfig.json new file mode 100644 index 000000000000..e2be47cf5499 --- /dev/null +++ b/packages/password-policies/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.client.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["./src/**/*"] +} diff --git a/packages/rest-typings/CHANGELOG.md b/packages/rest-typings/CHANGELOG.md index 9c5aa19f1b07..62f05e378e4e 100644 --- a/packages/rest-typings/CHANGELOG.md +++ b/packages/rest-typings/CHANGELOG.md @@ -1,5 +1,11 @@ # @rocket.chat/rest-typings +## 6.3.4 + +### Patch Changes + +- @rocket.chat/core-typings@6.3.4 + ## 6.3.3 ### Patch Changes diff --git a/packages/rest-typings/package.json b/packages/rest-typings/package.json index acb2cb635414..45bb773b7dee 100644 --- a/packages/rest-typings/package.json +++ b/packages/rest-typings/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/rest-typings", - "version": "6.3.3", + "version": "6.3.4", "devDependencies": { "@rocket.chat/eslint-config": "workspace:^", "@types/jest": "~29.5.3", diff --git a/packages/rest-typings/src/v1/users.ts b/packages/rest-typings/src/v1/users.ts index 947228476bdd..c47f4be6404d 100644 --- a/packages/rest-typings/src/v1/users.ts +++ b/packages/rest-typings/src/v1/users.ts @@ -9,7 +9,6 @@ import type { } from '@rocket.chat/core-typings'; import Ajv from 'ajv'; -import type { UsersSendConfirmationEmailParamsPOST } from '..'; import type { PaginatedRequest } from '../helpers/PaginatedRequest'; import type { PaginatedResult } from '../helpers/PaginatedResult'; import type { UserCreateParamsPOST } from './users/UserCreateParamsPOST'; @@ -20,7 +19,9 @@ import type { UserSetActiveStatusParamsPOST } from './users/UserSetActiveStatusP import type { UsersAutocompleteParamsGET } from './users/UsersAutocompleteParamsGET'; import type { UsersInfoParamsGet } from './users/UsersInfoParamsGet'; import type { UsersListTeamsParamsGET } from './users/UsersListTeamsParamsGET'; +import type { UsersSendConfirmationEmailParamsPOST } from './users/UsersSendConfirmationEmailParamsPOST'; import type { UsersSetPreferencesParamsPOST } from './users/UsersSetPreferenceParamsPOST'; +import type { UsersUpdateOwnBasicInfoParamsPOST } from './users/UsersUpdateOwnBasicInfoParamsPOST'; import type { UsersUpdateParamsPOST } from './users/UsersUpdateParamsPOST'; const ajv = new Ajv({ @@ -358,18 +359,7 @@ export type UsersEndpoints = { }; '/v1/users.updateOwnBasicInfo': { - POST: (params: { - data: { - email?: string; - name?: string; - username?: string; - nickname?: string; - statusText?: string; - newPassword?: string; - currentPassword?: string; - }; - customFields?: Record; - }) => { + POST: (params: UsersUpdateOwnBasicInfoParamsPOST) => { user: IUser; }; }; diff --git a/packages/rest-typings/src/v1/users/UsersUpdateOwnBasicInfoParamsPOST.ts b/packages/rest-typings/src/v1/users/UsersUpdateOwnBasicInfoParamsPOST.ts index cff6fee56bfa..13c3066e4767 100644 --- a/packages/rest-typings/src/v1/users/UsersUpdateOwnBasicInfoParamsPOST.ts +++ b/packages/rest-typings/src/v1/users/UsersUpdateOwnBasicInfoParamsPOST.ts @@ -10,7 +10,9 @@ export type UsersUpdateOwnBasicInfoParamsPOST = { name?: string; username?: string; nickname?: string; + bio?: string; statusText?: string; + statusType?: string; currentPassword?: string; newPassword?: string; }; @@ -39,6 +41,14 @@ const UsersUpdateOwnBasicInfoParamsPostSchema = { type: 'string', nullable: true, }, + bio: { + type: 'string', + nullable: true, + }, + statusType: { + type: 'string', + nullable: true, + }, statusText: { type: 'string', nullable: true, diff --git a/packages/ui-client/CHANGELOG.md b/packages/ui-client/CHANGELOG.md index 497a40137ee5..fa6ebadcedfa 100644 --- a/packages/ui-client/CHANGELOG.md +++ b/packages/ui-client/CHANGELOG.md @@ -1,5 +1,11 @@ # @rocket.chat/ui-client +## 1.0.4 + +### Patch Changes + +- @rocket.chat/ui-contexts@1.0.4 + ## 1.0.3 ### Patch Changes diff --git a/packages/ui-client/package.json b/packages/ui-client/package.json index 4bc9a7d8c681..adf07e87c9a0 100644 --- a/packages/ui-client/package.json +++ b/packages/ui-client/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/ui-client", - "version": "1.0.3", + "version": "1.0.4", "private": true, "devDependencies": { "@babel/core": "~7.22.9", @@ -61,7 +61,7 @@ "@rocket.chat/fuselage": "*", "@rocket.chat/fuselage-hooks": "*", "@rocket.chat/icons": "*", - "@rocket.chat/ui-contexts": "1.0.3", + "@rocket.chat/ui-contexts": "1.0.4", "react": "~17.0.2" }, "volta": { diff --git a/packages/ui-client/src/components/PasswordVerifier/PasswordVerifier.stories.tsx b/packages/ui-client/src/components/PasswordVerifier/PasswordVerifier.stories.tsx index fe3be924ab18..412305be13e9 100644 --- a/packages/ui-client/src/components/PasswordVerifier/PasswordVerifier.stories.tsx +++ b/packages/ui-client/src/components/PasswordVerifier/PasswordVerifier.stories.tsx @@ -3,46 +3,42 @@ import type { ComponentMeta, ComponentStory } from '@storybook/react'; import { PasswordVerifier } from './PasswordVerifier'; -type Response = { - enabled: boolean; - policy: [ - name: string, - value?: - | { - [x: string]: number; - } - | undefined, - ][]; -}; - export default { title: 'Components/PasswordVerifier', component: PasswordVerifier, -} as ComponentMeta; - -const response: Response = { - enabled: true, - policy: [ - ['get-password-policy-minLength', { minLength: 10 }], - ['get-password-policy-forbidRepeatingCharactersCount', { maxRepeatingChars: 3 }], - ['get-password-policy-mustContainAtLeastOneLowercase'], - ['get-password-policy-mustContainAtLeastOneUppercase'], - ['get-password-policy-mustContainAtLeastOneNumber'], - ['get-password-policy-mustContainAtLeastOneSpecialCharacter'], + decorators: [ + mockAppRoot() + .withSetting('Accounts_Password_Policy_Enabled', 'true') + .withSetting('Accounts_Password_Policy_MinLength', '12') + .withSetting('Accounts_Password_Policy_MaxLength', '24') + .withSetting('Accounts_Password_Policy_ForbidRepeatingCharacters', 'true') + .withSetting('Accounts_Password_Policy_ForbidRepeatingCharactersCount', '3') + .withSetting('Accounts_Password_Policy_AtLeastOneLowercase', 'true') + .withSetting('Accounts_Password_Policy_AtLeastOneUppercase', 'true') + .withSetting('Accounts_Password_Policy_AtLeastOneNumber', 'true') + .withSetting('Accounts_Password_Policy_AtLeastOneSpecialCharacter', 'true') + .withSetting('Language', 'en') + .withTranslations('en', 'core', { Password_must_have: 'Password must have:' }) + .withTranslations('en', 'core', { 'get-password-policy-minLength-label': 'At least {{limit}} characters' }) + .withTranslations('en', 'core', { 'get-password-policy-maxLength-label': 'At most {{limit}} characters' }) + .withTranslations('en', 'core', { + 'get-password-policy-forbidRepeatingCharactersCount-label': 'Max. {{limit}} repeating characters', + }) + .withTranslations('en', 'core', { + 'get-password-policy-mustContainAtLeastOneLowercase-label': 'At least one lowercase letter', + }) + .withTranslations('en', 'core', { + 'get-password-policy-mustContainAtLeastOneUppercase-label': 'At least one uppercase letter', + }) + .withTranslations('en', 'core', { 'get-password-policy-mustContainAtLeastOneNumber-label': 'At least one number' }) + .withTranslations('en', 'core', { + 'get-password-policy-mustContainAtLeastOneSpecialCharacter-label': 'At least one symbol', + }) + .buildStoryDecorator(), ], -}; - -const Wrapper = mockAppRoot() - .withEndpoint('GET', '/v1/pw.getPolicy', () => response) - .build(); - -export const Default: ComponentStory = (args) => ( - - - -); + args: { + password: '123', + }, +} as ComponentMeta; -Default.storyName = 'PasswordVerifier'; -Default.args = { - password: 'asd', -}; +export const Default: ComponentStory = (args) => ; diff --git a/packages/ui-client/src/components/PasswordVerifier/PasswordVerifier.tsx b/packages/ui-client/src/components/PasswordVerifier/PasswordVerifier.tsx index 9599f43c6cbb..bf53360a2351 100644 --- a/packages/ui-client/src/components/PasswordVerifier/PasswordVerifier.tsx +++ b/packages/ui-client/src/components/PasswordVerifier/PasswordVerifier.tsx @@ -1,4 +1,4 @@ -import { Box, Skeleton } from '@rocket.chat/fuselage'; +import { Box } from '@rocket.chat/fuselage'; import { useUniqueId } from '@rocket.chat/fuselage-hooks'; import { useVerifyPassword } from '@rocket.chat/ui-contexts'; import { useTranslation } from 'react-i18next'; @@ -10,15 +10,17 @@ type PasswordVerifierProps = { id?: string; }; +type PasswordVerificationProps = { + name: string; + isValid: boolean; + limit?: number; +}[]; + export const PasswordVerifier = ({ password, id }: PasswordVerifierProps) => { const { t } = useTranslation(); const uniqueId = useUniqueId(); - const { data: passwordVerifications, isLoading } = useVerifyPassword(password || ''); - - if (isLoading) { - return ; - } + const passwordVerifications: PasswordVerificationProps = useVerifyPassword(password || ''); if (!passwordVerifications?.length) { return null; diff --git a/packages/ui-client/src/components/PasswordVerifier/PasswordVerifiers.spec.tsx b/packages/ui-client/src/components/PasswordVerifier/PasswordVerifiers.spec.tsx index cd2a5175e59f..cb2efaca19f5 100644 --- a/packages/ui-client/src/components/PasswordVerifier/PasswordVerifiers.spec.tsx +++ b/packages/ui-client/src/components/PasswordVerifier/PasswordVerifiers.spec.tsx @@ -1,51 +1,24 @@ import { mockAppRoot } from '@rocket.chat/mock-providers'; -import { passwordVerificationsTemplate } from '@rocket.chat/ui-contexts/dist/hooks/useVerifyPassword'; import { render, waitFor } from '@testing-library/react'; import { PasswordVerifier } from './PasswordVerifier'; -type Response = { - enabled: boolean; - policy: [ - name: string, - value?: - | { - [x: string]: number; - } - | undefined, - ][]; -}; - afterEach(() => { // restore the spy created with spyOn jest.restoreAllMocks(); }); it('should render no policy if its disabled ', () => { - const response: Response = { - enabled: false, - policy: [], - }; - const { queryByRole } = render(, { - wrapper: mockAppRoot() - .withEndpoint('GET', '/v1/pw.getPolicy', () => response) - .build(), + wrapper: mockAppRoot().withSetting('Accounts_Password_Policy_Enabled', 'true').build(), }); expect(queryByRole('list')).toBeNull(); }); it('should render no policy if its enabled but empty', async () => { - const response: Response = { - enabled: true, - policy: [], - }; - const { queryByRole, queryByTestId } = render(, { - wrapper: mockAppRoot() - .withEndpoint('GET', '/v1/pw.getPolicy', () => response) - .build(), + wrapper: mockAppRoot().build(), }); await waitFor(() => { @@ -55,14 +28,10 @@ it('should render no policy if its enabled but empty', async () => { }); it('should render policy list if its enabled and not empty', async () => { - const response: Response = { - enabled: true, - policy: [['get-password-policy-minLength', { minLength: 10 }]], - }; - const { queryByRole, queryByTestId } = render(, { wrapper: mockAppRoot() - .withEndpoint('GET', '/v1/pw.getPolicy', () => response) + .withSetting('Accounts_Password_Policy_Enabled', 'true') + .withSetting('Accounts_Password_Policy_MinLength', '6') .build(), }); @@ -75,14 +44,17 @@ it('should render policy list if its enabled and not empty', async () => { }); it('should render all the policies when all policies are enabled', async () => { - const response: Response = { - enabled: true, - policy: Object.keys(passwordVerificationsTemplate).map((item) => [item]), - }; - const { queryByTestId, queryAllByRole } = render(, { wrapper: mockAppRoot() - .withEndpoint('GET', '/v1/pw.getPolicy', () => response) + .withSetting('Accounts_Password_Policy_Enabled', 'true') + .withSetting('Accounts_Password_Policy_MinLength', '6') + .withSetting('Accounts_Password_Policy_MaxLength', '24') + .withSetting('Accounts_Password_Policy_ForbidRepeatingCharacters', 'true') + .withSetting('Accounts_Password_Policy_ForbidRepeatingCharactersCount', '3') + .withSetting('Accounts_Password_Policy_AtLeastOneLowercase', 'true') + .withSetting('Accounts_Password_Policy_AtLeastOneUppercase', 'true') + .withSetting('Accounts_Password_Policy_AtLeastOneNumber', 'true') + .withSetting('Accounts_Password_Policy_AtLeastOneSpecialCharacter', 'true') .build(), }); @@ -90,18 +62,14 @@ it('should render all the policies when all policies are enabled', async () => { expect(queryByTestId('password-verifier-skeleton')).toBeNull(); }); - expect(queryAllByRole('listitem').length).toEqual(response.policy.length); + expect(queryAllByRole('listitem').length).toEqual(7); }); it("should render policy as invalid if password doesn't match the requirements", async () => { - const response: Response = { - enabled: true, - policy: [['get-password-policy-minLength', { minLength: 10 }]], - }; - const { queryByTestId, getByRole } = render(, { wrapper: mockAppRoot() - .withEndpoint('GET', '/v1/pw.getPolicy', () => response) + .withSetting('Accounts_Password_Policy_Enabled', 'true') + .withSetting('Accounts_Password_Policy_MinLength', '10') .build(), }); @@ -113,14 +81,10 @@ it("should render policy as invalid if password doesn't match the requirements", }); it('should render policy as valid if password matches the requirements', async () => { - const response: Response = { - enabled: true, - policy: [['get-password-policy-minLength', { minLength: 2 }]], - }; - const { queryByTestId, getByRole } = render(, { wrapper: mockAppRoot() - .withEndpoint('GET', '/v1/pw.getPolicy', () => response) + .withSetting('Accounts_Password_Policy_Enabled', 'true') + .withSetting('Accounts_Password_Policy_MinLength', '2') .build(), }); diff --git a/packages/ui-client/src/hooks/useValidatePassword.spec.ts b/packages/ui-client/src/hooks/useValidatePassword.spec.ts index 5d1c5a635c52..275e4ab8d6f5 100644 --- a/packages/ui-client/src/hooks/useValidatePassword.spec.ts +++ b/packages/ui-client/src/hooks/useValidatePassword.spec.ts @@ -3,64 +3,32 @@ import { renderHook } from '@testing-library/react-hooks'; import { useValidatePassword } from './useValidatePassword'; -type Response = { - enabled: boolean; - policy: [ - name: string, - value?: - | { - [x: string]: number; - } - | undefined, - ][]; -}; +const settingsMockWrapper = mockAppRoot() + .withSetting('Accounts_Password_Policy_Enabled', 'true') + .withSetting('Accounts_Password_Policy_MinLength', '6') + .withSetting('Accounts_Password_Policy_MaxLength', '24') + .withSetting('Accounts_Password_Policy_ForbidRepeatingCharacters', 'true') + .withSetting('Accounts_Password_Policy_ForbidRepeatingCharactersCount', '3') + .withSetting('Accounts_Password_Policy_AtLeastOneLowercase', 'true') + .withSetting('Accounts_Password_Policy_AtLeastOneUppercase', 'true') + .withSetting('Accounts_Password_Policy_AtLeastOneNumber', 'true') + .withSetting('Accounts_Password_Policy_AtLeastOneSpecialCharacter', 'true') + .build(); it("should return `false` if password doesn't match all the requirements", async () => { - const response: Response = { - enabled: true, - policy: [['get-password-policy-minLength', { minLength: 8 }]], - }; - - const { result, waitForValueToChange } = renderHook(async () => useValidatePassword('secret'), { - wrapper: mockAppRoot() - .withEndpoint('GET', '/v1/pw.getPolicy', () => response) - .build(), + const { result } = renderHook(async () => useValidatePassword('secret'), { + wrapper: settingsMockWrapper, }); - await waitForValueToChange(() => result.current); const res = await result.current; expect(res).toBeFalsy(); }); it('should return `true` if password matches all the requirements', async () => { - const response: Response = { - enabled: true, - policy: [['get-password-policy-minLength', { minLength: 8 }]], - }; - - const { result, waitForValueToChange } = renderHook(async () => useValidatePassword('secret-password'), { - wrapper: mockAppRoot() - .withEndpoint('GET', '/v1/pw.getPolicy', () => response) - .build(), + const { result } = renderHook(async () => useValidatePassword('5kgnGPq^&t4DSYW!SH#4N'), { + wrapper: settingsMockWrapper, }); - await waitForValueToChange(() => result.current); const res = await result.current; expect(res).toBeTruthy(); }); - -it('should return `undefined` if password validation is still loading', async () => { - const response: Response = { - enabled: true, - policy: [['get-password-policy-minLength', { minLength: 8 }]], - }; - - const { result } = renderHook(async () => useValidatePassword('secret-password'), { - wrapper: mockAppRoot() - .withEndpoint('GET', '/v1/pw.getPolicy', () => response) - .build(), - }); - - const res = await result.current; - expect(res).toBeUndefined(); -}); diff --git a/packages/ui-client/src/hooks/useValidatePassword.ts b/packages/ui-client/src/hooks/useValidatePassword.ts index 3402bbaf8435..9098d13009e1 100644 --- a/packages/ui-client/src/hooks/useValidatePassword.ts +++ b/packages/ui-client/src/hooks/useValidatePassword.ts @@ -1,8 +1,14 @@ import { useVerifyPassword } from '@rocket.chat/ui-contexts'; import { useMemo } from 'react'; -export const useValidatePassword = (password: string) => { - const { data: passwordVerifications, isLoading } = useVerifyPassword(password); +type passwordVerificationsType = { + name: string; + isValid: boolean; + limit?: number; +}[]; - return useMemo(() => (isLoading ? undefined : passwordVerifications.every(({ isValid }) => isValid)), [isLoading, passwordVerifications]); +export const useValidatePassword = (password: string): boolean => { + const passwordVerifications: passwordVerificationsType = useVerifyPassword(password); + + return useMemo(() => passwordVerifications.every(({ isValid }) => isValid), [passwordVerifications]); }; diff --git a/packages/ui-contexts/CHANGELOG.md b/packages/ui-contexts/CHANGELOG.md index 0be8b831d56b..af1cef5bcba6 100644 --- a/packages/ui-contexts/CHANGELOG.md +++ b/packages/ui-contexts/CHANGELOG.md @@ -1,5 +1,13 @@ # @rocket.chat/ui-contexts +## 1.0.4 + +### Patch Changes + +- @rocket.chat/core-typings@6.3.4 +- @rocket.chat/rest-typings@6.3.4 +- @rocket.chat/ddp-client@0.1.4 + ## 1.0.3 ### Patch Changes diff --git a/packages/ui-contexts/package.json b/packages/ui-contexts/package.json index 5742f523011f..fd262b4a66fc 100644 --- a/packages/ui-contexts/package.json +++ b/packages/ui-contexts/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/ui-contexts", - "version": "1.0.3", + "version": "1.0.4", "private": true, "devDependencies": { "@rocket.chat/core-typings": "workspace:^", @@ -29,6 +29,9 @@ "react": "~17.0.2", "use-sync-external-store": "^1.2.0" }, + "dependencies": { + "@rocket.chat/password-policies": "workspace:^" + }, "volta": { "extends": "../../package.json" }, diff --git a/packages/ui-contexts/src/hooks/usePasswordPolicy.ts b/packages/ui-contexts/src/hooks/usePasswordPolicy.ts deleted file mode 100644 index ca259a215586..000000000000 --- a/packages/ui-contexts/src/hooks/usePasswordPolicy.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; - -import { useEndpoint } from './useEndpoint'; - -export const usePasswordPolicy = () => { - const getPasswordPolicy = useEndpoint('GET', '/v1/pw.getPolicy'); - - return useQuery(['login', 'password-policy'], async () => getPasswordPolicy()); -}; diff --git a/packages/ui-contexts/src/hooks/useVerifyPassword.ts b/packages/ui-contexts/src/hooks/useVerifyPassword.ts index 71985e44887f..f1244b2552c6 100644 --- a/packages/ui-contexts/src/hooks/useVerifyPassword.ts +++ b/packages/ui-contexts/src/hooks/useVerifyPassword.ts @@ -1,69 +1,47 @@ -import { useCallback, useMemo } from 'react'; +import { PasswordPolicy } from '@rocket.chat/password-policies'; +import { useMemo } from 'react'; -import { usePasswordPolicy } from './usePasswordPolicy'; - -export const passwordVerificationsTemplate: Record boolean> = { - 'get-password-policy-minLength': (password: string, minLength?: number) => Boolean(minLength && password.length >= minLength), - 'get-password-policy-maxLength': (password: string, maxLength?: number) => Boolean(maxLength && password.length <= maxLength), - 'get-password-policy-forbidRepeatingCharactersCount': (password: string, maxRepeatingChars?: number) => { - const repeatingCharsHash = {} as Record; - - for (let i = 0; i < password.length; i++) { - const currentChar = password[i]; - - if (repeatingCharsHash[currentChar]) { - repeatingCharsHash[currentChar]++; - if (repeatingCharsHash[currentChar] === maxRepeatingChars) return false; - } else { - repeatingCharsHash[currentChar] = 1; - } - } - - return true; - }, - 'get-password-policy-mustContainAtLeastOneLowercase': (password: string) => /[a-z]/.test(password), - 'get-password-policy-mustContainAtLeastOneUppercase': (password: string) => /[A-Z]/.test(password), - 'get-password-policy-mustContainAtLeastOneNumber': (password: string) => /[0-9]/.test(password), - 'get-password-policy-mustContainAtLeastOneSpecialCharacter': (password: string) => /[^A-Za-z0-9\s]/.test(password), -}; +import { useSetting } from './useSetting'; type PasswordVerifications = { isValid: boolean; limit?: number; name: string }[]; -type PasswordPolicies = [key: string, value?: Record][]; - -export const useVerifyPasswordByPolices = (policies?: PasswordPolicies) => { - return useCallback( - (password: string): PasswordVerifications => { - if (!policies) { - return []; - } - return policies - .map(([name, rules]) => { - if (name === 'get-password-policy-forbidRepeatingCharacters') return; - const limit = rules && Object.values(rules)[0]; - - return { - name, - isValid: password.length !== 0 && passwordVerificationsTemplate[name](password, limit), - ...(limit && { limit }), - }; - }) - .filter(Boolean) as PasswordVerifications; - }, - [policies], +export const useVerifyPassword = (password: string): PasswordVerifications => { + const enabled = Boolean(useSetting('Accounts_Password_Policy_Enabled')); + const minLength = Number(useSetting('Accounts_Password_Policy_MinLength')); + const maxLength = Number(useSetting('Accounts_Password_Policy_MaxLength')); + const forbidRepeatingCharacters = Boolean(useSetting('Accounts_Password_Policy_ForbidRepeatingCharacters')); + const forbidRepeatingCharactersCount = Number(useSetting('Accounts_Password_Policy_ForbidRepeatingCharactersCount')); + const mustContainAtLeastOneLowercase = Boolean(useSetting('Accounts_Password_Policy_AtLeastOneLowercase')); + const mustContainAtLeastOneUppercase = Boolean(useSetting('Accounts_Password_Policy_AtLeastOneUppercase')); + const mustContainAtLeastOneNumber = Boolean(useSetting('Accounts_Password_Policy_AtLeastOneNumber')); + const mustContainAtLeastOneSpecialCharacter = Boolean(useSetting('Accounts_Password_Policy_AtLeastOneSpecialCharacter')); + + const validator = useMemo( + () => + new PasswordPolicy({ + enabled, + minLength, + maxLength, + forbidRepeatingCharacters, + forbidRepeatingCharactersCount, + mustContainAtLeastOneLowercase, + mustContainAtLeastOneUppercase, + mustContainAtLeastOneNumber, + mustContainAtLeastOneSpecialCharacter, + throwError: true, + }), + [ + enabled, + minLength, + maxLength, + forbidRepeatingCharacters, + forbidRepeatingCharactersCount, + mustContainAtLeastOneLowercase, + mustContainAtLeastOneUppercase, + mustContainAtLeastOneNumber, + mustContainAtLeastOneSpecialCharacter, + ], ); -}; - -export const useVerifyPassword = (password: string): { data: PasswordVerifications; isLoading: boolean } => { - const { data, isLoading } = usePasswordPolicy(); - const validator = useVerifyPasswordByPolices((data?.enabled && data?.policy) || undefined); - - return useMemo( - () => ({ - data: validator(password), - isLoading, - }), - [password, validator, isLoading], - ); + return useMemo(() => validator.sendValidationMessage(password || ''), [password, validator]); }; diff --git a/packages/ui-contexts/src/index.ts b/packages/ui-contexts/src/index.ts index fca1dc0e4edb..d404dc921579 100644 --- a/packages/ui-contexts/src/index.ts +++ b/packages/ui-contexts/src/index.ts @@ -80,7 +80,6 @@ export { useUserRoom } from './hooks/useUserRoom'; export { useUserSubscription } from './hooks/useUserSubscription'; export { useUserSubscriptionByName } from './hooks/useUserSubscriptionByName'; export { useUserSubscriptions } from './hooks/useUserSubscriptions'; -export { usePasswordPolicy } from './hooks/usePasswordPolicy'; export { useVerifyPassword } from './hooks/useVerifyPassword'; export { useSelectedDevices } from './hooks/useSelectedDevices'; export { useDeviceConstraints } from './hooks/useDeviceConstraints'; diff --git a/packages/ui-video-conf/CHANGELOG.md b/packages/ui-video-conf/CHANGELOG.md index b5408f61908a..114a6fe5d3ca 100644 --- a/packages/ui-video-conf/CHANGELOG.md +++ b/packages/ui-video-conf/CHANGELOG.md @@ -1,5 +1,11 @@ # @rocket.chat/ui-video-conf +## 1.0.4 + +### Patch Changes + +- @rocket.chat/ui-contexts@1.0.4 + ## 1.0.3 ### Patch Changes diff --git a/packages/ui-video-conf/package.json b/packages/ui-video-conf/package.json index c2ce9e393647..00106886a26e 100644 --- a/packages/ui-video-conf/package.json +++ b/packages/ui-video-conf/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/ui-video-conf", - "version": "1.0.3", + "version": "1.0.4", "private": true, "devDependencies": { "@babel/core": "~7.22.9", @@ -35,7 +35,7 @@ "@rocket.chat/fuselage-hooks": "*", "@rocket.chat/icons": "*", "@rocket.chat/styled": "*", - "@rocket.chat/ui-contexts": "1.0.3", + "@rocket.chat/ui-contexts": "1.0.4", "react": "^17.0.2", "react-dom": "^17.0.2" }, diff --git a/packages/uikit-playground/CHANGELOG.md b/packages/uikit-playground/CHANGELOG.md index 852e0fe95baf..fd1c5f2f689b 100644 --- a/packages/uikit-playground/CHANGELOG.md +++ b/packages/uikit-playground/CHANGELOG.md @@ -1,5 +1,12 @@ # @rocket.chat/uikit-playground +## 0.1.4 + +### Patch Changes + +- @rocket.chat/ui-contexts@1.0.4 +- @rocket.chat/fuselage-ui-kit@1.0.4 + ## 0.1.3 ### Patch Changes diff --git a/packages/uikit-playground/package.json b/packages/uikit-playground/package.json index db348c6300f5..d84f96820cd6 100644 --- a/packages/uikit-playground/package.json +++ b/packages/uikit-playground/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/uikit-playground", "private": true, - "version": "0.1.3", + "version": "0.1.4", "type": "module", "scripts": { "dev": "vite", diff --git a/packages/web-ui-registration/CHANGELOG.md b/packages/web-ui-registration/CHANGELOG.md index cf000c3483bc..e89a16f355fb 100644 --- a/packages/web-ui-registration/CHANGELOG.md +++ b/packages/web-ui-registration/CHANGELOG.md @@ -1,5 +1,11 @@ # @rocket.chat/web-ui-registration +## 1.0.4 + +### Patch Changes + +- @rocket.chat/ui-contexts@1.0.4 + ## 1.0.3 ### Patch Changes diff --git a/packages/web-ui-registration/package.json b/packages/web-ui-registration/package.json index a6e7332c948f..f6727baaac37 100644 --- a/packages/web-ui-registration/package.json +++ b/packages/web-ui-registration/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/web-ui-registration", - "version": "1.0.3", + "version": "1.0.4", "private": true, "homepage": "https://rocket.chat", "main": "./dist/index.js", @@ -49,7 +49,7 @@ }, "peerDependencies": { "@rocket.chat/layout": "*", - "@rocket.chat/ui-contexts": "1.0.3", + "@rocket.chat/ui-contexts": "1.0.4", "@tanstack/react-query": "*", "react": "*", "react-hook-form": "*", diff --git a/yarn.lock b/yarn.lock index b82c0d982e7a..89a0a48d4270 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7870,9 +7870,9 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/apps-engine@npm:1.41.0-alpha.325": - version: 1.41.0-alpha.325 - resolution: "@rocket.chat/apps-engine@npm:1.41.0-alpha.325" +"@rocket.chat/apps-engine@npm:1.41.0-alpha.312": + version: 1.41.0-alpha.312 + resolution: "@rocket.chat/apps-engine@npm:1.41.0-alpha.312" dependencies: adm-zip: ^0.5.9 cryptiles: ^4.1.3 @@ -7880,11 +7880,11 @@ __metadata: lodash.clonedeep: ^4.5.0 semver: ^5.7.1 stack-trace: 0.0.10 - uuid: ~8.3.2 + uuid: ^3.4.0 vm2: ^3.9.19 peerDependencies: "@rocket.chat/ui-kit": "*" - checksum: 3159b69d1174166bfe1fea13ac51e81bc39ddcabad3a8dcd20e4614d33592b7f93ae45625578d7545c149f52f90e9c30dee92a1bbd3f5830f7bcdc13d19fcef4 + checksum: 003853d3c4d4374ab984474026e4ae657daf4591fe4c375b914aa57c27f576af0fcba66e70c539e056b5d80a1ef655775f6f3a07bf81a36ab6fd438ce464e70f languageName: node linkType: hard @@ -8024,6 +8024,19 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/css-in-js@npm:~0.31.26-dev.23": + version: 0.31.26-dev.23 + resolution: "@rocket.chat/css-in-js@npm:0.31.26-dev.23" + dependencies: + "@emotion/hash": ^0.9.0 + "@rocket.chat/css-supports": ~0.31.26-dev.23 + "@rocket.chat/memo": ~0.31.26-dev.23 + "@rocket.chat/stylis-logical-props-middleware": ~0.31.26-dev.23 + stylis: ~4.1.3 + checksum: 6d71bd0f232c8ea3fc2711347064ddd14925b1c2b8713f6d7649b98679455029a53ee41d08b98d010da3ea4789afa21a15901a92efef61dee7b32d6965157445 + languageName: node + linkType: hard + "@rocket.chat/css-supports@npm:~0.31.26-dev.19": version: 0.31.26-dev.19 resolution: "@rocket.chat/css-supports@npm:0.31.26-dev.19" @@ -8033,6 +8046,15 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/css-supports@npm:~0.31.26-dev.23": + version: 0.31.26-dev.23 + resolution: "@rocket.chat/css-supports@npm:0.31.26-dev.23" + dependencies: + "@rocket.chat/memo": ~0.31.26-dev.23 + checksum: a4f25562df67214b1c92c85a1cd16eb03fc2aea385f48cdde42ad0053b9e03a92ca9e3486d1387c7a31cf68f47fa888825f31acae8f4700ee2b9f03495286a12 + languageName: node + linkType: hard + "@rocket.chat/ddp-client@workspace:^, @rocket.chat/ddp-client@workspace:ee/packages/ddp-client": version: 0.0.0-use.local resolution: "@rocket.chat/ddp-client@workspace:ee/packages/ddp-client" @@ -8238,13 +8260,20 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/fuselage-tokens@npm:next, @rocket.chat/fuselage-tokens@npm:~0.32.0-dev.379": +"@rocket.chat/fuselage-tokens@npm:next": version: 0.32.0-dev.379 resolution: "@rocket.chat/fuselage-tokens@npm:0.32.0-dev.379" checksum: c5cf40295c4ae1a5918651b9e156629d6400d5823b8cf5f81a14c66da986a9302d79392b45e991c2fc37aad9633f3d8e2f7f29c68969592340b05968265244e6 languageName: node linkType: hard +"@rocket.chat/fuselage-tokens@npm:~0.32.0-dev.383": + version: 0.32.0-dev.383 + resolution: "@rocket.chat/fuselage-tokens@npm:0.32.0-dev.383" + checksum: bd3504fa6a7ce4ed6fc91246c4c8a4e3e3da8bc5e2c5590e7f913bc1fd6f08896aa4a6c4b1d01dccf78267ade9ad5a831c788cb17a4eb744deefb45032a34894 + languageName: node + linkType: hard + "@rocket.chat/fuselage-ui-kit@workspace:^, @rocket.chat/fuselage-ui-kit@workspace:packages/fuselage-ui-kit, @rocket.chat/fuselage-ui-kit@workspace:~": version: 0.0.0-use.local resolution: "@rocket.chat/fuselage-ui-kit@workspace:packages/fuselage-ui-kit" @@ -8291,9 +8320,9 @@ __metadata: "@rocket.chat/icons": "*" "@rocket.chat/prettier-config": "*" "@rocket.chat/styled": "*" - "@rocket.chat/ui-contexts": 1.0.3 + "@rocket.chat/ui-contexts": 1.0.4 "@rocket.chat/ui-kit": "*" - "@rocket.chat/ui-video-conf": 1.0.3 + "@rocket.chat/ui-video-conf": 1.0.4 "@tanstack/react-query": "*" react: "*" react-dom: "*" @@ -8301,14 +8330,14 @@ __metadata: linkType: soft "@rocket.chat/fuselage@npm:next": - version: 0.32.0-dev.429 - resolution: "@rocket.chat/fuselage@npm:0.32.0-dev.429" - dependencies: - "@rocket.chat/css-in-js": ~0.31.26-dev.19 - "@rocket.chat/css-supports": ~0.31.26-dev.19 - "@rocket.chat/fuselage-tokens": ~0.32.0-dev.379 - "@rocket.chat/memo": ~0.31.26-dev.19 - "@rocket.chat/styled": ~0.31.26-dev.19 + version: 0.32.0-dev.433 + resolution: "@rocket.chat/fuselage@npm:0.32.0-dev.433" + dependencies: + "@rocket.chat/css-in-js": ~0.31.26-dev.23 + "@rocket.chat/css-supports": ~0.31.26-dev.23 + "@rocket.chat/fuselage-tokens": ~0.32.0-dev.383 + "@rocket.chat/memo": ~0.31.26-dev.23 + "@rocket.chat/styled": ~0.31.26-dev.23 invariant: ^2.2.4 react-aria: ~3.23.1 react-keyed-flatten-children: ^1.3.0 @@ -8320,7 +8349,7 @@ __metadata: react: ^17.0.2 react-dom: ^17.0.2 react-virtuoso: 1.2.4 - checksum: 13ea95dea15c3677f82ffeb50780bc3479512cba6e226080bf464cf876794ed267db3419c45f63ddeaaff6a3401426ca4722e23e0f3586ca4f8eb2e6e25a7a70 + checksum: 2696da3e5cdf9d21c9c96ba069a3ef44b946ce832796ed10047666da780ee3ae88d679f3c3222fe6c6d88b1bdc0b7dab97c83d0acfbb67ca330a14b6e4739aa9 languageName: node linkType: hard @@ -8375,7 +8404,7 @@ __metadata: ts-jest: ~29.0.5 typescript: ~5.2.2 peerDependencies: - "@rocket.chat/core-typings": 6.3.3 + "@rocket.chat/core-typings": 6.3.4 "@rocket.chat/css-in-js": "*" "@rocket.chat/fuselage": "*" "@rocket.chat/fuselage-tokens": "*" @@ -8578,6 +8607,13 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/memo@npm:~0.31.26-dev.23": + version: 0.31.26-dev.23 + resolution: "@rocket.chat/memo@npm:0.31.26-dev.23" + checksum: 68301161d87ba25347f1d2ab85c139ba86c5fdd1101f41678808c19ba461772814f4bff048a30e4aefd08978fe2feb952c541bddc0beb6bc3cd190bd7852393b + languageName: node + linkType: hard + "@rocket.chat/message-parser@npm:next": version: 0.32.0-dev.377 resolution: "@rocket.chat/message-parser@npm:0.32.0-dev.377" @@ -8616,7 +8652,7 @@ __metadata: "@rocket.chat/account-utils": "workspace:^" "@rocket.chat/agenda": "workspace:^" "@rocket.chat/api-client": "workspace:^" - "@rocket.chat/apps-engine": 1.41.0-alpha.325 + "@rocket.chat/apps-engine": 1.41.0-alpha.312 "@rocket.chat/base64": "workspace:^" "@rocket.chat/cas-validate": "workspace:^" "@rocket.chat/core-services": "workspace:^" @@ -8651,6 +8687,7 @@ __metadata: "@rocket.chat/mp3-encoder": 0.24.0 "@rocket.chat/omnichannel-services": "workspace:^" "@rocket.chat/onboarding-ui": next + "@rocket.chat/password-policies": "workspace:^" "@rocket.chat/pdf-worker": "workspace:^" "@rocket.chat/poplib": "workspace:^" "@rocket.chat/presence": "workspace:^" @@ -9113,6 +9150,20 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/password-policies@workspace:^, @rocket.chat/password-policies@workspace:packages/password-policies": + version: 0.0.0-use.local + resolution: "@rocket.chat/password-policies@workspace:packages/password-policies" + dependencies: + "@types/chai": ^4.3.5 + "@types/jest": ~29.5.3 + chai: ^4.3.7 + eslint: ~8.45.0 + jest: ~29.6.1 + ts-jest: ~29.0.5 + typescript: ~5.2.2 + languageName: unknown + linkType: soft + "@rocket.chat/pdf-worker@workspace:^, @rocket.chat/pdf-worker@workspace:ee/packages/pdf-worker": version: 0.0.0-use.local resolution: "@rocket.chat/pdf-worker@workspace:ee/packages/pdf-worker" @@ -9408,6 +9459,15 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/styled@npm:~0.31.26-dev.23": + version: 0.31.26-dev.23 + resolution: "@rocket.chat/styled@npm:0.31.26-dev.23" + dependencies: + "@rocket.chat/css-in-js": ~0.31.26-dev.23 + checksum: 0a1ff89b068f011097671c617844856b91f2477c16ff3771fcfc0bab62a905a9b21c7b79549ff028613700a72685fd591ba9cbeda6b5d3bd8becd3af7aef0498 + languageName: node + linkType: hard + "@rocket.chat/stylis-logical-props-middleware@npm:~0.31.26-dev.19": version: 0.31.26-dev.19 resolution: "@rocket.chat/stylis-logical-props-middleware@npm:0.31.26-dev.19" @@ -9419,6 +9479,17 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/stylis-logical-props-middleware@npm:~0.31.26-dev.23": + version: 0.31.26-dev.23 + resolution: "@rocket.chat/stylis-logical-props-middleware@npm:0.31.26-dev.23" + dependencies: + "@rocket.chat/css-supports": ~0.31.26-dev.23 + peerDependencies: + stylis: 4.0.10 + checksum: b2fbfad3b2f4dedd9023b30d4cdc51e76ae76faeeca5819cf697e896c02fd4bb2dde5bbc428b377d77f32011fd8cc82c6d98a84d66b93056ef981c13aee1dc67 + languageName: node + linkType: hard + "@rocket.chat/tools@workspace:^, @rocket.chat/tools@workspace:packages/tools": version: 0.0.0-use.local resolution: "@rocket.chat/tools@workspace:packages/tools" @@ -9478,7 +9549,7 @@ __metadata: "@rocket.chat/fuselage": "*" "@rocket.chat/fuselage-hooks": "*" "@rocket.chat/icons": "*" - "@rocket.chat/ui-contexts": 1.0.3 + "@rocket.chat/ui-contexts": 1.0.4 react: ~17.0.2 languageName: unknown linkType: soft @@ -9523,6 +9594,7 @@ __metadata: "@rocket.chat/core-typings": "workspace:^" "@rocket.chat/emitter": next "@rocket.chat/fuselage-hooks": next + "@rocket.chat/password-policies": "workspace:^" "@rocket.chat/rest-typings": "workspace:^" "@types/jest": ~29.5.3 "@types/react": ~17.0.62 @@ -9630,7 +9702,7 @@ __metadata: "@rocket.chat/fuselage-hooks": "*" "@rocket.chat/icons": "*" "@rocket.chat/styled": "*" - "@rocket.chat/ui-contexts": 1.0.3 + "@rocket.chat/ui-contexts": 1.0.4 react: ^17.0.2 react-dom: ^17.0.2 languageName: unknown @@ -9718,7 +9790,7 @@ __metadata: typescript: ~5.2.2 peerDependencies: "@rocket.chat/layout": "*" - "@rocket.chat/ui-contexts": 1.0.3 + "@rocket.chat/ui-contexts": 1.0.4 "@tanstack/react-query": "*" react: "*" react-hook-form: "*" @@ -39105,7 +39177,7 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^8.0.0, uuid@npm:^8.3.1, uuid@npm:^8.3.2, uuid@npm:~8.3.2": +"uuid@npm:^8.0.0, uuid@npm:^8.3.1, uuid@npm:^8.3.2": version: 8.3.2 resolution: "uuid@npm:8.3.2" bin: