diff --git a/.changeset/fast-dogs-love.md b/.changeset/fast-dogs-love.md new file mode 100644 index 00000000..ec6102ef --- /dev/null +++ b/.changeset/fast-dogs-love.md @@ -0,0 +1,5 @@ +--- +'@nordeck/matrix-meetings-bot': minor +--- + +Enables encryption support for the Bot diff --git a/.dockerignore b/.dockerignore index e3aa0ac6..9ba8c356 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,6 +4,5 @@ !/matrix-meetings-bot/package.json !/matrix-meetings-bot/conf !/matrix-meetings-bot/lib -!/resolutions/matrix-sdk-crypto-nodejs !/packages/calendar/package.json !/packages/calendar/lib diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd030ffd..2edd6f45 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ on: jobs: build-widget: runs-on: ubuntu-latest - timeout-minutes: 25 + timeout-minutes: 30 env: DOCKER_IMAGE: ghcr.io/nordeck/matrix-meetings-widget steps: diff --git a/charts/matrix-meetings-bot/files/shell-tools/create_bot_account.sh b/charts/matrix-meetings-bot/files/shell-tools/create_bot_account.sh index 9c5a9574..bd61bf6f 100644 --- a/charts/matrix-meetings-bot/files/shell-tools/create_bot_account.sh +++ b/charts/matrix-meetings-bot/files/shell-tools/create_bot_account.sh @@ -5,9 +5,9 @@ while [ $(curl -k -sw '%{http_code}' "$HOMESERVER" -o /dev/null) -ne 302 ]; do done response=$(curl -k --write-out '%{http_code}' --silent --output /dev/null -X GET --header 'Accept: application/json' $HOMESERVER/_matrix/client/r0/register/available?username=$USERTOCREATE) if [ "$response" = 400 ]; then - echo "User already existant" + echo "Bot user already exists" else echo "Will create User $USERTOCREATE on $HOMESERVER" - register_new_matrix_user -a -u $USERTOCREATE -p `cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1` -c /data/homeserver.yaml $HOMESERVER + register_new_matrix_user -a -u $USERTOCREATE -p $BOT_PASSWORD -c /data/homeserver.yaml $HOMESERVER fi exit 0 diff --git a/charts/matrix-meetings-bot/files/shell-tools/get_meetings_bot_token.sh b/charts/matrix-meetings-bot/files/shell-tools/get_meetings_bot_token.sh index 538dbc4c..d3d7ee28 100644 --- a/charts/matrix-meetings-bot/files/shell-tools/get_meetings_bot_token.sh +++ b/charts/matrix-meetings-bot/files/shell-tools/get_meetings_bot_token.sh @@ -1,4 +1,16 @@ #/bin/sh -TOKEN=$(psql -X -A -w -t -c "select token from access_tokens where user_id='@$USERTOCREATE:$SERVER'") -echo "ACCESS_TOKEN=$TOKEN" > /work-dir/.env +# Get the login token +TOKEN_RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" -d '{"type":"m.login.password","user":"'${USERTOCREATE}'","password":"'${BOT_PASSWORD}'"}' "${HOMESERVER}/_matrix/client/r0/login") + +# Extract the access token from the response +ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | grep -o '"access_token":"[^"]*' | cut -d'"' -f4) + +if [ "$ACCESS_TOKEN" != "null" ]; then + echo "Login successful. Access token: $ACCESS_TOKEN" +else + echo "Login failed. Check your credentials and try again." +fi + +# Add it to the env file so it can be used by the bot +echo "ACCESS_TOKEN=$ACCESS_TOKEN" > /work-dir/.env diff --git a/charts/matrix-meetings-bot/files/shell-tools/set_ratelimit_bot_db.sh b/charts/matrix-meetings-bot/files/shell-tools/set_ratelimit_bot_db.sh index d1b19bd9..7a0f3b84 100644 --- a/charts/matrix-meetings-bot/files/shell-tools/set_ratelimit_bot_db.sh +++ b/charts/matrix-meetings-bot/files/shell-tools/set_ratelimit_bot_db.sh @@ -1,6 +1,6 @@ #/bin/sh - USER=$(psql -X -A -w -t -c "select user_id from ratelimit_override where user_id='@$USERTOCREATE:$SERVER'") +USER=$(psql -X -A -w -t -c "select user_id from ratelimit_override where user_id='@$USERTOCREATE:$SERVER'") if [ "$USER" = 400 ]; then echo "Limit is already set" exit 0 diff --git a/charts/matrix-meetings-bot/templates/deployment.yaml b/charts/matrix-meetings-bot/templates/deployment.yaml index 17b539a0..37402533 100644 --- a/charts/matrix-meetings-bot/templates/deployment.yaml +++ b/charts/matrix-meetings-bot/templates/deployment.yaml @@ -37,6 +37,11 @@ spec: value: "{{ .Values.init.username }}" - name: HOMESERVER value: "{{ .Values.init.homeserverUrl }}" + - name: BOT_PASSWORD + valueFrom: + secretKeyRef: + name: meetings-bot-credentials + key: password command: - sh - /scripts/create_bot_account.sh @@ -89,7 +94,7 @@ spec: - name: SERVER value: "{{ .Values.init.homeserver }}" {{- end }} - {{- if .Values.init.readBotTokenFromDB.enabled }} + {{- if .Values.init.getFreshDeviceToken.enabled }} - name: getbottoken image: {{ .Values.init.postgresClient.image }} command: @@ -102,35 +107,15 @@ spec: - name: shell-tools mountPath: /scripts env: - - name: PGPORT - valueFrom: - secretKeyRef: - name: pg-credentials - key: db_port - - name: PGPASSWORD + - name: USERTOCREATE + value: "{{ .Values.init.username }}" + - name: HOMESERVER + value: "{{ .Values.init.homeserverUrl }}" + - name: BOT_PASSWORD valueFrom: secretKeyRef: - name: pg-credentials + name: meetings-bot-credentials key: password - - name: PGDATABASE - valueFrom: - secretKeyRef: - name: pg-credentials - key: db_name - - name: PGUSER - valueFrom: - secretKeyRef: - name: pg-credentials - key: username - - name: PGHOST - valueFrom: - secretKeyRef: - name: pg-credentials - key: db_host - - name: USERTOCREATE - value: "{{ .Values.init.username }}" - - name: SERVER - value: "{{ .Values.init.homeserver }}" {{- end }} containers: - name: {{ .Chart.Name }} @@ -165,7 +150,7 @@ spec: volumeMounts: - name: data mountPath: /app/storage - {{- if .Values.init.readBotTokenFromDB.enabled }} + {{- if .Values.init.getFreshDeviceToken.enabled }} - name: workdir mountPath: "/app/.env" subPath: ".env" @@ -196,7 +181,7 @@ spec: configMap: name: {{ include "matrix-meetings-bot.fullname" . }}-cm defaultMode: 0777 - {{- if or .Values.init.createUserAccount.enabled .Values.init.disableRateLimitInDB.enabled .Values.init.readBotTokenFromDB.enabled }} + {{- if or .Values.init.createUserAccount.enabled .Values.init.disableRateLimitInDB.enabled .Values.init.getFreshDeviceToken.enabled }} - name: shell-tools configMap: name: {{ include "matrix-meetings-bot.fullname" . }}-sh-tools diff --git a/charts/matrix-meetings-bot/values.yaml b/charts/matrix-meetings-bot/values.yaml index 9aa16fb9..62c0f900 100644 --- a/charts/matrix-meetings-bot/values.yaml +++ b/charts/matrix-meetings-bot/values.yaml @@ -107,7 +107,7 @@ settings: # - name: HOMESERVER_URL # value: 'https://matrix-client.matrix.org' - ## Configure the access token (can be skipped if init.readBotTokenFromDB.enabled is activated) + ## Configure the access token (can be skipped if init.getFreshDeviceToken.enabled is activated) # - name: ACCESS_TOKEN # secretKeyRef: # name: pg-credentials @@ -137,5 +137,5 @@ init: disableRateLimitInDB: enabled: false - readBotTokenFromDB: + getFreshDeviceToken: enabled: false diff --git a/charts/matrix-meetings/values.dev.yaml.tpl b/charts/matrix-meetings/values.dev.yaml.tpl index 97ef7333..038a75af 100644 --- a/charts/matrix-meetings/values.dev.yaml.tpl +++ b/charts/matrix-meetings/values.dev.yaml.tpl @@ -22,3 +22,5 @@ matrix-meetings-bot: value: 'info' - name: AUTO_DELETION_OFFSET value: '60' + - name: ENABLE_CRYPTO + value: 'true' diff --git a/docs/configuration.md b/docs/configuration.md index 558a00b6..32ae0a6c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -99,6 +99,12 @@ MATRIX_SERVER_EVENT_MAX_AGE_MINUTES=5 # optional - the folder where the bot stores local data like a persisted sessions STORAGE_FILE_DATA_PATH=storage +# optional - enables the bot to create end-to-end encrypted rooms for meetings and control rooms +ENABLE_CRYPTO=false + +# optional - the folder where the bot stores local encryption data. This is relative to the storage path above, ie 'storage/crypto' +CRYPTO_DATA_PATH=crypto + # optional - the json file with the session information inside the storage folder STORAGE_FILE_FILENAME=bot.json diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts index f97fd996..a89ce3a5 100644 --- a/e2e/playwright.config.ts +++ b/e2e/playwright.config.ts @@ -23,7 +23,7 @@ import { devices } from '@playwright/test'; const config: PlaywrightTestConfig = { testDir: './src', /* Increase default timeout from 30 sec as we often scratch it. */ - timeout: 60000, + timeout: 90000, /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ diff --git a/e2e/src/deploy/matrixMeetingsBot/index.ts b/e2e/src/deploy/matrixMeetingsBot/index.ts index dbeceefd..365e6b88 100644 --- a/e2e/src/deploy/matrixMeetingsBot/index.ts +++ b/e2e/src/deploy/matrixMeetingsBot/index.ts @@ -60,6 +60,7 @@ export async function startMatrixMeetingsBot({ 'https://webmail-hostname/appsuite/#app=io.ox/calendar&id={{id}}&folder={{folder}}', AUTO_DELETION_OFFSET: '60', ENABLE_GUEST_USER_POWER_LEVEL_CHANGE: 'true', + ENABLE_CRYPTO: 'true', LOG_LEVEL: 'debug', }) .withCopyFilesToContainer([ diff --git a/e2e/src/e2eeMeetingRoom.spec.ts b/e2e/src/e2eeMeetingRoom.spec.ts new file mode 100644 index 00000000..08087622 --- /dev/null +++ b/e2e/src/e2eeMeetingRoom.spec.ts @@ -0,0 +1,202 @@ +/* + * Copyright 2024 Nordeck IT + Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from '@playwright/test'; +import { test } from './fixtures'; + +test.describe('Encrypted Meeting Room', () => { + test.beforeEach( + async ({ + bob, + aliceEncryptedMeetingsWidgetPage, + aliceElementWebPage, + aliceJitsiWidgetPage, + }) => { + test.setTimeout(60000); + + await aliceEncryptedMeetingsWidgetPage.setDateFilter( + [2040, 10, 1], + [2040, 10, 8], + ); + + const aliceScheduleMeetingWidgetPage = + await aliceEncryptedMeetingsWidgetPage.scheduleMeeting(); + + await aliceScheduleMeetingWidgetPage.titleTextbox.fill('My Meeting'); + await aliceScheduleMeetingWidgetPage.descriptionTextbox.fill( + 'My Description', + ); + await aliceScheduleMeetingWidgetPage.setStart([2040, 10, 3], '10:30 AM'); + await aliceScheduleMeetingWidgetPage.addParticipant(bob.displayName); + await aliceScheduleMeetingWidgetPage.submit(); + + await aliceElementWebPage.inviteUser(bob.username); + + await aliceElementWebPage.waitForRoomJoin('My Meeting'); + await aliceEncryptedMeetingsWidgetPage + .getMeeting('My Meeting', '10/03/2040') + .joinMeeting(); + await aliceJitsiWidgetPage.joinConferenceButton.waitFor(); + }, + ); + + test('should have jitsi, breakout sessions, and settings widget setup in the room', async ({ + aliceElementWebPage, + aliceCockpitWidgetPage, + }) => { + expect(await aliceElementWebPage.getWidgets()).toEqual([ + 'Breakout Sessions', + 'NeoDateFix Details', + 'Video Conference', + ]); + + await expect(aliceElementWebPage.roomNameText).toHaveText('My Meeting'); + await expect(aliceElementWebPage.roomTopicText).toHaveText( + 'My Description', + ); + + await aliceElementWebPage.showWidgetInSidebar('NeoDateFix Details'); + const meetingDetails = aliceCockpitWidgetPage.getMeeting(); + await aliceElementWebPage.approveWidgetIdentity(); + await expect(meetingDetails.meetingDescriptionText).toHaveText( + 'My Description', + ); + await expect(meetingDetails.meetingTitleText).toHaveText('My Meeting'); + await expect(meetingDetails.meetingTimeRangeText).toHaveText( + 'October 3, 2040, 10:30 – 11:30 AM', + ); + }); + + test('should edit the meeting title from within the meeting', async ({ + aliceElementWebPage, + aliceCockpitWidgetPage, + }) => { + await aliceElementWebPage.showWidgetInSidebar('NeoDateFix Details'); + + const meetingDetails = aliceCockpitWidgetPage.getMeeting(); + await aliceElementWebPage.approveWidgetIdentity(); + const aliceEditMeetingWidgetPage = await meetingDetails.editMeeting(); + await aliceEditMeetingWidgetPage.titleTextbox.fill('New Meeting'); + await aliceEditMeetingWidgetPage.submit(); + + await expect(meetingDetails.meetingTitleText).toHaveText('New Meeting'); + await expect(aliceElementWebPage.roomNameText).toHaveText('New Meeting'); + await expect( + aliceElementWebPage.locateChatMessageInRoom(/Title: New Meeting/), + ).toBeVisible(); + }); + + // eslint-disable-next-line playwright/expect-expect + test('should add the meeting participant from within the meeting', async ({ + aliceElementWebPage, + aliceCockpitWidgetPage, + charlie, + }) => { + await aliceElementWebPage.showWidgetInSidebar('NeoDateFix Details'); + + const meetingDetails = aliceCockpitWidgetPage.getMeeting(); + await aliceElementWebPage.approveWidgetIdentity(); + + const aliceEditMeetingWidgetPage = await meetingDetails.editMeeting(); + await aliceEditMeetingWidgetPage.addParticipant(charlie.displayName); + await aliceEditMeetingWidgetPage.submit(); + + await aliceElementWebPage.waitForUserMembership(charlie.username, 'invite'); + }); + + test('should disable the video conference from within the meeting', async ({ + aliceElementWebPage, + aliceCockpitWidgetPage, + }) => { + await aliceElementWebPage.showWidgetInSidebar('NeoDateFix Details'); + const meetingDetails = aliceCockpitWidgetPage.getMeeting(); + await aliceElementWebPage.approveWidgetIdentity(); + const aliceEditMeetingWidgetPage = await meetingDetails.editMeeting(); + await aliceEditMeetingWidgetPage.removeLastWidget(); + await aliceEditMeetingWidgetPage.submit(); + + await aliceElementWebPage + .locateChatMessageInRoom('Video conference ended by Bot') + .waitFor(); + + await aliceElementWebPage.closeWidgetInSidebar(); + + await expect + .poll(async () => { + return await aliceElementWebPage.getWidgets(); + }) + .toEqual(['Breakout Sessions', 'NeoDateFix Details']); + }); + + test('should enable the optional widget from within the meeting', async ({ + aliceElementWebPage, + aliceCockpitWidgetPage, + }) => { + await aliceElementWebPage.showWidgetInSidebar('NeoDateFix Details'); + const meetingDetails = aliceCockpitWidgetPage.getMeeting(); + await aliceElementWebPage.approveWidgetIdentity(); + + const aliceEditMeetingWidgetPage = await meetingDetails.editMeeting(); + await aliceEditMeetingWidgetPage.addWidget('Video Conference (optional)'); + await aliceEditMeetingWidgetPage.submit(); + + await expect + .poll(async () => { + return await aliceElementWebPage.getWidgets(); + }) + .toEqual([ + 'Breakout Sessions', + 'NeoDateFix Details', + 'Video Conference', + 'Video Conference (optional)', + ]); + }); + + test('should toggle whether users can use the chat', async ({ + aliceElementWebPage, + aliceCockpitWidgetPage, + bobElementWebPage, + bobMeetingsWidgetPage, + }) => { + await bobElementWebPage.navigateToRoomOrInvitation('Calendar'); + await bobElementWebPage.acceptRoomInvitation(); + await bobElementWebPage.approveWidgetWarning(); + await bobElementWebPage.approveWidgetCapabilities(); + + await bobMeetingsWidgetPage.setDateFilter([2040, 10, 1], [2040, 10, 8]); + + await bobMeetingsWidgetPage + .getMeeting('My Meeting', '10/03/2040') + .joinMeeting(); + await bobElementWebPage.acceptRoomInvitation(); + await bobElementWebPage.sendMessage('I am Bob'); + await aliceElementWebPage.sendMessage('I am Alice'); + await aliceElementWebPage.showWidgetInSidebar('NeoDateFix Details'); + const meetingCard = aliceCockpitWidgetPage.getMeeting(); + await aliceElementWebPage.approveWidgetIdentity(); + let aliceEditMeetingWidgetPage = await meetingCard.editMeeting(); + await aliceEditMeetingWidgetPage.toggleChatPermission(); + await aliceEditMeetingWidgetPage.submit(); + await aliceElementWebPage.sendMessage('I am still here'); + await expect(bobElementWebPage.noChatPermissionText).toBeVisible(); + aliceEditMeetingWidgetPage = await meetingCard.editMeeting(); + await aliceEditMeetingWidgetPage.toggleChatPermission(); + await aliceEditMeetingWidgetPage.submit(); + await expect(bobElementWebPage.noChatPermissionText).toBeHidden(); + await bobElementWebPage.sendMessage('I am Bob again'); + await aliceElementWebPage.sendMessage('I am Alice again'); + }); +}); diff --git a/e2e/src/e2eeScheduleMeeting.spec.ts b/e2e/src/e2eeScheduleMeeting.spec.ts new file mode 100644 index 00000000..db421e0d --- /dev/null +++ b/e2e/src/e2eeScheduleMeeting.spec.ts @@ -0,0 +1,151 @@ +/* + * Copyright 2024 Nordeck IT + Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from '@playwright/test'; +import { test } from './fixtures'; + +test.describe('Schedule Encrypted Meeting', () => { + test('should schedule an encrypted meeting room and join automatically', async ({ + aliceElementWebPage, + aliceEncryptedMeetingsWidgetPage, + aliceJitsiWidgetPage, + }) => { + await aliceEncryptedMeetingsWidgetPage.setDateFilter( + [2040, 10, 1], + [2040, 10, 8], + ); + + const aliceScheduleMeetingWidgetPage = + await aliceEncryptedMeetingsWidgetPage.scheduleMeeting(); + + await aliceScheduleMeetingWidgetPage.titleTextbox.fill('My Meeting'); + await aliceScheduleMeetingWidgetPage.descriptionTextbox.fill( + 'My Description', + ); + await aliceScheduleMeetingWidgetPage.setStart([2040, 10, 3], '10:30 AM'); + await aliceScheduleMeetingWidgetPage.submit(); + + await expect( + aliceEncryptedMeetingsWidgetPage.getMeeting('My Meeting', '10/03/2040') + .meetingTimeRangeText, + ).toHaveText('10:30 AM – 11:30 AM'); + + await aliceElementWebPage.switchToRoom('My Meeting'); + + await expect(aliceJitsiWidgetPage.joinConferenceButton).toBeVisible(); + }); + + test('should schedule an encrypted meeting and invite a second user', async ({ + aliceElementWebPage, + aliceEncryptedMeetingsWidgetPage, + bob, + bobElementWebPage, + bobMeetingsWidgetPage, + bobJitsiWidgetPage, + }) => { + await aliceEncryptedMeetingsWidgetPage.setDateFilter( + [2040, 10, 1], + [2040, 10, 8], + ); + const aliceScheduleMeetingWidgetPage = + await aliceEncryptedMeetingsWidgetPage.scheduleMeeting(); + + await aliceScheduleMeetingWidgetPage.titleTextbox.fill('My Meeting'); + await aliceScheduleMeetingWidgetPage.descriptionTextbox.fill( + 'My Description', + ); + await aliceScheduleMeetingWidgetPage.setStart([2040, 10, 3], '10:30 AM'); + await aliceScheduleMeetingWidgetPage.selectRecurrence('daily'); + await aliceScheduleMeetingWidgetPage.setEndAfterMeetingCount(2); + await aliceScheduleMeetingWidgetPage.addParticipant(bob.displayName); + await aliceScheduleMeetingWidgetPage.submit(); + + await aliceElementWebPage.inviteUser(bob.username); + + await expect( + aliceEncryptedMeetingsWidgetPage.getMeeting('My Meeting', '10/03/2040') + .meetingTimeRangeText, + ).toHaveText('10:30 AM – 11:30 AM. Recurrence: Every day for 2 times', { + timeout: 30000, + }); + + await bobElementWebPage.navigateToRoomOrInvitation('Calendar'); + await bobElementWebPage.acceptRoomInvitation(); + await bobElementWebPage.approveWidgetWarning(); + await bobElementWebPage.approveWidgetCapabilities(); + + await bobMeetingsWidgetPage.setDateFilter([2040, 10, 1], [2040, 10, 8]); + + await bobMeetingsWidgetPage + .getMeeting('My Meeting', '10/03/2040') + .joinMeeting(); + + const inviteReason = await bobElementWebPage.revealRoomInviteReason(); + + await expect(inviteReason).toContainText( + '📅 10/3/2040, 10:30 – 11:30 AM GMT+2', + ); + await expect(inviteReason).toContainText( + '🔁 Recurrence: Every day for 2 times', + ); + await expect(inviteReason).toContainText( + "you've been invited to a meeting by Alice", + ); + await expect(inviteReason).toContainText('My Description'); + + await bobElementWebPage.acceptRoomInvitation(); + await expect(bobJitsiWidgetPage.joinConferenceButton).toBeVisible(); + }); + + test('should invite the second user via a link to an encrypted meeting', async ({ + aliceElementWebPage, + aliceEncryptedMeetingsWidgetPage, + bobPage, + bobElementWebPage, + bobJitsiWidgetPage, + }) => { + await aliceEncryptedMeetingsWidgetPage.setDateFilter( + [2040, 10, 1], + [2040, 10, 8], + ); + const aliceScheduleMeetingWidgetPage = + await aliceEncryptedMeetingsWidgetPage.scheduleMeeting(); + + await aliceScheduleMeetingWidgetPage.titleTextbox.fill('My Meeting'); + await aliceScheduleMeetingWidgetPage.descriptionTextbox.fill( + 'My Description', + ); + await aliceScheduleMeetingWidgetPage.setStart([2040, 10, 3], '10:30 AM'); + await aliceScheduleMeetingWidgetPage.submit(); + + const aliceMeeting = aliceEncryptedMeetingsWidgetPage.getMeeting( + 'My Meeting', + '10/03/2040', + ); + + await expect(aliceMeeting.meetingTimeRangeText).toHaveText( + '10:30 AM – 11:30 AM', + ); + + await aliceMeeting.switchToShareMeeting(); + await aliceElementWebPage.approveWidgetIdentity(); + const meetingLink = await aliceMeeting.getShareLink(); + + await bobPage.goto(meetingLink); + await bobElementWebPage.joinRoom(); + await expect(bobJitsiWidgetPage.joinConferenceButton).toBeVisible(); + }); +}); diff --git a/e2e/src/fixtures.ts b/e2e/src/fixtures.ts index 3c98239f..bd288444 100644 --- a/e2e/src/fixtures.ts +++ b/e2e/src/fixtures.ts @@ -32,6 +32,7 @@ type Fixtures = { alicePage: Page; aliceElementWebPage: ElementWebPage; aliceMeetingsWidgetPage: MeetingsWidgetPage; + aliceEncryptedMeetingsWidgetPage: MeetingsWidgetPage; aliceJitsiWidgetPage: JitsiWidgetPage; aliceCockpitWidgetPage: CockpitWidgetPage; aliceBreakoutSessionsPage: BreakoutSessionsPage; @@ -101,6 +102,30 @@ export const test = base.extend({ await use(meetingsWidgetPage); }, + aliceEncryptedMeetingsWidgetPage: async ( + { alicePage, aliceElementWebPage }, + use, + ) => { + await aliceElementWebPage.createRoom('Calendar', { encrypted: true }); + await aliceElementWebPage.inviteUser(getBotUsername()); + await aliceElementWebPage.waitForUserToJoin(getBotUsername()); + await aliceElementWebPage.promoteUserAsModerator(getBotUsername()); + + await aliceElementWebPage.approveWidgetWarning(); + await aliceElementWebPage.approveWidgetCapabilities(); + + const meetingsWidgetPage = new MeetingsWidgetPage( + alicePage, + aliceElementWebPage.widgetByTitle('NeoDateFix'), + ); + + await meetingsWidgetPage.scheduleMeetingButton.waitFor({ + state: 'attached', + }); + + await use(meetingsWidgetPage); + }, + aliceJitsiWidgetPage: async ({ aliceElementWebPage }, use) => { const jitsiWidgetPage = new JitsiWidgetPage( aliceElementWebPage.widgetByTitle('Video Conference'), diff --git a/e2e/src/meetingRoom.spec.ts b/e2e/src/meetingRoom.spec.ts index 66aa218b..b0bc1019 100644 --- a/e2e/src/meetingRoom.spec.ts +++ b/e2e/src/meetingRoom.spec.ts @@ -25,6 +25,8 @@ test.describe('Meeting Room', () => { aliceElementWebPage, aliceJitsiWidgetPage, }) => { + test.setTimeout(30000); + await aliceMeetingsWidgetPage.setDateFilter([2040, 10, 1], [2040, 10, 8]); const aliceScheduleMeetingWidgetPage = @@ -94,6 +96,7 @@ test.describe('Meeting Room', () => { ).toBeVisible(); }); + // eslint-disable-next-line playwright/expect-expect test('should add the meeting participant from within the meeting', async ({ aliceElementWebPage, aliceCockpitWidgetPage, diff --git a/e2e/src/pages/breakoutSessionsPage.ts b/e2e/src/pages/breakoutSessionsPage.ts index 58f5bd1e..fb5a2392 100644 --- a/e2e/src/pages/breakoutSessionsPage.ts +++ b/e2e/src/pages/breakoutSessionsPage.ts @@ -73,7 +73,7 @@ export class BreakoutSessionsPage { } async sendMessageToAllBreakoutSession(message: string) { - await this.sendMessageToBreakoutSessionsTextbox.type(message); + await this.sendMessageToBreakoutSessionsTextbox.fill(message); await this.sendMessageToBreakoutSessionsTextbox.press('Enter'); } diff --git a/e2e/src/pages/elementWebPage.ts b/e2e/src/pages/elementWebPage.ts index 699b1841..ed044626 100644 --- a/e2e/src/pages/elementWebPage.ts +++ b/e2e/src/pages/elementWebPage.ts @@ -67,7 +67,7 @@ export class ElementWebPage { await dialogLocator .getByRole('switch', { name: 'Remember my selection for this widget' }) // Increase but also limit the timeout to account for widget load time - .click({ timeout: 15000 }); + .click({ timeout: 30000 }); await dialogLocator.getByRole('button', { name: 'Approve' }).click(); } @@ -114,15 +114,24 @@ export class ElementWebPage { async ({ name, encrypted }) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const client = (window as any).mxMatrixClientPeg.get(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let initialState: any[] = []; if (encrypted) { - // TODO: Support encryption in rooms - - throw new Error('Encryption not supported!'); + initialState = [ + { + type: 'm.room.encryption', + state_key: '', + content: { + algorithm: 'm.megolm.v1.aes-sha2', + }, + }, + ]; } await client.createRoom({ name, + initial_state: initialState, }); }, { name, encrypted }, @@ -208,7 +217,7 @@ export class ElementWebPage { async sendMessage(message: string) { // Both for encrypted and non-encrypted cases - await this.sendMessageTextbox.type(message); + await this.sendMessageTextbox.fill(message); await this.sendMessageTextbox.press('Enter'); } @@ -271,29 +280,32 @@ export class ElementWebPage { ) { // Instead of controling the UI, we use the matrix client as it is faster. await expect - .poll(async () => { - const roomId = this.getCurrentRoomId(); - - return await this.page.evaluate( - async ({ roomId, username }) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const client = (window as any).mxMatrixClientPeg.get(); - - try { - const memberEvent = await client.getStateEvent( - roomId, - 'm.room.member', - `@${username}:localhost`, - ); - - return memberEvent.membership; - } catch (err) { - return undefined; - } - }, - { roomId, username }, - ); - }) + .poll( + async () => { + const roomId = this.getCurrentRoomId(); + + return await this.page.evaluate( + async ({ roomId, username }) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const client = (window as any).mxMatrixClientPeg.get(); + + try { + const memberEvent = await client.getStateEvent( + roomId, + 'm.room.member', + `@${username}:localhost`, + ); + + return memberEvent.membership; + } catch (err) { + return undefined; + } + }, + { roomId, username }, + ); + }, + { timeout: 90000 }, + ) .toEqual(membership); } diff --git a/e2e/src/pages/helper.ts b/e2e/src/pages/helper.ts index f2c45c0c..452dc88b 100644 --- a/e2e/src/pages/helper.ts +++ b/e2e/src/pages/helper.ts @@ -51,7 +51,8 @@ export async function fillDate( const [year, month, day] = date; const monthLabel = new Intl.DateTimeFormat('en', { month: 'short', - }).format(new Date(Date.UTC(2021, (month - 1) % 12))); + timeZone: 'UTC', + }).format(new Date(Date.UTC(year, (month - 1) % 12))); await pickerModal .getByRole('button', { name: /switch to year view/ }) diff --git a/e2e/src/pages/meetingDetailsPage.ts b/e2e/src/pages/meetingDetailsPage.ts index c50a25d9..b8662301 100644 --- a/e2e/src/pages/meetingDetailsPage.ts +++ b/e2e/src/pages/meetingDetailsPage.ts @@ -80,7 +80,10 @@ export class MeetingDetailsPage { await elementWebPage.approveWidgetCapabilities(); - await editMeetingWidgetPage.titleTextbox.waitFor({ state: 'attached' }); + await editMeetingWidgetPage.titleTextbox.waitFor({ + state: 'attached', + timeout: 30000, + }); await elementWebPage.approveWidgetIdentity(); diff --git a/e2e/src/pages/scheduleBreakoutSessionsWidgetPage.ts b/e2e/src/pages/scheduleBreakoutSessionsWidgetPage.ts index c321f52e..585dcb98 100644 --- a/e2e/src/pages/scheduleBreakoutSessionsWidgetPage.ts +++ b/e2e/src/pages/scheduleBreakoutSessionsWidgetPage.ts @@ -58,7 +58,7 @@ export class ScheduleBreakoutSessionsWidgetPage { .getByRole('list', { name: 'Groups' }) .locator(`role=listItem[name="${group}"]`) .getByRole('combobox', { name: 'Select participants' }); - await participantCombobox.type(name); + await participantCombobox.fill(name); await participantCombobox.press('ArrowDown'); await participantCombobox.press('Enter'); } diff --git a/e2e/src/pages/scheduleMeetingWidgetPage.ts b/e2e/src/pages/scheduleMeetingWidgetPage.ts index 72193200..654e7af5 100644 --- a/e2e/src/pages/scheduleMeetingWidgetPage.ts +++ b/e2e/src/pages/scheduleMeetingWidgetPage.ts @@ -100,8 +100,8 @@ export class ScheduleMeetingWidgetPage { } async addParticipant(name: string) { - await this.participantsCombobox.type(name); - await this.widget.getByRole('option', { name }).waitFor(); + await this.participantsCombobox.fill(name); + await this.widget.getByRole('option', { name }).waitFor({ timeout: 30000 }); await this.participantsCombobox.press('ArrowDown'); await this.participantsCombobox.press('Enter'); } diff --git a/e2e/src/recurringMeetings.spec.ts b/e2e/src/recurringMeetings.spec.ts index 873f9e4b..3832d9dd 100644 --- a/e2e/src/recurringMeetings.spec.ts +++ b/e2e/src/recurringMeetings.spec.ts @@ -244,7 +244,7 @@ test.describe('Recurring Meetings', () => { ).toBeVisible(); }); - test('should covert a recurring meeting into a single meeting', async ({ + test('should convert a recurring meeting into a single meeting', async ({ aliceMeetingsWidgetPage, aliceElementWebPage, }) => { diff --git a/e2e/src/scheduleBreakoutSessions.spec.ts b/e2e/src/scheduleBreakoutSessions.spec.ts index 70a37c8f..ecc3eec1 100644 --- a/e2e/src/scheduleBreakoutSessions.spec.ts +++ b/e2e/src/scheduleBreakoutSessions.spec.ts @@ -179,6 +179,7 @@ test.describe('Schedule Breakout Sessions', () => { ).meetingDescriptionText, ).toHaveText( 'My Description' + repeat('+', 20000 - 'My Description'.length), + { timeout: 30000 }, ); await expect( @@ -187,6 +188,7 @@ test.describe('Schedule Breakout Sessions', () => { ).meetingDescriptionText, ).toHaveText( 'My Description' + repeat('+', 20000 - 'My Description'.length), + { timeout: 30000 }, ); }); @@ -224,11 +226,11 @@ test.describe('Schedule Breakout Sessions', () => { await expect( aliceBreakoutSessionsPage.getBreakoutSession('Group 1') .meetingDescriptionText, - ).toHaveText('My Description'); + ).toHaveText('My Description', { timeout: 30000 }); await expect( aliceBreakoutSessionsPage.getBreakoutSession('Group 2') .meetingDescriptionText, - ).toHaveText('My Description'); + ).toHaveText('My Description', { timeout: 30000 }); await aliceBreakoutSessionsPage.sendMessageToAllBreakoutSession( 'Alice says hi to all breakout session rooms', diff --git a/matrix-meetings-bot/Dockerfile b/matrix-meetings-bot/Dockerfile index 473c39d6..70d3fd47 100644 --- a/matrix-meetings-bot/Dockerfile +++ b/matrix-meetings-bot/Dockerfile @@ -1,14 +1,12 @@ -# We use Debian for build and Alpine to run because the s390x Alpine container has issues running yarn -FROM node:20-bullseye AS node_modules +FROM node:20-bullseye-slim AS node_modules WORKDIR /build COPY package.json yarn.lock ./ COPY matrix-meetings-bot/package.json ./matrix-meetings-bot/ -COPY resolutions/matrix-sdk-crypto-nodejs ./resolutions/matrix-sdk-crypto-nodejs/ COPY packages/calendar/package.json ./packages/calendar/package.json COPY packages/calendar/lib ./packages/calendar/lib RUN yarn install --production --frozen-lockfile --network-timeout 1000000 -FROM node:20-alpine3.19 +FROM node:20-bullseye-slim ENV NODE_ENV=production WORKDIR /app RUN set -x\ @@ -20,4 +18,4 @@ COPY --from=node_modules /build/node_modules/ ./node_modules COPY --from=node_modules /build/packages/calendar/ ./packages/calendar/ COPY matrix-meetings-bot/conf ./conf COPY matrix-meetings-bot/lib ./lib -CMD ["node", "./lib/index.js"] +CMD ["node", "./lib/index.js"] \ No newline at end of file diff --git a/matrix-meetings-bot/package.json b/matrix-meetings-bot/package.json index a06762ae..78440f23 100644 --- a/matrix-meetings-bot/package.json +++ b/matrix-meetings-bot/package.json @@ -49,7 +49,7 @@ "joi": "^17.12.0", "lodash": "^4.17.20", "luxon": "^3.3.0", - "matrix-bot-sdk": "^0.7.1", + "matrix-bot-sdk": "npm:@nordeck/matrix-bot-sdk@0.7.1-crypto.beta.12", "@nordeck/matrix-meetings-calendar": "1.0.0", "mime-types": "^2.1.35", "mustache": "^4.2.0", diff --git a/matrix-meetings-bot/src/IAppConfiguration.ts b/matrix-meetings-bot/src/IAppConfiguration.ts index 3dbdb818..d129c619 100644 --- a/matrix-meetings-bot/src/IAppConfiguration.ts +++ b/matrix-meetings-bot/src/IAppConfiguration.ts @@ -40,6 +40,10 @@ export interface IAppConfiguration { data_path: string; data_filename: string; + // encryption + enable_crypto: boolean; + crypto_data_path: string; + enable_welcome_workflow: boolean; enable_control_room_migration: boolean; enable_private_room_error_sending: boolean; diff --git a/matrix-meetings-bot/src/app.module.ts b/matrix-meetings-bot/src/app.module.ts index 108ecafd..355d8a28 100644 --- a/matrix-meetings-bot/src/app.module.ts +++ b/matrix-meetings-bot/src/app.module.ts @@ -26,8 +26,10 @@ import i18next from 'i18next'; import i18nextFsBackend from 'i18next-fs-backend'; import i18nextMiddleware from 'i18next-http-middleware'; import { + ICryptoStorageProvider, IStorageProvider, MatrixClient, + RustSdkCryptoStorageProvider, SimpleFsStorageProvider, UserID, } from 'matrix-bot-sdk'; @@ -120,6 +122,20 @@ const matrixClientFactoryHelper = { return storage; }, + createCryptoStorageProvider: async ( + appConfig: IAppConfiguration, + ): Promise => { + const cryptoStorage = new RustSdkCryptoStorageProvider( + path.join(appConfig.data_path, appConfig.crypto_data_path), + ); + + logger.log( + `createCryptoStorageProvider.filepath: ${appConfig.data_path}/${appConfig.crypto_data_path}`, + ); + + return cryptoStorage; + }, + getServer: (appConfig: IAppConfiguration) => ({ accessToken: appConfig.access_token, url: appConfig.homeserver_url, @@ -132,10 +148,22 @@ const matrixClientFactory: FactoryProvider> = { const storage: IStorageProvider = await matrixClientFactoryHelper.createStorageProvider(appConfig); + let cryptoStorage: ICryptoStorageProvider | undefined = undefined; + + if (appConfig.enable_crypto) { + cryptoStorage = + await matrixClientFactoryHelper.createCryptoStorageProvider(appConfig); + } + // create server object const server = matrixClientFactoryHelper.getServer(appConfig); // create matrix client - const client = new MatrixClient(server.url, server.accessToken, storage); + const client = new MatrixClient( + server.url, + server.accessToken, + storage, + cryptoStorage, + ); // the 'room.archived' event listeners AutojoinUpgradedRoomsMixin.setupOnClient(client); diff --git a/matrix-meetings-bot/src/configuration.ts b/matrix-meetings-bot/src/configuration.ts index 8194aaca..3cbb61d9 100644 --- a/matrix-meetings-bot/src/configuration.ts +++ b/matrix-meetings-bot/src/configuration.ts @@ -71,6 +71,10 @@ function createConfiguration() { data_path: process.env.STORAGE_FILE_DATA_PATH ?? 'storage', data_filename: process.env.STORAGE_FILE_FILENAME ?? 'bot.json', + // encryption + enable_crypto: toBoolean(process.env.ENABLE_CRYPTO, false) ?? 'false', + crypto_data_path: process.env.CRYPTO_DATA_PATH ?? 'crypto', + enable_welcome_workflow: toBoolean( process.env.ENABLE_WELCOME_WORKFLOW, true, @@ -153,6 +157,10 @@ export const ValidationSchema = Joi.object({ STORAGE_FILE_DATA_PATH: Joi.string(), STORAGE_FILE_FILENAME: Joi.string(), + // encryption + enable_crypto: Joi.boolean(), + CRYPTO_DATA_PATH: Joi.string(), + ENABLE_WELCOME_WORKFLOW: Joi.boolean(), ENABLE_CONTROL_ROOM_MIGRATION: Joi.boolean(), ENABLE_PRIVATE_ROOM_ERROR_SENDING: Joi.boolean(), diff --git a/matrix-meetings-bot/src/model/RoomEventName.ts b/matrix-meetings-bot/src/model/RoomEventName.ts index 93cb63e1..d96f9205 100644 --- a/matrix-meetings-bot/src/model/RoomEventName.ts +++ b/matrix-meetings-bot/src/model/RoomEventName.ts @@ -15,6 +15,7 @@ */ export enum RoomEventName { + M_ROOM_ENCRYPTED = 'm.room.encrypted', M_ROOM_MESSAGE = 'm.room.message', M_ROOM_NOTICE = 'm.notice', M_TEXT = 'm.text', diff --git a/matrix-meetings-bot/src/rpc/MatrixServer.ts b/matrix-meetings-bot/src/rpc/MatrixServer.ts index 90ed52bc..10c8f05d 100644 --- a/matrix-meetings-bot/src/rpc/MatrixServer.ts +++ b/matrix-meetings-bot/src/rpc/MatrixServer.ts @@ -141,6 +141,7 @@ export class MatrixServer types: [ StateEventName.M_ROOM_MEMBER_EVENT, StateEventName.M_ROOM_POWER_LEVELS_EVENT, + RoomEventName.M_ROOM_ENCRYPTED, RoomEventName.M_ROOM_MESSAGE, RoomEventName.NIC_MEETINGS_MEETING_CREATE, RoomEventName.NIC_MEETINGS_BREAKOUTSESSIONS_CREATE, diff --git a/matrix-meetings-bot/test/util/MockUtils.ts b/matrix-meetings-bot/test/util/MockUtils.ts index c8a2d752..4fbc01a5 100644 --- a/matrix-meetings-bot/test/util/MockUtils.ts +++ b/matrix-meetings-bot/test/util/MockUtils.ts @@ -36,6 +36,8 @@ export function createAppConfig(): IAppConfiguration { breakout_session_widget_url: '', data_filename: '', data_path: '', + enable_crypto: false, + crypto_data_path: '', homeserver_url: '', matrix_link_share: '', matrix_server_event_max_age_minutes: 0, diff --git a/package.json b/package.json index 284d6d17..928be068 100644 --- a/package.json +++ b/package.json @@ -39,9 +39,6 @@ "translate": "yarn workspaces run translate", "e2e": "yarn workspace e2e e2e" }, - "resolutions": { - "@matrix-org/matrix-sdk-crypto-nodejs": "./resolutions/matrix-sdk-crypto-nodejs" - }, "lint-staged": { "*.{js,jsx,ts,tsx}": [ "yarn eslint --max-warnings=0", diff --git a/resolutions/matrix-sdk-crypto-nodejs/README.md b/resolutions/matrix-sdk-crypto-nodejs/README.md deleted file mode 100644 index c75a297e..00000000 --- a/resolutions/matrix-sdk-crypto-nodejs/README.md +++ /dev/null @@ -1,4 +0,0 @@ -This package replaces the original [@matrix-org/matrix-sdk-crypto-nodejs](https://www.npmjs.com/package/@matrix-org/matrix-sdk-crypto-nodejs) package that doesn't provide support for the [`s390x` platform](https://github.com/matrix-org/matrix-rust-sdk/issues/1503). -Since we don't use the crypto support, yet, we decided to replace it with a dummy package in the meantime to unblock providing it for `s390x`. - -This package is configured in the root [`package.json`](../../package.json) file in `"resolutions"`. diff --git a/resolutions/matrix-sdk-crypto-nodejs/index.js b/resolutions/matrix-sdk-crypto-nodejs/index.js deleted file mode 100644 index aa8be605..00000000 --- a/resolutions/matrix-sdk-crypto-nodejs/index.js +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 Nordeck IT + Consulting GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -module.exports = {}; diff --git a/resolutions/matrix-sdk-crypto-nodejs/package.json b/resolutions/matrix-sdk-crypto-nodejs/package.json deleted file mode 100644 index e967b252..00000000 --- a/resolutions/matrix-sdk-crypto-nodejs/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "matrix-sdk-crypto-nodejs", - "version": "1.0.0", - "main": "index.js", - "license": "MIT" -} diff --git a/yarn.lock b/yarn.lock index 2c4d424b..d844594d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2054,8 +2054,13 @@ globby "^11.0.0" read-yaml-file "^1.1.0" -"@matrix-org/matrix-sdk-crypto-nodejs@./resolutions/matrix-sdk-crypto-nodejs", "@matrix-org/matrix-sdk-crypto-nodejs@0.1.0-beta.6": - version "1.0.0" +"@matrix-org/matrix-sdk-crypto-nodejs@0.1.0-beta.12": + version "0.1.0-beta.12" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-nodejs/-/matrix-sdk-crypto-nodejs-0.1.0-beta.12.tgz#4adc5a6d93983cf589d64c450c10c6670b88f7c5" + integrity sha512-E3409r/kTaHVEc1TfcDxHZlT+NAcnJcMs8j94CYqWy4P4K8y2G4W/E+L+MGvktg7d1YqTiJtA0xQX3b7yWnUjw== + dependencies: + https-proxy-agent "^5.0.1" + node-downloader-helper "^2.1.5" "@matrix-widget-toolkit/api@^3.2.2": version "3.2.2" @@ -4428,9 +4433,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001517: - version "1.0.30001522" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001522.tgz#44b87a406c901269adcdb834713e23582dd71856" - integrity sha512-TKiyTVZxJGhsTszLuzb+6vUZSjVOAhClszBr2Ta2k9IwtNBT/4dzmL6aywt0HCgEZlmwJzXJd8yNiob6HgwTRg== + version "1.0.30001587" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz#a0bce920155fa56a1885a69c74e1163fc34b4881" + integrity sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA== case-sensitive-paths-webpack-plugin@^2.4.0: version "2.4.0" @@ -7376,6 +7381,14 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" +https-proxy-agent@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + human-id@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/human-id/-/human-id-1.0.2.tgz#e654d4b2b0d8b07e45da9f6020d8af17ec0a5df3" @@ -9041,12 +9054,12 @@ matcher-collection@^2.0.0: "@types/minimatch" "^3.0.3" minimatch "^3.0.2" -matrix-bot-sdk@^0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/matrix-bot-sdk/-/matrix-bot-sdk-0.7.1.tgz#b6b80d9f1a2c06795c61f114c12568ea78a1e694" - integrity sha512-klbuohKoOVdCw3foQyDxAswHLgIvBsEfTvt+SNu1RJYk/80UvKd/03YwYD470vILR/XtRXO8Cm+VC+5DvLwHaA== +"matrix-bot-sdk@npm:@nordeck/matrix-bot-sdk@0.7.1-crypto.beta.12": + version "0.7.1-crypto.beta.12" + resolved "https://registry.yarnpkg.com/@nordeck/matrix-bot-sdk/-/matrix-bot-sdk-0.7.1-crypto.beta.12.tgz#76f9ea275c663092929cd22f3f384e3dbc2c05f9" + integrity sha512-JXQ5Mu/c+I+Qg3Q7v3kLWwc2N5oW58w5OWF/WT3BV6lUIPBOzqXJMg8bVQFcyErR2NZQdsFMY1oUo+BtZbYMeg== dependencies: - "@matrix-org/matrix-sdk-crypto-nodejs" "0.1.0-beta.6" + "@matrix-org/matrix-sdk-crypto-nodejs" "0.1.0-beta.12" "@types/express" "^4.17.20" another-json "^0.2.0" async-lock "^1.4.0" @@ -9433,6 +9446,11 @@ node-abort-controller@^3.0.1: resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== +node-downloader-helper@^2.1.5: + version "2.1.9" + resolved "https://registry.yarnpkg.com/node-downloader-helper/-/node-downloader-helper-2.1.9.tgz#a59ee7276b2bf708bbac2cc5872ad28fc7cd1b0e" + integrity sha512-FSvAol2Z8UP191sZtsUZwHIN0eGoGue3uEXGdWIH5228e9KH1YHXT7fN8Oa33UGf+FbqGTQg3sJfrRGzmVCaJA== + node-emoji@1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c"