Compare commits
116 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c4048d985d | |||
| a8f8a9c14d | |||
| 0e2f73d7a7 | |||
| 31aeb3044f | |||
| 0a29063bc9 | |||
| 3cc3bd0728 | |||
| f891fe4423 | |||
| b99ff83785 | |||
| 23c4c9fd8a | |||
| 8b8ee91210 | |||
| d8c431f23e | |||
| a6fb7530cb | |||
| b5c3f15a67 | |||
| 91f6f0f9c5 | |||
| 88cf5eb769 | |||
| 13a967ae8f | |||
| 66c80949e8 | |||
| 3d51e31da8 | |||
| 86c190fa0d | |||
| e9a9280e3c | |||
| fabdd6da64 | |||
| ab5d95102c | |||
| b4556d6552 | |||
| 6c22da9a96 | |||
| d29329bddb | |||
| 68b06d7b60 | |||
| 5f599ee978 | |||
| aabd558bce | |||
| 662b772c73 | |||
| b240c44128 | |||
| 5508993d79 | |||
| c9b43ab251 | |||
| 2fb1e659c8 | |||
| b842192a34 | |||
| 868bbfcb31 | |||
| 860161bdd2 | |||
| 0c9d82e40a | |||
| 73ab7c342a | |||
| 3386c66b98 | |||
| da044820d7 | |||
| 9f40f32b3e | |||
| f679942584 | |||
| c1b4f97372 | |||
| 5f3b89990d | |||
| 866fd6f4a3 | |||
| 9ecb66e695 | |||
| baa6d13506 | |||
| 2d6230f199 | |||
| 825d85f18d | |||
| 1dcb7a6e75 | |||
| 823316b2ff | |||
| d56fa197d0 | |||
| f7229bfff0 | |||
| 538717c23e | |||
| 1a8ea3d685 | |||
| d0890d9450 | |||
| 42ec17b977 | |||
| 82e9eefce6 | |||
| f8208b1891 | |||
| 092a59af66 | |||
| dbd7d26968 | |||
| 4fda9e8419 | |||
| a13e0389db | |||
| dbb4828eda | |||
| 414ac9d8cc | |||
| 30058a4bdc | |||
| fab9cab3df | |||
| 53b599f8fe | |||
| 17f6cc733e | |||
| c8403f39aa | |||
| 8cf5df73ee | |||
| febe27ddcc | |||
| 7987ce76ec | |||
| 60cedf2fdb | |||
| ed44514974 | |||
| 9f8c1ee953 | |||
| 593a57fc2b | |||
| e8128d34a1 | |||
| ba7bd06295 | |||
| e4db6008b8 | |||
| 52f35409ec | |||
| f50aab37c3 | |||
| df0f817f83 | |||
| 7efa6352f8 | |||
| f74614705e | |||
| 169e8f8613 | |||
| f2f77bd1f7 | |||
| 5a1c70ad19 | |||
| 27cb16ffe4 | |||
| 9be0b3e701 | |||
| 05ba27f36b | |||
| e6acfdf275 | |||
| 2a6612c73a | |||
| 2f8b05b0da | |||
| fe984ede6e | |||
| 3f74b9a0cc | |||
| 802b996b10 | |||
| 8d44f9d665 | |||
| a72a1b294a | |||
| ab5f32f984 | |||
| 6a21d812ab | |||
| ee94e93354 | |||
| 31c4786a96 | |||
| 17b6e59819 | |||
| 42510022a1 | |||
| 5f0978ac3f | |||
| cd6787e0ac | |||
| 03baa3e358 | |||
| 658563e2a7 | |||
| 26d3033b17 | |||
| a4bd7dc7d7 | |||
| 1f48544b38 | |||
| 7ef4062f59 | |||
| 968bc51a35 | |||
| d413f5042e | |||
| b8e8b14375 |
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: ["matrix-org", "import", "jsdoc"],
|
||||
plugins: ["matrix-org", "import", "jsdoc", "n"],
|
||||
extends: ["plugin:matrix-org/babel", "plugin:matrix-org/jest", "plugin:import/typescript"],
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.json"],
|
||||
@@ -108,10 +108,8 @@ module.exports = {
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
// We're okay with assertion errors when we ask for them
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
|
||||
// The non-TypeScript rule produces false positives
|
||||
"func-call-spacing": "off",
|
||||
"@typescript-eslint/func-call-spacing": ["error"],
|
||||
// We do this sometimes to brand interfaces
|
||||
"@typescript-eslint/no-empty-object-type": "off",
|
||||
|
||||
"quotes": "off",
|
||||
// We use a `logger` intermediary module
|
||||
@@ -128,6 +126,14 @@ module.exports = {
|
||||
// These need a bit more work before we can enable
|
||||
// "jsdoc/check-param-names": "error",
|
||||
// "jsdoc/check-indentation": "error",
|
||||
// Ensure .ts extension on imports outside of tests
|
||||
"n/file-extension-in-import": [
|
||||
"error",
|
||||
"always",
|
||||
{
|
||||
tryExtensions: [".ts"],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -2,6 +2,7 @@
|
||||
/.github/workflows/** @matrix-org/element-web-team
|
||||
/package.json @matrix-org/element-web-team
|
||||
/yarn.lock @matrix-org/element-web-team
|
||||
/scripts/** @matrix-org/element-web-team
|
||||
/src/webrtc @matrix-org/element-call-reviewers
|
||||
/src/matrixrtc @matrix-org/element-call-reviewers
|
||||
/spec/*/webrtc @matrix-org/element-call-reviewers
|
||||
|
||||
@@ -22,7 +22,7 @@ runs:
|
||||
|
||||
- name: Upload tarball signature
|
||||
if: ${{ inputs.upload-url }}
|
||||
uses: shogo82148/actions-upload-release-asset@8f032eff0255912cc9c8455797fd6d72f25c7ab7 # v1
|
||||
uses: shogo82148/actions-upload-release-asset@aac270e08f6b4547ada0b3800f88e1eb3ce9d400 # v1
|
||||
with:
|
||||
upload_url: ${{ inputs.upload-url }}
|
||||
asset_path: ${{ env.VERSION }}.tar.gz.asc
|
||||
|
||||
@@ -29,13 +29,13 @@ runs:
|
||||
|
||||
- name: Upload asset signatures
|
||||
if: inputs.gpg-fingerprint
|
||||
uses: shogo82148/actions-upload-release-asset@8f032eff0255912cc9c8455797fd6d72f25c7ab7 # v1
|
||||
uses: shogo82148/actions-upload-release-asset@aac270e08f6b4547ada0b3800f88e1eb3ce9d400 # v1
|
||||
with:
|
||||
upload_url: ${{ inputs.upload-url }}
|
||||
asset_path: ${{ inputs.asset-path }}.asc
|
||||
|
||||
- name: Upload assets
|
||||
uses: shogo82148/actions-upload-release-asset@8f032eff0255912cc9c8455797fd6d72f25c7ab7 # v1
|
||||
uses: shogo82148/actions-upload-release-asset@aac270e08f6b4547ada0b3800f88e1eb3ce9d400 # v1
|
||||
with:
|
||||
upload_url: ${{ inputs.upload-url }}
|
||||
asset_path: ${{ inputs.asset-path }}
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
jobs:
|
||||
backport:
|
||||
name: Backport
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
# Only react to merged PRs for security reasons.
|
||||
# See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target.
|
||||
if: >
|
||||
|
||||
@@ -9,7 +9,7 @@ on:
|
||||
jobs:
|
||||
netlify:
|
||||
if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: 📥 Download artifact
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
@@ -21,14 +21,13 @@ concurrency:
|
||||
jobs:
|
||||
playwright:
|
||||
name: Playwright
|
||||
uses: matrix-org/matrix-react-sdk/.github/workflows/end-to-end-tests.yaml@develop
|
||||
uses: element-hq/element-web/.github/workflows/end-to-end-tests.yaml@develop
|
||||
permissions:
|
||||
actions: read
|
||||
issues: read
|
||||
pull-requests: read
|
||||
with:
|
||||
matrix-js-sdk-sha: ${{ github.sha }}
|
||||
react-sdk-repository: matrix-org/matrix-react-sdk
|
||||
# We only want to run the playwright tests on merge queue to prevent regressions
|
||||
# from creeping in. They take a long time to run and consume multiple concurrent runners.
|
||||
skip: ${{ github.event_name != 'merge_group' }}
|
||||
|
||||
@@ -14,10 +14,8 @@ jobs:
|
||||
include:
|
||||
- repo: element-hq/element-web
|
||||
event: element-web-notify
|
||||
- repo: matrix-org/matrix-react-sdk
|
||||
event: upstream-sdk-notify
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Notify matrix-react-sdk repo that a new SDK build is on develop so it can CI against it
|
||||
uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3
|
||||
|
||||
@@ -12,9 +12,9 @@ concurrency: ${{ github.workflow }}-${{ github.event.pull_request.head.ref || gi
|
||||
jobs:
|
||||
changelog:
|
||||
name: Preview Changelog
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: mheap/github-action-required-labels@5847eef68201219cf0a4643ea7be61e77837bbce # v5
|
||||
- uses: mheap/github-action-required-labels@d25134c992b943fb6ad00c25ea00eb5988c0a9dd # v5
|
||||
if: github.event_name != 'merge_group'
|
||||
with:
|
||||
labels: |
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
prevent-blocked:
|
||||
name: Prevent Blocked
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
pull-requests: read
|
||||
steps:
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
community-prs:
|
||||
name: Label Community PRs
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
if: github.event.action == 'opened'
|
||||
steps:
|
||||
- name: Check membership
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
|
||||
close-if-fork-develop:
|
||||
name: Forbid develop branch fork contributions
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
if: >
|
||||
github.event.action == 'opened' &&
|
||||
github.event.pull_request.head.ref == 'develop' &&
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
concurrency: release-drafter-action
|
||||
jobs:
|
||||
draft:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: 🧮 Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
script: |
|
||||
const { RELEASE_ID: releaseId, DEPENDENCY, VERSION } = process.env;
|
||||
const { owner, repo } = context.repo;
|
||||
const script = require("./.action-repo/scripts/release/merge-release-notes.js");
|
||||
const script = require("./.action-repo/scripts/release/merge-release-notes.cjs");
|
||||
|
||||
let deps = [];
|
||||
if (DEPENDENCY.includes("/")) {
|
||||
|
||||
@@ -15,7 +15,7 @@ on:
|
||||
concurrency: ${{ github.workflow }}
|
||||
jobs:
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -61,26 +61,25 @@ jobs:
|
||||
CURRENT_VERSION=$(cat package.json | jq -r .dependencies[\"$PACKAGE\"])
|
||||
echo "Current $PACKAGE version is $CURRENT_VERSION"
|
||||
|
||||
if [ "$CURRENT_VERSION" == "null" ]
|
||||
if [[ "$CURRENT_VERSION" == "null" ]]
|
||||
then
|
||||
echo "Unable to find $PACKAGE in package.json"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$CURRENT_VERSION" == "develop" ]
|
||||
if [[ "$CURRENT_VERSION" == *"#develop" ]]
|
||||
then
|
||||
echo "Not updating dependency $PACKAGE"
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "Resetting $1 to develop branch..."
|
||||
echo "Resetting $PACKAGE to develop branch..."
|
||||
yarn add "github:matrix-org/$PACKAGE#develop"
|
||||
git add -u
|
||||
git commit -m "Reset $PACKAGE back to develop branch"
|
||||
done <<< "$DEPENDENCIES"
|
||||
env:
|
||||
DEPENDENCIES: ${{ inputs.dependencies }}
|
||||
FINAL: ${{ inputs.final }}
|
||||
|
||||
- name: Push changes
|
||||
run: git push origin develop
|
||||
|
||||
@@ -41,7 +41,7 @@ on:
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
environment: Release
|
||||
steps:
|
||||
- name: Load GPG key
|
||||
@@ -276,7 +276,7 @@ jobs:
|
||||
post-release:
|
||||
name: Post release steps
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- id: repository
|
||||
run: echo "REPO=${GITHUB_REPOSITORY#*/}" >> $GITHUB_OUTPUT
|
||||
@@ -304,7 +304,7 @@ jobs:
|
||||
bump-downstreams:
|
||||
name: Update npm dependency in downstream projects
|
||||
needs: npm
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
if: inputs.downstreams
|
||||
strategy:
|
||||
matrix:
|
||||
|
||||
@@ -11,7 +11,7 @@ on:
|
||||
jobs:
|
||||
npm:
|
||||
name: Publish to npm
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
id: ${{ steps.npm-publish.outputs.id }}
|
||||
steps:
|
||||
|
||||
@@ -28,13 +28,13 @@ jobs:
|
||||
with:
|
||||
final: ${{ inputs.mode == 'final' }}
|
||||
npm: ${{ inputs.npm }}
|
||||
downstreams: '["matrix-org/matrix-react-sdk", "element-hq/element-web"]'
|
||||
downstreams: '["element-hq/element-web"]'
|
||||
|
||||
docs:
|
||||
name: Publish Documentation
|
||||
needs: release
|
||||
if: inputs.docs
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: 🧮 Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
needs: docs
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
|
||||
@@ -14,7 +14,7 @@ on:
|
||||
description: "Whether to combine multiple LCOV and jest-sonar-report files in coverage artifact"
|
||||
jobs:
|
||||
sonarqube:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
if: |
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.event != 'merge_group'
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
target_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
- name: "🧮 Checkout code"
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
repository: ${{ github.event.workflow_run.head_repository.full_name }}
|
||||
ref: ${{ github.event.workflow_run.head_branch }} # checkout commit that triggered this workflow
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
|
||||
- name: "🩻 SonarCloud Scan"
|
||||
id: sonarcloud
|
||||
uses: matrix-org/sonarcloud-workflow-action@v3.2
|
||||
uses: matrix-org/sonarcloud-workflow-action@v3.3
|
||||
# workflow_run fails report against the develop commit always, we don't want that for PRs
|
||||
continue-on-error: ${{ github.event.workflow_run.head_branch != 'develop' }}
|
||||
with:
|
||||
|
||||
@@ -11,7 +11,7 @@ concurrency:
|
||||
jobs:
|
||||
ts_lint:
|
||||
name: "Typescript Syntax Check"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
js_lint:
|
||||
name: "ESLint"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
workflow_lint:
|
||||
name: "Workflow Lint"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
|
||||
docs:
|
||||
name: "JSDoc Checker"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
|
||||
analyse_dead_code:
|
||||
name: "Analyse Dead Code"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ env:
|
||||
jobs:
|
||||
jest:
|
||||
name: "Jest [${{ matrix.specs }}] (Node ${{ matrix.node == '*' && 'latest' || matrix.node }})"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -68,15 +68,15 @@ jobs:
|
||||
name: Jest tests
|
||||
needs: jest
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- if: needs.jest.result != 'skipped' && needs.jest.result != 'success'
|
||||
run: exit 1
|
||||
|
||||
matrix-react-sdk:
|
||||
name: Downstream test matrix-react-sdk
|
||||
element-web:
|
||||
name: Downstream test element-web
|
||||
if: github.event_name == 'merge_group'
|
||||
uses: matrix-org/matrix-react-sdk/.github/workflows/tests.yml@develop
|
||||
uses: element-hq/element-web/.github/workflows/tests.yml@develop
|
||||
with:
|
||||
disable_coverage: true
|
||||
matrix-js-sdk-sha: ${{ github.sha }}
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
# we need this so the job is reported properly when run in a merge queue
|
||||
downstream-complement-crypto:
|
||||
name: Downstream Complement Crypto tests
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
if: always()
|
||||
needs:
|
||||
- complement-crypto
|
||||
@@ -103,10 +103,10 @@ jobs:
|
||||
# and skip sonarcloud coverage within merge queues
|
||||
downstream:
|
||||
name: Downstream tests
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
if: always()
|
||||
needs:
|
||||
- matrix-react-sdk
|
||||
- element-web
|
||||
steps:
|
||||
- name: Skip SonarCloud on merge queues
|
||||
if: env.ENABLE_COVERAGE == 'false'
|
||||
@@ -119,5 +119,5 @@ jobs:
|
||||
sha: ${{ github.sha }}
|
||||
target_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
- if: needs.matrix-react-sdk.result != 'skipped' && needs.matrix-react-sdk.result != 'success'
|
||||
- if: needs.element-web.result != 'skipped' && needs.element-web.result != 'success'
|
||||
run: exit 1
|
||||
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
automate-project-columns-next:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/add-to-project@main
|
||||
with:
|
||||
|
||||
@@ -1,3 +1,94 @@
|
||||
Changes in [34.10.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.10.0) (2024-11-05)
|
||||
====================================================================================================
|
||||
## 🦖 Deprecations
|
||||
|
||||
* Deprecate `CreateSecretStorageOpts.keyBackupInfo` used in `CryptoApi.bootstrapSecretStorage.` ([#4474](https://github.com/matrix-org/matrix-js-sdk/pull/4474)). Contributed by @florianduros.
|
||||
* Add CryptoApi.encryptToDeviceMessages() and deprecate Crypto.encryptAndSendToDevices() ([#4380](https://github.com/matrix-org/matrix-js-sdk/pull/4380)). Contributed by @hughns.
|
||||
* Remove abandoned MSC3886, MSC3903, MSC3906 experimental implementations ([#4469](https://github.com/matrix-org/matrix-js-sdk/pull/4469)). Contributed by @t3chguy.
|
||||
* Deprecate `MatrixClient.getDehydratedDevice` ([#4467](https://github.com/matrix-org/matrix-js-sdk/pull/4467)). Contributed by @florianduros.
|
||||
* Deprecate top level crypto events re-export ([#4444](https://github.com/matrix-org/matrix-js-sdk/pull/4444)). Contributed by @florianduros.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
* Add CryptoApi.encryptToDeviceMessages() and deprecate Crypto.encryptAndSendToDevices() ([#4380](https://github.com/matrix-org/matrix-js-sdk/pull/4380)). Contributed by @hughns.
|
||||
* Do not rotate MatrixRTC media encryption key when a new member joins a session ([#4472](https://github.com/matrix-org/matrix-js-sdk/pull/4472)). Contributed by @hughns.
|
||||
* Avoid `<sender>|<session>` notation in log messages ([#4473](https://github.com/matrix-org/matrix-js-sdk/pull/4473)). Contributed by @richvdh.
|
||||
* Refactor/simplify Promises in MatrixRTCSession ([#4466](https://github.com/matrix-org/matrix-js-sdk/pull/4466)). Contributed by @AndrewFerr.
|
||||
* Prepare delayed call leave events more reliably ([#4447](https://github.com/matrix-org/matrix-js-sdk/pull/4447)). Contributed by @AndrewFerr.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix DelayedEventInfo type ([#4446](https://github.com/matrix-org/matrix-js-sdk/pull/4446)). Contributed by @AndrewFerr.
|
||||
|
||||
|
||||
Changes in [34.9.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.9.0) (2024-10-22)
|
||||
==================================================================================================
|
||||
## 🦖 Deprecations
|
||||
|
||||
* Deprecate the crypto events which are not used by the rust-crypto ([#4442](https://github.com/matrix-org/matrix-js-sdk/pull/4442)). Contributed by @florianduros.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix the rust crypto import in esm environments. ([#4445](https://github.com/matrix-org/matrix-js-sdk/pull/4445)). Contributed by @saul-jb.
|
||||
* Fix MatrixRTC sender key wrapping ([#4441](https://github.com/matrix-org/matrix-js-sdk/pull/4441)). Contributed by @hughns.
|
||||
|
||||
|
||||
Changes in [34.8.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.8.0) (2024-10-15)
|
||||
==================================================================================================
|
||||
This release removes insecure functionality, resolving CVE-2024-47080 / GHSA-4jf8-g8wp-cx7c.
|
||||
|
||||
Changes in [34.7.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.7.0) (2024-10-08)
|
||||
==================================================================================================
|
||||
## 🦖 Deprecations
|
||||
|
||||
* RTCSession cleanup: deprecate getKeysForParticipant() and getEncryption(); add emitEncryptionKeys() ([#4427](https://github.com/matrix-org/matrix-js-sdk/pull/4427)). Contributed by @hughns.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
* Bump matrix-rust-sdk to 9.1.0 ([#4435](https://github.com/matrix-org/matrix-js-sdk/pull/4435)). Contributed by @richvdh.
|
||||
* Rotate Matrix RTC media encryption key when a new member joins a call for Post Compromise Security ([#4422](https://github.com/matrix-org/matrix-js-sdk/pull/4422)). Contributed by @hughns.
|
||||
* Update media event content types to include captions ([#4403](https://github.com/matrix-org/matrix-js-sdk/pull/4403)). Contributed by @tulir.
|
||||
* Update OIDC registration types to match latest MSC2966 state ([#4432](https://github.com/matrix-org/matrix-js-sdk/pull/4432)). Contributed by @t3chguy.
|
||||
* Add `CryptoApi.pinCurrentUserIdentity` and `UserIdentity.needsUserApproval` ([#4415](https://github.com/matrix-org/matrix-js-sdk/pull/4415)). Contributed by @richvdh.
|
||||
|
||||
|
||||
Changes in [34.6.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.6.0) (2024-09-24)
|
||||
==================================================================================================
|
||||
## 🦖 Deprecations
|
||||
|
||||
* Element-R: Mark unsupported MatrixClient methods as deprecated ([#4389](https://github.com/matrix-org/matrix-js-sdk/pull/4389)). Contributed by @richvdh.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
* Add crypto mode setting for invisible crypto, and apply it to decrypting events ([#4407](https://github.com/matrix-org/matrix-js-sdk/pull/4407)). Contributed by @uhoreg.
|
||||
* Don't share full key history for RTC per-participant encryption ([#4406](https://github.com/matrix-org/matrix-js-sdk/pull/4406)). Contributed by @hughns.
|
||||
* Export membership types ([#4405](https://github.com/matrix-org/matrix-js-sdk/pull/4405)). Contributed by @Johennes.
|
||||
* Fix sending redacts in embedded (widget) mode ([#4398](https://github.com/matrix-org/matrix-js-sdk/pull/4398)). Contributed by @toger5.
|
||||
* Expose the event ID of a call membership ([#4395](https://github.com/matrix-org/matrix-js-sdk/pull/4395)). Contributed by @robintown.
|
||||
* MSC4133 - Extended profiles ([#4391](https://github.com/matrix-org/matrix-js-sdk/pull/4391)). Contributed by @Half-Shot.
|
||||
|
||||
|
||||
Changes in [34.5.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.5.0) (2024-09-10)
|
||||
==================================================================================================
|
||||
## 🦖 Deprecations
|
||||
|
||||
* Deprecate unused callback hooks `CryptoCallbacks.onSecretRequested` and `CryptoCallbacks.getDehydrationKey` ([#4376](https://github.com/matrix-org/matrix-js-sdk/pull/4376)). Contributed by @richvdh.
|
||||
|
||||
|
||||
Changes in [34.4.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.4.0) (2024-08-27)
|
||||
==================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Use non-legacy calls if any are found ([#4337](https://github.com/matrix-org/matrix-js-sdk/pull/4337)). Contributed by @AndrewFerr.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Retry event decryption failures on first failure ([#4346](https://github.com/matrix-org/matrix-js-sdk/pull/4346)). Contributed by @hughns.
|
||||
* Ensure "type" = "module" ES declaration in pre-release.sh ([#4350](https://github.com/matrix-org/matrix-js-sdk/pull/4350)). Contributed by @BLCK-B.
|
||||
* Handle MatrixRTC encryption keys arriving out of order ([#4345](https://github.com/matrix-org/matrix-js-sdk/pull/4345)). Contributed by @hughns.
|
||||
* Resend MatrixRTC encryption keys if a membership has changed ([#4343](https://github.com/matrix-org/matrix-js-sdk/pull/4343)). Contributed by @hughns.
|
||||
|
||||
|
||||
Changes in [34.3.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.3.1) (2024-08-20)
|
||||
==================================================================================================
|
||||
# Security
|
||||
|
||||
@@ -191,6 +191,7 @@ As well as the primary entry point (`matrix-js-sdk`), there are several other en
|
||||
| `matrix-js-sdk/lib/crypto-api` | Cryptography functionality. |
|
||||
| `matrix-js-sdk/lib/types` | Low-level types, reflecting data structures defined in the Matrix spec. |
|
||||
| `matrix-js-sdk/lib/testing` | Test utilities, which may be useful in test code but should not be used in production code. |
|
||||
| `matrix-js-sdk/lib/utils/*.js` | A set of modules exporting standalone functions (and their types). |
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
+27
-1
@@ -14,7 +14,16 @@ module.exports = {
|
||||
modules: process.env.NODE_ENV === "test" ? "commonjs" : false,
|
||||
},
|
||||
],
|
||||
"@babel/preset-typescript",
|
||||
[
|
||||
"@babel/preset-typescript",
|
||||
{
|
||||
// When using the transpiled javascript in `lib`, Node.js requires `.js` extensions on any `import`
|
||||
// specifiers. However, Jest uses the TS source (via babel) and fails to resolve the `.js` names.
|
||||
// To resolve this,we use the `.ts` names in the source, and rewrite the `import` specifiers to use
|
||||
// `.js` during transpilation, *except* when we are targetting Jest.
|
||||
rewriteImportExtensions: process.env.NODE_ENV !== "test",
|
||||
},
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
"@babel/plugin-transform-numeric-separator",
|
||||
@@ -22,5 +31,22 @@ module.exports = {
|
||||
"@babel/plugin-transform-object-rest-spread",
|
||||
"@babel/plugin-syntax-dynamic-import",
|
||||
"@babel/plugin-transform-runtime",
|
||||
[
|
||||
"search-and-replace",
|
||||
{
|
||||
// Since rewriteImportExtensions doesn't work on dynamic imports (yet), we need to manually replace
|
||||
// the dynamic rust-crypto import.
|
||||
// (see https://github.com/babel/babel/issues/16750)
|
||||
rules:
|
||||
process.env.NODE_ENV !== "test"
|
||||
? [
|
||||
{
|
||||
search: "./rust-crypto/index.ts",
|
||||
replace: "./rust-crypto/index.js",
|
||||
},
|
||||
]
|
||||
: [],
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
+16
-11
@@ -1,9 +1,15 @@
|
||||
import clc from "cli-color";
|
||||
import fs from "fs";
|
||||
import readline from "readline";
|
||||
import sdk, { ClientEvent, EventType, MsgType, RoomEvent } from "matrix-js-sdk";
|
||||
import { KnownMembership } from "matrix-js-sdk/lib/@types/membership.js";
|
||||
|
||||
var myHomeServer = "http://localhost:8008";
|
||||
var myUserId = "@example:localhost";
|
||||
var myAccessToken = "QGV4YW1wbGU6bG9jYWxob3N0.qPEvLuYfNBjxikiCjP";
|
||||
var sdk = require("matrix-js-sdk");
|
||||
var clc = require("cli-color");
|
||||
|
||||
var matrixClient = sdk.createClient({
|
||||
baseUrl: "http://localhost:8008",
|
||||
baseUrl: myHomeServer,
|
||||
accessToken: myAccessToken,
|
||||
userId: myUserId,
|
||||
});
|
||||
@@ -15,7 +21,6 @@ var numMessagesToShow = 20;
|
||||
|
||||
// Reading from stdin
|
||||
var CLEAR_CONSOLE = "\x1B[2J";
|
||||
var readline = require("readline");
|
||||
var rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
@@ -97,7 +102,7 @@ rl.on("line", function (line) {
|
||||
})
|
||||
.then(function (url) {
|
||||
var content = {
|
||||
msgtype: "m.file",
|
||||
msgtype: MsgType.File,
|
||||
body: filename,
|
||||
url: JSON.parse(url).content_uri,
|
||||
};
|
||||
@@ -138,7 +143,7 @@ rl.on("line", function (line) {
|
||||
// ==== END User input
|
||||
|
||||
// show the room list after syncing.
|
||||
matrixClient.on("sync", function (state, prevState, data) {
|
||||
matrixClient.on(ClientEvent.Sync, function (state, prevState, data) {
|
||||
switch (state) {
|
||||
case "PREPARED":
|
||||
setRoomList();
|
||||
@@ -149,7 +154,7 @@ matrixClient.on("sync", function (state, prevState, data) {
|
||||
}
|
||||
});
|
||||
|
||||
matrixClient.on("Room", function () {
|
||||
matrixClient.on(ClientEvent.Room, function () {
|
||||
setRoomList();
|
||||
if (!viewingRoom) {
|
||||
printRoomList();
|
||||
@@ -158,7 +163,7 @@ matrixClient.on("Room", function () {
|
||||
});
|
||||
|
||||
// print incoming messages.
|
||||
matrixClient.on("Room.timeline", function (event, room, toStartOfTimeline) {
|
||||
matrixClient.on(RoomEvent.Timeline, function (event, room, toStartOfTimeline) {
|
||||
if (toStartOfTimeline) {
|
||||
return; // don't print paginated results
|
||||
}
|
||||
@@ -305,7 +310,7 @@ function printRoomInfo(room) {
|
||||
print(eTypeHeader + sendHeader + contentHeader);
|
||||
print(new Array(100).join("-"));
|
||||
eventMap.keys().forEach(function (eventType) {
|
||||
if (eventType === "m.room.member") {
|
||||
if (eventType === EventType.RoomMember) {
|
||||
return;
|
||||
} // use /members instead.
|
||||
var eventEventMap = eventMap.get(eventType);
|
||||
@@ -343,7 +348,7 @@ function printLine(event) {
|
||||
name = name.slice(0, maxNameWidth - 1) + "\u2026";
|
||||
}
|
||||
|
||||
if (event.getType() === "m.room.message") {
|
||||
if (event.getType() === EventType.RoomMessage) {
|
||||
body = event.getContent().body;
|
||||
} else if (event.isState()) {
|
||||
var stateName = event.getType();
|
||||
@@ -394,4 +399,4 @@ function fixWidth(str, len) {
|
||||
return str;
|
||||
}
|
||||
|
||||
matrixClient.startClient(numMessagesToShow); // messages for each room.
|
||||
matrixClient.startClient({ initialSyncLimit: numMessagesToShow });
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
"main": "app.js",
|
||||
"author": "",
|
||||
"license": "Apache 2.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"cli-color": "^1.0.0",
|
||||
"matrix-js-sdk": "^32.0.0"
|
||||
"matrix-js-sdk": "^34.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ if (env["GITHUB_ACTIONS"] !== undefined) {
|
||||
|
||||
// if we're running against the develop branch, also enable the slow test reporter
|
||||
if (env["GITHUB_REF"] == "refs/heads/develop") {
|
||||
reporters.push("<rootDir>/spec/slowReporter.js");
|
||||
reporters.push("<rootDir>/spec/slowReporter.cjs");
|
||||
}
|
||||
config.reporters = reporters;
|
||||
}
|
||||
|
||||
+16
-18
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-js-sdk",
|
||||
"version": "34.4.0-rc.0",
|
||||
"version": "34.10.0",
|
||||
"description": "Matrix Client-Server SDK for Javascript",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
@@ -31,13 +31,10 @@
|
||||
"keywords": [
|
||||
"matrix-org"
|
||||
],
|
||||
"type": "module",
|
||||
"main": "./lib/index.js",
|
||||
"browser": "./lib/browser-index.js",
|
||||
"matrix_src_main": "./src/index.ts",
|
||||
"matrix_src_browser": "./src/browser-index.ts",
|
||||
"matrix_lib_main": "./lib/index.js",
|
||||
"matrix_lib_browser": "./lib/browser-index.js",
|
||||
"matrix_lib_typings": "./lib/index.d.ts",
|
||||
"typings": "./lib/index.d.ts",
|
||||
"author": "matrix.org",
|
||||
"license": "Apache-2.0",
|
||||
"files": [
|
||||
@@ -53,7 +50,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@matrix-org/matrix-sdk-crypto-wasm": "^7.0.0",
|
||||
"@matrix-org/matrix-sdk-crypto-wasm": "^9.0.0",
|
||||
"@matrix-org/olm": "3.2.15",
|
||||
"another-json": "^0.2.0",
|
||||
"bs58": "^6.0.0",
|
||||
@@ -84,6 +81,7 @@
|
||||
"@babel/preset-typescript": "^7.12.7",
|
||||
"@casualbot/jest-sonar-reporter": "2.2.7",
|
||||
"@peculiar/webcrypto": "^1.4.5",
|
||||
"@stylistic/eslint-plugin": "^2.9.0",
|
||||
"@types/bs58": "^4.0.1",
|
||||
"@types/content-type": "^1.1.5",
|
||||
"@types/debug": "^4.1.7",
|
||||
@@ -91,22 +89,24 @@
|
||||
"@types/node": "18",
|
||||
"@types/sdp-transform": "^2.4.5",
|
||||
"@types/uuid": "10",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||
"@typescript-eslint/parser": "^7.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
"babel-jest": "^29.0.0",
|
||||
"babel-plugin-search-and-replace": "^1.1.1",
|
||||
"debug": "^4.3.4",
|
||||
"eslint": "8.57.0",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-import-resolver-typescript": "^3.5.1",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jest": "^28.0.0",
|
||||
"eslint-plugin-jsdoc": "^48.0.0",
|
||||
"eslint-plugin-matrix-org": "^1.0.0",
|
||||
"eslint-plugin-jsdoc": "^50.0.0",
|
||||
"eslint-plugin-matrix-org": "^2.0.1",
|
||||
"eslint-plugin-n": "^14.0.0",
|
||||
"eslint-plugin-tsdoc": "^0.3.0",
|
||||
"eslint-plugin-unicorn": "^54.0.0",
|
||||
"eslint-plugin-unicorn": "^56.0.0",
|
||||
"fake-indexeddb": "^5.0.2",
|
||||
"fetch-mock": "10.1.0",
|
||||
"fetch-mock": "11.1.5",
|
||||
"fetch-mock-jest": "^1.5.1",
|
||||
"husky": "^9.0.0",
|
||||
"jest": "^29.0.0",
|
||||
@@ -124,13 +124,11 @@
|
||||
"typedoc-plugin-coverage": "^3.0.0",
|
||||
"typedoc-plugin-mdn-links": "^3.0.3",
|
||||
"typedoc-plugin-missing-exports": "^3.0.0",
|
||||
"typescript": "^5.3.3"
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"@casualbot/jest-sonar-reporter": {
|
||||
"outputDirectory": "coverage",
|
||||
"outputName": "jest-sonar-report.xml",
|
||||
"relativePaths": true
|
||||
},
|
||||
"typings": "./lib/index.d.ts",
|
||||
"type": "module"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,3 @@ do
|
||||
jq ".$i = .matrix_lib_$i" package.json > package.json.new && mv package.json.new package.json && yarn prettier --write package.json
|
||||
fi
|
||||
done
|
||||
|
||||
# Ensure that "type": "module" is present
|
||||
jq '.type = "module"' package.json > package.json.new && mv package.json.new package.json && yarn prettier --write package.json
|
||||
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fsProm = require("fs/promises");
|
||||
|
||||
const PKGJSON = "package.json";
|
||||
|
||||
async function main() {
|
||||
const pkgJson = JSON.parse(await fsProm.readFile(PKGJSON, "utf8"));
|
||||
for (const field of ["main", "typings"]) {
|
||||
if (pkgJson["matrix_lib_" + field] !== undefined) {
|
||||
pkgJson[field] = pkgJson["matrix_lib_" + field];
|
||||
}
|
||||
}
|
||||
|
||||
await fsProm.writeFile(PKGJSON, JSON.stringify(pkgJson, null, 2));
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fsProm = require("fs/promises");
|
||||
|
||||
const PKGJSON = "package.json";
|
||||
|
||||
async function main() {
|
||||
const pkgJson = JSON.parse(await fsProm.readFile(PKGJSON, "utf8"));
|
||||
for (const field of ["main", "typings"]) {
|
||||
if (pkgJson["matrix_lib_" + field] !== undefined) {
|
||||
pkgJson[field] = pkgJson["matrix_lib_" + field];
|
||||
}
|
||||
}
|
||||
|
||||
// matrix-js-sdk is built into ECMAScript modules. Make sure we declare it as such.
|
||||
// See https://nodejs.org/api/packages.html#type.
|
||||
pkgJson["type"] = "module";
|
||||
|
||||
await fsProm.writeFile(PKGJSON, JSON.stringify(pkgJson, null, 2));
|
||||
}
|
||||
|
||||
main();
|
||||
+1
@@ -0,0 +1 @@
|
||||
switch_package_to_release.cjs
|
||||
@@ -21,7 +21,7 @@ import { IDBFactory } from "fake-indexeddb";
|
||||
import { CRYPTO_BACKENDS, InitCrypto, syncPromise } from "../../test-utils/test-utils";
|
||||
import { AuthDict, createClient, CryptoEvent, MatrixClient } from "../../../src";
|
||||
import { mockInitialApiRequests, mockSetupCrossSigningRequests } from "../../test-utils/mockEndpoints";
|
||||
import { encryptAES } from "../../../src/crypto/aes";
|
||||
import encryptAESSecretStorageItem from "../../../src/utils/encryptAESSecretStorageItem.ts";
|
||||
import { CryptoCallbacks, CrossSigningKey } from "../../../src/crypto-api";
|
||||
import { SECRET_STORAGE_ALGORITHM_V1_AES } from "../../../src/secret-storage";
|
||||
import { ISyncResponder, SyncResponder } from "../../test-utils/SyncResponder";
|
||||
@@ -169,17 +169,17 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("cross-signing (%s)", (backend: s
|
||||
mockInitialApiRequests(aliceClient.getHomeserverUrl());
|
||||
|
||||
// Encrypt the private keys and return them in the /sync response as if they are in Secret Storage
|
||||
const masterKey = await encryptAES(
|
||||
const masterKey = await encryptAESSecretStorageItem(
|
||||
MASTER_CROSS_SIGNING_PRIVATE_KEY_BASE64,
|
||||
encryptionKey,
|
||||
"m.cross_signing.master",
|
||||
);
|
||||
const selfSigningKey = await encryptAES(
|
||||
const selfSigningKey = await encryptAESSecretStorageItem(
|
||||
SELF_CROSS_SIGNING_PRIVATE_KEY_BASE64,
|
||||
encryptionKey,
|
||||
"m.cross_signing.self_signing",
|
||||
);
|
||||
const userSigningKey = await encryptAES(
|
||||
const userSigningKey = await encryptAESSecretStorageItem(
|
||||
USER_CROSS_SIGNING_PRIVATE_KEY_BASE64,
|
||||
encryptionKey,
|
||||
"m.cross_signing.user_signing",
|
||||
|
||||
@@ -83,9 +83,12 @@ import {
|
||||
CrossSigningKey,
|
||||
CryptoCallbacks,
|
||||
DecryptionFailureCode,
|
||||
DeviceIsolationMode,
|
||||
EventShieldColour,
|
||||
EventShieldReason,
|
||||
KeyBackupInfo,
|
||||
AllDevicesIsolationMode,
|
||||
OnlySignedDevicesIsolationMode,
|
||||
} from "../../../src/crypto-api";
|
||||
import { E2EKeyResponder } from "../../test-utils/E2EKeyResponder";
|
||||
import { IKeyBackup } from "../../../src/crypto/backup";
|
||||
@@ -746,6 +749,95 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
||||
);
|
||||
});
|
||||
|
||||
describe("IsolationMode decryption tests", () => {
|
||||
newBackendOnly(
|
||||
"OnlySigned mode - fails with an error when cross-signed sender is required but sender is not cross-signed",
|
||||
async () => {
|
||||
const decryptedEvent = await setUpTestAndDecrypt(new OnlySignedDevicesIsolationMode());
|
||||
|
||||
// It will error as an unknown device because we haven't fetched
|
||||
// the sender's device keys.
|
||||
expect(decryptedEvent.isDecryptionFailure()).toBe(true);
|
||||
expect(decryptedEvent.decryptionFailureReason).toEqual(DecryptionFailureCode.UNKNOWN_SENDER_DEVICE);
|
||||
},
|
||||
);
|
||||
|
||||
newBackendOnly(
|
||||
"NoIsolation mode - Decrypts with warning when cross-signed sender is required but sender is not cross-signed",
|
||||
async () => {
|
||||
const decryptedEvent = await setUpTestAndDecrypt(new AllDevicesIsolationMode(false));
|
||||
|
||||
expect(decryptedEvent.isDecryptionFailure()).toBe(false);
|
||||
|
||||
expect(await aliceClient.getCrypto()!.getEncryptionInfoForEvent(decryptedEvent)).toEqual({
|
||||
shieldColour: EventShieldColour.RED,
|
||||
shieldReason: EventShieldReason.UNKNOWN_DEVICE,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
async function setUpTestAndDecrypt(isolationMode: DeviceIsolationMode): Promise<MatrixEvent> {
|
||||
// This tests that a message will not be decrypted if the sender
|
||||
// is not sufficiently trusted according to the selected crypto
|
||||
// mode.
|
||||
//
|
||||
// This test is almost the same as the "Alice receives a megolm
|
||||
// message" test, with the main difference that we set the
|
||||
// crypto mode.
|
||||
expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
|
||||
|
||||
// Start by using Invisible crypto mode
|
||||
aliceClient.getCrypto()!.setDeviceIsolationMode(isolationMode);
|
||||
|
||||
await startClientAndAwaitFirstSync();
|
||||
|
||||
const p2pSession = await createOlmSession(testOlmAccount, keyReceiver);
|
||||
const groupSession = new Olm.OutboundGroupSession();
|
||||
groupSession.create();
|
||||
|
||||
// make the room_key event
|
||||
const roomKeyEncrypted = encryptGroupSessionKey({
|
||||
recipient: aliceClient.getUserId()!,
|
||||
recipientCurve25519Key: keyReceiver.getDeviceKey(),
|
||||
recipientEd25519Key: keyReceiver.getSigningKey(),
|
||||
olmAccount: testOlmAccount,
|
||||
p2pSession: p2pSession,
|
||||
groupSession: groupSession,
|
||||
room_id: ROOM_ID,
|
||||
});
|
||||
|
||||
// encrypt a message with the group session
|
||||
const messageEncrypted = encryptMegolmEvent({
|
||||
senderKey: testSenderKey,
|
||||
groupSession: groupSession,
|
||||
room_id: ROOM_ID,
|
||||
});
|
||||
|
||||
// Alice gets both the events in a single sync
|
||||
const syncResponse = {
|
||||
next_batch: 1,
|
||||
to_device: {
|
||||
events: [roomKeyEncrypted],
|
||||
},
|
||||
rooms: {
|
||||
join: {
|
||||
[ROOM_ID]: { timeline: { events: [messageEncrypted] } },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
syncResponder.sendOrQueueSyncResponse(syncResponse);
|
||||
await syncPromise(aliceClient);
|
||||
|
||||
const room = aliceClient.getRoom(ROOM_ID)!;
|
||||
const event = room.getLiveTimeline().getEvents()[0];
|
||||
expect(event.isEncrypted()).toBe(true);
|
||||
|
||||
// it probably won't be decrypted yet, because it takes a while to process the olm keys
|
||||
return await testUtils.awaitDecryption(event);
|
||||
}
|
||||
});
|
||||
|
||||
it("Decryption fails with Unable to decrypt for other errors", async () => {
|
||||
expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
|
||||
await startClientAndAwaitFirstSync();
|
||||
@@ -3179,15 +3271,13 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
||||
});
|
||||
});
|
||||
|
||||
describe("Check if the cross signing keys are available for a user", () => {
|
||||
describe("User identity", () => {
|
||||
let keyResponder: E2EKeyResponder;
|
||||
beforeEach(async () => {
|
||||
// anything that we don't have a specific matcher for silently returns a 404
|
||||
fetchMock.catch(404);
|
||||
|
||||
const keyResponder = new E2EKeyResponder(aliceClient.getHomeserverUrl());
|
||||
keyResponder = new E2EKeyResponder(aliceClient.getHomeserverUrl());
|
||||
keyResponder.addCrossSigningData(SIGNED_CROSS_SIGNING_KEYS_DATA);
|
||||
keyResponder.addDeviceKeys(SIGNED_TEST_DEVICE_DATA);
|
||||
keyResponder.addKeyReceiver(BOB_TEST_USER_ID, keyReceiver);
|
||||
keyResponder.addKeyReceiver(TEST_USER_ID, keyReceiver);
|
||||
keyResponder.addCrossSigningData(BOB_SIGNED_CROSS_SIGNING_KEYS_DATA);
|
||||
keyResponder.addDeviceKeys(BOB_SIGNED_TEST_DEVICE_DATA);
|
||||
|
||||
@@ -3203,6 +3293,12 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
||||
.getCrypto()!
|
||||
.userHasCrossSigningKeys(BOB_TEST_USER_ID, true);
|
||||
expect(hasCrossSigningKeysForUser).toBe(true);
|
||||
|
||||
const verificationStatus = await aliceClient.getCrypto()!.getUserVerificationStatus(BOB_TEST_USER_ID);
|
||||
expect(verificationStatus.isVerified()).toBe(false);
|
||||
expect(verificationStatus.isCrossSigningVerified()).toBe(false);
|
||||
expect(verificationStatus.wasCrossSigningVerified()).toBe(false);
|
||||
expect(verificationStatus.needsUserApproval).toBe(false);
|
||||
});
|
||||
|
||||
it("Cross signing keys are available for a tracked user", async () => {
|
||||
@@ -3213,11 +3309,60 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
||||
// Alice is the local user and should be tracked !
|
||||
const hasCrossSigningKeysForUser = await aliceClient.getCrypto()!.userHasCrossSigningKeys(TEST_USER_ID);
|
||||
expect(hasCrossSigningKeysForUser).toBe(true);
|
||||
|
||||
const verificationStatus = await aliceClient.getCrypto()!.getUserVerificationStatus(BOB_TEST_USER_ID);
|
||||
expect(verificationStatus.isVerified()).toBe(false);
|
||||
expect(verificationStatus.isCrossSigningVerified()).toBe(false);
|
||||
expect(verificationStatus.wasCrossSigningVerified()).toBe(false);
|
||||
expect(verificationStatus.needsUserApproval).toBe(false);
|
||||
});
|
||||
|
||||
it("Cross signing keys are not available for an unknown user", async () => {
|
||||
const hasCrossSigningKeysForUser = await aliceClient.getCrypto()!.userHasCrossSigningKeys("@unknown:xyz");
|
||||
expect(hasCrossSigningKeysForUser).toBe(false);
|
||||
|
||||
const verificationStatus = await aliceClient.getCrypto()!.getUserVerificationStatus(BOB_TEST_USER_ID);
|
||||
expect(verificationStatus.isVerified()).toBe(false);
|
||||
expect(verificationStatus.isCrossSigningVerified()).toBe(false);
|
||||
expect(verificationStatus.wasCrossSigningVerified()).toBe(false);
|
||||
expect(verificationStatus.needsUserApproval).toBe(false);
|
||||
});
|
||||
|
||||
newBackendOnly("An unverified user changes identity", async () => {
|
||||
// We have to be tracking Bob's keys, which means we need to share a room with him
|
||||
syncResponder.sendOrQueueSyncResponse({
|
||||
...getSyncResponse([BOB_TEST_USER_ID]),
|
||||
device_lists: { changed: [BOB_TEST_USER_ID] },
|
||||
});
|
||||
await syncPromise(aliceClient);
|
||||
|
||||
const hasCrossSigningKeysForUser = await aliceClient.getCrypto()!.userHasCrossSigningKeys(BOB_TEST_USER_ID);
|
||||
expect(hasCrossSigningKeysForUser).toBe(true);
|
||||
|
||||
// Bob changes his cross-signing keys
|
||||
keyResponder.addCrossSigningData(testData.BOB_ALT_SIGNED_CROSS_SIGNING_KEYS_DATA);
|
||||
syncResponder.sendOrQueueSyncResponse({
|
||||
next_batch: "2",
|
||||
device_lists: { changed: [BOB_TEST_USER_ID] },
|
||||
});
|
||||
await syncPromise(aliceClient);
|
||||
|
||||
await aliceClient.getCrypto()!.userHasCrossSigningKeys(BOB_TEST_USER_ID, true);
|
||||
|
||||
{
|
||||
const verificationStatus = await aliceClient.getCrypto()!.getUserVerificationStatus(BOB_TEST_USER_ID);
|
||||
expect(verificationStatus.isVerified()).toBe(false);
|
||||
expect(verificationStatus.isCrossSigningVerified()).toBe(false);
|
||||
expect(verificationStatus.wasCrossSigningVerified()).toBe(false);
|
||||
expect(verificationStatus.needsUserApproval).toBe(true);
|
||||
}
|
||||
|
||||
// Pinning the new identity should clear the needsUserApproval flag.
|
||||
await aliceClient.getCrypto()!.pinCurrentUserIdentity(BOB_TEST_USER_ID);
|
||||
{
|
||||
const verificationStatus = await aliceClient.getCrypto()!.getUserVerificationStatus(BOB_TEST_USER_ID);
|
||||
expect(verificationStatus.needsUserApproval).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3354,7 +3499,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
||||
return client;
|
||||
}
|
||||
|
||||
function mkEncryptionEvent(content: Object) {
|
||||
function mkEncryptionEvent(content: object) {
|
||||
return mkEventCustom({
|
||||
sender: persistentStoreClient.getSafeUserId(),
|
||||
type: "m.room.encryption",
|
||||
@@ -3367,7 +3512,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
||||
*
|
||||
* @param stateEvents - Additional state events for the test room
|
||||
*/
|
||||
function getSyncResponseWithState(stateEvents: Array<Object>) {
|
||||
function getSyncResponseWithState(stateEvents: Array<object>) {
|
||||
const roomResponse = {
|
||||
state: {
|
||||
events: [
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 fetchMock from "fetch-mock-jest";
|
||||
import "fake-indexeddb/auto";
|
||||
import { IDBFactory } from "fake-indexeddb";
|
||||
|
||||
import { CRYPTO_BACKENDS, getSyncResponse, InitCrypto, syncPromise } from "../../test-utils/test-utils";
|
||||
import { createClient, MatrixClient } from "../../../src";
|
||||
import * as testData from "../../test-utils/test-data";
|
||||
import { E2EKeyResponder } from "../../test-utils/E2EKeyResponder";
|
||||
import { SyncResponder } from "../../test-utils/SyncResponder";
|
||||
import { E2EKeyReceiver } from "../../test-utils/E2EKeyReceiver";
|
||||
|
||||
afterEach(() => {
|
||||
// reset fake-indexeddb after each test, to make sure we don't leak connections
|
||||
// cf https://github.com/dumbmatter/fakeIndexedDB#wipingresetting-the-indexeddb-for-a-fresh-state
|
||||
// eslint-disable-next-line no-global-assign
|
||||
indexedDB = new IDBFactory();
|
||||
});
|
||||
|
||||
/**
|
||||
* Integration tests for to-device messages functionality.
|
||||
*
|
||||
* These tests work by intercepting HTTP requests via fetch-mock rather than mocking out bits of the client, so as
|
||||
* to provide the most effective integration tests possible.
|
||||
*/
|
||||
describe.each(Object.entries(CRYPTO_BACKENDS))("to-device-messages (%s)", (backend: string, initCrypto: InitCrypto) => {
|
||||
let aliceClient: MatrixClient;
|
||||
|
||||
/** an object which intercepts `/keys/query` requests on the test homeserver */
|
||||
let e2eKeyResponder: E2EKeyResponder;
|
||||
|
||||
beforeEach(
|
||||
async () => {
|
||||
// anything that we don't have a specific matcher for silently returns a 404
|
||||
fetchMock.catch(404);
|
||||
fetchMock.config.warnOnFallback = false;
|
||||
|
||||
const homeserverUrl = "https://server.com";
|
||||
aliceClient = createClient({
|
||||
baseUrl: homeserverUrl,
|
||||
userId: testData.TEST_USER_ID,
|
||||
accessToken: "akjgkrgjsalice",
|
||||
deviceId: testData.TEST_DEVICE_ID,
|
||||
});
|
||||
|
||||
e2eKeyResponder = new E2EKeyResponder(homeserverUrl);
|
||||
new E2EKeyReceiver(homeserverUrl);
|
||||
const syncResponder = new SyncResponder(homeserverUrl);
|
||||
|
||||
// add bob as known user
|
||||
syncResponder.sendOrQueueSyncResponse(getSyncResponse([testData.BOB_TEST_USER_ID]));
|
||||
|
||||
// Silence warnings from the backup manager
|
||||
fetchMock.getOnce(new URL("/_matrix/client/v3/room_keys/version", homeserverUrl).toString(), {
|
||||
status: 404,
|
||||
body: { errcode: "M_NOT_FOUND" },
|
||||
});
|
||||
|
||||
fetchMock.get(new URL("/_matrix/client/v3/pushrules/", homeserverUrl).toString(), {});
|
||||
fetchMock.get(new URL("/_matrix/client/versions/", homeserverUrl).toString(), {});
|
||||
fetchMock.post(
|
||||
new URL(
|
||||
`/_matrix/client/v3/user/${encodeURIComponent(testData.TEST_USER_ID)}/filter`,
|
||||
homeserverUrl,
|
||||
).toString(),
|
||||
{ filter_id: "fid" },
|
||||
);
|
||||
|
||||
await initCrypto(aliceClient);
|
||||
},
|
||||
/* it can take a while to initialise the crypto library on the first pass, so bump up the timeout. */
|
||||
10000,
|
||||
);
|
||||
|
||||
afterEach(async () => {
|
||||
aliceClient.stopClient();
|
||||
fetchMock.mockReset();
|
||||
});
|
||||
|
||||
describe("encryptToDeviceMessages", () => {
|
||||
it("returns empty batch for device that is not known", async () => {
|
||||
await aliceClient.startClient();
|
||||
|
||||
const toDeviceBatch = await aliceClient
|
||||
.getCrypto()
|
||||
?.encryptToDeviceMessages(
|
||||
"m.test.event",
|
||||
[{ userId: testData.BOB_TEST_USER_ID, deviceId: testData.BOB_TEST_DEVICE_ID }],
|
||||
{
|
||||
some: "content",
|
||||
},
|
||||
);
|
||||
|
||||
expect(toDeviceBatch).toBeDefined();
|
||||
const { batch, eventType } = toDeviceBatch!;
|
||||
expect(eventType).toBe("m.room.encrypted");
|
||||
expect(batch.length).toBe(0);
|
||||
});
|
||||
|
||||
it("returns encrypted batch for known device", async () => {
|
||||
await aliceClient.startClient();
|
||||
e2eKeyResponder.addDeviceKeys(testData.BOB_SIGNED_TEST_DEVICE_DATA);
|
||||
fetchMock.post("express:/_matrix/client/v3/keys/claim", () => ({
|
||||
one_time_keys: testData.BOB_ONE_TIME_KEYS,
|
||||
}));
|
||||
await syncPromise(aliceClient);
|
||||
|
||||
const toDeviceBatch = await aliceClient
|
||||
.getCrypto()
|
||||
?.encryptToDeviceMessages(
|
||||
"m.test.event",
|
||||
[{ userId: testData.BOB_TEST_USER_ID, deviceId: testData.BOB_TEST_DEVICE_ID }],
|
||||
{
|
||||
some: "content",
|
||||
},
|
||||
);
|
||||
|
||||
expect(toDeviceBatch?.batch.length).toBe(1);
|
||||
expect(toDeviceBatch?.eventType).toBe("m.room.encrypted");
|
||||
const { deviceId, payload, userId } = toDeviceBatch!.batch[0];
|
||||
expect(deviceId).toBe(testData.BOB_TEST_DEVICE_ID);
|
||||
expect(userId).toBe(testData.BOB_TEST_USER_ID);
|
||||
expect(payload.algorithm).toBe("m.olm.v1.curve25519-aes-sha2");
|
||||
expect(payload.sender_key).toEqual(expect.any(String));
|
||||
expect(payload.ciphertext).toEqual(
|
||||
expect.objectContaining({
|
||||
[testData.BOB_SIGNED_TEST_DEVICE_DATA.keys[`curve25519:${testData.BOB_TEST_DEVICE_ID}`]]: {
|
||||
body: expect.any(String),
|
||||
type: 0,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// for future: check that bob's device can decrypt the ciphertext?
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -95,6 +95,7 @@ beforeAll(async () => {
|
||||
|
||||
// load the rust library. This can take a few seconds on a slow GH worker.
|
||||
beforeAll(async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const RustSdkCryptoJs = await require("@matrix-org/matrix-sdk-crypto-wasm");
|
||||
await RustSdkCryptoJs.initAsync();
|
||||
}, 10000);
|
||||
|
||||
@@ -267,7 +267,7 @@ describe("MSC4108SignInWithQR", () => {
|
||||
|
||||
it("should abort if device doesn't come up by timeout", async () => {
|
||||
jest.spyOn(global, "setTimeout").mockImplementation((fn) => {
|
||||
(<Function>fn)();
|
||||
fn();
|
||||
// TODO: mock timers properly
|
||||
return -1 as any;
|
||||
});
|
||||
@@ -320,7 +320,7 @@ describe("MSC4108SignInWithQR", () => {
|
||||
|
||||
it("should not send secrets if user cancels", async () => {
|
||||
jest.spyOn(global, "setTimeout").mockImplementation((fn) => {
|
||||
(<Function>fn)();
|
||||
fn();
|
||||
// TODO: mock timers properly
|
||||
return -1 as any;
|
||||
});
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ import { logger } from "../src/logger";
|
||||
|
||||
// try to load the olm library.
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
globalThis.Olm = require("@matrix-org/olm");
|
||||
logger.log("loaded libolm");
|
||||
} catch (e) {
|
||||
|
||||
@@ -27,7 +27,7 @@ export class AccountDataAccumulator {
|
||||
* Will be updated when fetchMock intercepts calls to PUT `/_matrix/client/v3/user/:userId/account_data/`.
|
||||
* Will be used by `sendSyncResponseWithUpdatedAccountData`
|
||||
*/
|
||||
public accountDataEvents: Map<String, any> = new Map();
|
||||
public accountDataEvents: Map<string, any> = new Map();
|
||||
|
||||
/**
|
||||
* Intercept requests to set a particular type of account data.
|
||||
@@ -101,7 +101,7 @@ export class AccountDataAccumulator {
|
||||
})),
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
} catch {
|
||||
// Might fail with "Cannot queue more than one /sync response" if called too often.
|
||||
// It's ok if it fails here, the sync response is cumulative and will contain
|
||||
// the latest account data.
|
||||
|
||||
@@ -101,9 +101,8 @@ export const makeGeolocationPosition = ({
|
||||
}: {
|
||||
timestamp?: number;
|
||||
coords: Partial<GeolocationCoordinates>;
|
||||
}): GeolocationPosition => ({
|
||||
timestamp: timestamp ?? 1647256791840,
|
||||
coords: {
|
||||
}): GeolocationPosition => {
|
||||
const { toJSON, ...coordsJSON } = {
|
||||
accuracy: 1,
|
||||
latitude: 54.001927,
|
||||
longitude: -8.253491,
|
||||
@@ -112,5 +111,16 @@ export const makeGeolocationPosition = ({
|
||||
heading: null,
|
||||
speed: null,
|
||||
...coords,
|
||||
},
|
||||
});
|
||||
};
|
||||
const posJSON = {
|
||||
timestamp: timestamp ?? 1647256791840,
|
||||
coords: {
|
||||
toJSON: () => coordsJSON,
|
||||
...coordsJSON,
|
||||
},
|
||||
};
|
||||
return {
|
||||
toJSON: () => posJSON,
|
||||
...posJSON,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -66,6 +66,7 @@ BOB_DATA = {
|
||||
"TEST_DEVICE_CURVE_PRIVATE_KEY_BYTES": b"Deadmuledeadmuledeadmuledeadmule",
|
||||
|
||||
"MASTER_CROSS_SIGNING_PRIVATE_KEY_BYTES": b"Doyouspeakwhaaaaaaaaaaaaaaaaaale",
|
||||
"ALT_MASTER_CROSS_SIGNING_PRIVATE_KEY_BYTES": b"DoYouSpeakWhaaaaaaaaaaaaaaaaaale",
|
||||
"USER_CROSS_SIGNING_PRIVATE_KEY_BYTES": b"Useruseruseruseruseruseruseruser",
|
||||
"SELF_CROSS_SIGNING_PRIVATE_KEY_BYTES": b"Selfselfselfselfselfselfselfself",
|
||||
|
||||
@@ -208,7 +209,7 @@ def build_test_data(user_data, prefix = "") -> str:
|
||||
|
||||
backup_recovery_key = export_recovery_key(user_data["B64_BACKUP_DECRYPTION_KEY"])
|
||||
|
||||
return f"""\
|
||||
result = f"""\
|
||||
export const {prefix}TEST_USER_ID = "{user_data['TEST_USER_ID']}";
|
||||
export const {prefix}TEST_DEVICE_ID = "{user_data['TEST_DEVICE_ID']}";
|
||||
export const {prefix}TEST_ROOM_ID = "{user_data['TEST_ROOM_ID']}";
|
||||
@@ -239,7 +240,7 @@ export const {prefix}USER_CROSS_SIGNING_PRIVATE_KEY_BASE64 = "{b64_user_signing_
|
||||
|
||||
/** Signed cross-signing keys data, also suitable for returning from a `/keys/query` call */
|
||||
export const {prefix}SIGNED_CROSS_SIGNING_KEYS_DATA: Partial<IDownloadKeyResult> = {
|
||||
json.dumps(build_cross_signing_keys_data(user_data), indent=4)
|
||||
json.dumps(build_cross_signing_keys_data(user_data, user_data["MASTER_CROSS_SIGNING_PRIVATE_KEY_BYTES"]), indent=4)
|
||||
};
|
||||
|
||||
/** Signed OTKs, returned by `POST /keys/claim` */
|
||||
@@ -279,12 +280,20 @@ export const {prefix}CLEAR_EVENT: Partial<IEvent> = {json.dumps(clear_event, ind
|
||||
export const {prefix}ENCRYPTED_EVENT: Partial<IEvent> = {json.dumps(encrypted_event, indent=4)};
|
||||
"""
|
||||
|
||||
alt_master_key = user_data.get("ALT_MASTER_CROSS_SIGNING_PRIVATE_KEY_BYTES")
|
||||
if alt_master_key is not None:
|
||||
result += f"""
|
||||
/** A second set of signed cross-signing keys data, also suitable for returning from a `/keys/query` call */
|
||||
export const {prefix}ALT_SIGNED_CROSS_SIGNING_KEYS_DATA: Partial<IDownloadKeyResult> = {
|
||||
json.dumps(build_cross_signing_keys_data(user_data, alt_master_key), indent=4)
|
||||
};
|
||||
"""
|
||||
|
||||
def build_cross_signing_keys_data(user_data) -> dict:
|
||||
return result
|
||||
|
||||
def build_cross_signing_keys_data(user_data, master_key_bytes) -> dict:
|
||||
"""Build the signed cross-signing-keys data for return from /keys/query"""
|
||||
master_private_key = ed25519.Ed25519PrivateKey.from_private_bytes(
|
||||
user_data["MASTER_CROSS_SIGNING_PRIVATE_KEY_BYTES"]
|
||||
)
|
||||
master_private_key = ed25519.Ed25519PrivateKey.from_private_bytes(master_key_bytes)
|
||||
b64_master_public_key = encode_base64(
|
||||
master_private_key.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)
|
||||
)
|
||||
|
||||
@@ -449,3 +449,50 @@ export const BOB_ENCRYPTED_EVENT: Partial<IEvent> = {
|
||||
"origin_server_ts": 1507753886000
|
||||
};
|
||||
|
||||
/** A second set of signed cross-signing keys data, also suitable for returning from a `/keys/query` call */
|
||||
export const BOB_ALT_SIGNED_CROSS_SIGNING_KEYS_DATA: Partial<IDownloadKeyResult> = {
|
||||
"master_keys": {
|
||||
"@bob:xyz": {
|
||||
"keys": {
|
||||
"ed25519:MCYxU7myKVkoQ55VYw/rXdg5cEupRfDdHmFPJUmR5+E": "MCYxU7myKVkoQ55VYw/rXdg5cEupRfDdHmFPJUmR5+E"
|
||||
},
|
||||
"user_id": "@bob:xyz",
|
||||
"usage": [
|
||||
"master"
|
||||
]
|
||||
}
|
||||
},
|
||||
"self_signing_keys": {
|
||||
"@bob:xyz": {
|
||||
"keys": {
|
||||
"ed25519:DaScI3WulBvDjf/d2vdyP5Cgjdypn1c/PSDX23MgN+A": "DaScI3WulBvDjf/d2vdyP5Cgjdypn1c/PSDX23MgN+A"
|
||||
},
|
||||
"user_id": "@bob:xyz",
|
||||
"usage": [
|
||||
"self_signing"
|
||||
],
|
||||
"signatures": {
|
||||
"@bob:xyz": {
|
||||
"ed25519:MCYxU7myKVkoQ55VYw/rXdg5cEupRfDdHmFPJUmR5+E": "eDZETBRUw9yW0WJnBZ7vxo12TW09Yb7/47qBPKZzPZzZEvs9M82dnAOtWUv00mcTdp2K9GpeFYDQJ6qLQgxaCA"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"user_signing_keys": {
|
||||
"@bob:xyz": {
|
||||
"keys": {
|
||||
"ed25519:lXP89FP6zvFH9TSbU1S8uSdAsVawm1NmV6z+Rfr3lEw": "lXP89FP6zvFH9TSbU1S8uSdAsVawm1NmV6z+Rfr3lEw"
|
||||
},
|
||||
"user_id": "@bob:xyz",
|
||||
"usage": [
|
||||
"user_signing"
|
||||
],
|
||||
"signatures": {
|
||||
"@bob:xyz": {
|
||||
"ed25519:MCYxU7myKVkoQ55VYw/rXdg5cEupRfDdHmFPJUmR5+E": "Q1CbIXvp2BxBsu3F/eZ1ZpuR5rXIt0+FrrA/l6itskpW748xwMoIKxQRVQqs87kh7pCsWEoTy6FzIL8nV+P6BQ"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ export function mock<T>(constr: { new (...args: any[]): T }, name: string): T {
|
||||
if (constr.prototype[key] instanceof Function) {
|
||||
result[key] = jest.fn();
|
||||
}
|
||||
} catch (ex) {
|
||||
} catch {
|
||||
// Direct access to some non-function fields of DOM prototypes may
|
||||
// cause exceptions.
|
||||
// Overwriting will not work either in that case.
|
||||
|
||||
@@ -147,10 +147,13 @@ export class MockRTCPeerConnection {
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.localDescription = {
|
||||
const localDescriptionJSON = {
|
||||
sdp: DUMMY_SDP,
|
||||
type: "offer",
|
||||
toJSON: function () {},
|
||||
type: "offer" as RTCSdpType,
|
||||
};
|
||||
this.localDescription = {
|
||||
toJSON: () => localDescriptionJSON,
|
||||
...localDescriptionJSON,
|
||||
};
|
||||
|
||||
this.readyToNegotiate = new Promise<void>((resolve) => {
|
||||
@@ -265,7 +268,7 @@ export class MockRTCRtpTransceiver {
|
||||
this.peerConn.needsNegotiation = true;
|
||||
}
|
||||
|
||||
public setCodecPreferences = jest.fn<void, RTCRtpCodecCapability[]>();
|
||||
public setCodecPreferences = jest.fn<void, RTCRtpCodec[]>();
|
||||
}
|
||||
|
||||
export class MockMediaStreamTrack {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { defer } from "../../src/utils";
|
||||
|
||||
describe("onResumedSync", () => {
|
||||
let batch: IndexedToDeviceBatch | null;
|
||||
let shouldFailSendToDevice: Boolean;
|
||||
let shouldFailSendToDevice: boolean;
|
||||
let onSendToDeviceFailure: () => void;
|
||||
let onSendToDeviceSuccess: () => void;
|
||||
let resumeSync: (newState: SyncState, oldState: SyncState) => void;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 { keyFromAuthData } from "../../../src/common-crypto/key-passphrase.ts";
|
||||
|
||||
describe("key-passphrase", () => {
|
||||
describe("keyFromAuthData", () => {
|
||||
it("should throw an error if salt or iterations are missing", async () => {
|
||||
// missing salt
|
||||
expect(() => keyFromAuthData({ private_key_iterations: 5 }, "passphrase")).toThrow(
|
||||
"Salt and/or iterations not found: this backup cannot be restored with a passphrase",
|
||||
);
|
||||
|
||||
// missing iterations
|
||||
expect(() => keyFromAuthData({ private_key_salt: "salt" }, "passphrase")).toThrow(
|
||||
"Salt and/or iterations not found: this backup cannot be restored with a passphrase",
|
||||
);
|
||||
});
|
||||
|
||||
it("should derive key from auth data", async () => {
|
||||
const key = await keyFromAuthData({ private_key_salt: "salt", private_key_iterations: 5 }, "passphrase");
|
||||
expect(key).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 { decodeRecoveryKey, encodeRecoveryKey } from "../../../src/crypto-api";
|
||||
|
||||
describe("recovery key", () => {
|
||||
describe("decodeRecoveryKey", () => {
|
||||
it("should thrown an incorrect length error", () => {
|
||||
const key = [0, 1];
|
||||
const encodedKey = encodeRecoveryKey(key)!;
|
||||
|
||||
expect(() => decodeRecoveryKey(encodedKey)).toThrow("Incorrect length");
|
||||
});
|
||||
|
||||
it("should thrown an incorrect parity", () => {
|
||||
const key = Array.from({ length: 32 }, (_, i) => i);
|
||||
let encodedKey = encodeRecoveryKey(key)!;
|
||||
// Mutate the encoded key to have incorrect parity
|
||||
encodedKey = encodedKey.replace("EsSz", "EsSZ");
|
||||
|
||||
expect(() => decodeRecoveryKey(encodedKey!)).toThrow("Incorrect parity");
|
||||
});
|
||||
|
||||
it("should decode a valid encoded key", () => {
|
||||
const key = Array.from({ length: 32 }, (_, i) => i);
|
||||
const encodedKey = encodeRecoveryKey(key)!;
|
||||
|
||||
expect(decodeRecoveryKey(encodedKey)).toEqual(new Uint8Array(key));
|
||||
});
|
||||
});
|
||||
});
|
||||
+113
-1
@@ -26,6 +26,7 @@ import { CryptoBackend } from "../../src/common-crypto/CryptoBackend";
|
||||
import { EventDecryptionResult } from "../../src/common-crypto/CryptoBackend";
|
||||
import * as testData from "../test-utils/test-data";
|
||||
import { KnownMembership } from "../../src/@types/membership";
|
||||
import type { DeviceInfoMap } from "../../src/crypto/DeviceList";
|
||||
|
||||
const Olm = global.Olm;
|
||||
|
||||
@@ -607,7 +608,7 @@ describe("Crypto", function () {
|
||||
event.claimedEd25519Key = null;
|
||||
try {
|
||||
await bobClient.crypto!.decryptEvent(event);
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// we expect this to fail because we don't have the
|
||||
// decryption keys yet
|
||||
}
|
||||
@@ -1245,6 +1246,117 @@ describe("Crypto", function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe("encryptToDeviceMessages", () => {
|
||||
let client: TestClient;
|
||||
let ensureOlmSessionsForDevices: jest.SpiedFunction<typeof olmlib.ensureOlmSessionsForDevices>;
|
||||
let encryptMessageForDevice: jest.SpiedFunction<typeof olmlib.encryptMessageForDevice>;
|
||||
const payload = { hello: "world" };
|
||||
let encryptedPayload: object;
|
||||
let crypto: Crypto;
|
||||
|
||||
beforeEach(async () => {
|
||||
ensureOlmSessionsForDevices = jest.spyOn(olmlib, "ensureOlmSessionsForDevices");
|
||||
ensureOlmSessionsForDevices.mockResolvedValue(new Map());
|
||||
encryptMessageForDevice = jest.spyOn(olmlib, "encryptMessageForDevice");
|
||||
encryptMessageForDevice.mockImplementation(async (...[result, , , , , , payload]) => {
|
||||
result.plaintext = { type: 0, body: JSON.stringify(payload) };
|
||||
});
|
||||
|
||||
client = new TestClient("@alice:example.org", "aliceweb");
|
||||
|
||||
// running initCrypto should trigger a key upload
|
||||
client.httpBackend.when("POST", "/keys/upload").respond(200, {});
|
||||
await Promise.all([client.client.initCrypto(), client.httpBackend.flush("/keys/upload", 1)]);
|
||||
|
||||
encryptedPayload = {
|
||||
algorithm: "m.olm.v1.curve25519-aes-sha2",
|
||||
sender_key: client.client.crypto!.olmDevice.deviceCurve25519Key,
|
||||
ciphertext: { plaintext: { type: 0, body: JSON.stringify(payload) } },
|
||||
};
|
||||
|
||||
crypto = client.client.getCrypto() as Crypto;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
ensureOlmSessionsForDevices.mockRestore();
|
||||
encryptMessageForDevice.mockRestore();
|
||||
await client.stop();
|
||||
});
|
||||
|
||||
it("returns encrypted batch where devices known", async () => {
|
||||
const deviceInfoMap: DeviceInfoMap = new Map([
|
||||
[
|
||||
"@bob:example.org",
|
||||
new Map([
|
||||
["bobweb", new DeviceInfo("bobweb")],
|
||||
["bobmobile", new DeviceInfo("bobmobile")],
|
||||
]),
|
||||
],
|
||||
["@carol:example.org", new Map([["caroldesktop", new DeviceInfo("caroldesktop")]])],
|
||||
]);
|
||||
jest.spyOn(crypto.deviceList, "downloadKeys").mockResolvedValue(deviceInfoMap);
|
||||
// const deviceInfoMap = await this.downloadKeys(Array.from(userIds), false);
|
||||
|
||||
const batch = await client.client.getCrypto()?.encryptToDeviceMessages(
|
||||
"m.test.type",
|
||||
[
|
||||
{ userId: "@bob:example.org", deviceId: "bobweb" },
|
||||
{ userId: "@bob:example.org", deviceId: "bobmobile" },
|
||||
{ userId: "@carol:example.org", deviceId: "caroldesktop" },
|
||||
{ userId: "@carol:example.org", deviceId: "carolmobile" }, // not known
|
||||
],
|
||||
payload,
|
||||
);
|
||||
expect(crypto.deviceList.downloadKeys).toHaveBeenCalledWith(
|
||||
["@bob:example.org", "@carol:example.org"],
|
||||
false,
|
||||
);
|
||||
expect(encryptMessageForDevice).toHaveBeenCalledTimes(3);
|
||||
const expectedPayload = expect.objectContaining({
|
||||
...encryptedPayload,
|
||||
"org.matrix.msgid": expect.any(String),
|
||||
"sender_key": expect.any(String),
|
||||
});
|
||||
expect(batch?.eventType).toEqual("m.room.encrypted");
|
||||
expect(batch?.batch.length).toEqual(3);
|
||||
expect(batch).toEqual({
|
||||
eventType: "m.room.encrypted",
|
||||
batch: expect.arrayContaining([
|
||||
{
|
||||
userId: "@bob:example.org",
|
||||
deviceId: "bobweb",
|
||||
payload: expectedPayload,
|
||||
},
|
||||
{
|
||||
userId: "@bob:example.org",
|
||||
deviceId: "bobmobile",
|
||||
payload: expectedPayload,
|
||||
},
|
||||
{
|
||||
userId: "@carol:example.org",
|
||||
deviceId: "caroldesktop",
|
||||
payload: expectedPayload,
|
||||
},
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
it("returns empty batch if no devices known", async () => {
|
||||
jest.spyOn(crypto.deviceList, "downloadKeys").mockResolvedValue(new Map());
|
||||
const batch = await crypto.encryptToDeviceMessages(
|
||||
"m.test.type",
|
||||
[
|
||||
{ deviceId: "AAA", userId: "@user1:domain" },
|
||||
{ deviceId: "BBB", userId: "@user1:domain" },
|
||||
{ deviceId: "CCC", userId: "@user2:domain" },
|
||||
],
|
||||
payload,
|
||||
);
|
||||
expect(batch?.eventType).toEqual("m.room.encrypted");
|
||||
expect(batch?.batch).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkSecretStoragePrivateKey", () => {
|
||||
let client: TestClient;
|
||||
|
||||
|
||||
@@ -140,9 +140,7 @@ describe("Cross Signing", function () {
|
||||
});
|
||||
}
|
||||
|
||||
const error = new MatrixError(errorResponse);
|
||||
error.httpStatus == 401;
|
||||
throw error;
|
||||
throw new MatrixError(errorResponse, 401);
|
||||
};
|
||||
alice.uploadKeySignatures = async () => ({ failures: {} });
|
||||
alice.setAccountData = async () => ({});
|
||||
@@ -827,7 +825,7 @@ describe("Cross Signing", function () {
|
||||
});
|
||||
|
||||
it("should offer to upgrade device verifications to cross-signing", async function () {
|
||||
let upgradeResolveFunc: Function;
|
||||
let upgradeResolveFunc: () => void;
|
||||
|
||||
const { client: alice } = await makeTestClient(
|
||||
{ userId: "@alice:example.com", deviceId: "Osborne2" },
|
||||
@@ -866,7 +864,7 @@ describe("Cross Signing", function () {
|
||||
// cross-signing key is signed by his Dynabook, which alice has
|
||||
// verified, and ask if the device verification should be upgraded to a
|
||||
// cross-signing verification
|
||||
let upgradePromise = new Promise((resolve) => {
|
||||
let upgradePromise = new Promise<void>((resolve) => {
|
||||
upgradeResolveFunc = resolve;
|
||||
});
|
||||
await resetCrossSigningKeys(alice);
|
||||
@@ -885,7 +883,7 @@ describe("Cross Signing", function () {
|
||||
expect(bobTrust2.isCrossSigningVerified()).toBeFalsy();
|
||||
expect(bobTrust2.isTofu()).toBeTruthy();
|
||||
|
||||
upgradePromise = new Promise((resolve) => {
|
||||
upgradePromise = new Promise<void>((resolve) => {
|
||||
upgradeResolveFunc = resolve;
|
||||
});
|
||||
alice.crypto!.deviceList.emit(CryptoEvent.UserCrossSigningUpdated, "@bob:example.com");
|
||||
|
||||
@@ -20,7 +20,7 @@ import { IObject } from "../../../src/crypto/olmlib";
|
||||
import { MatrixEvent } from "../../../src/models/event";
|
||||
import { TestClient } from "../../TestClient";
|
||||
import { makeTestClients } from "./verification/util";
|
||||
import { encryptAES } from "../../../src/crypto/aes";
|
||||
import encryptAESSecretStorageItem from "../../../src/utils/encryptAESSecretStorageItem.ts";
|
||||
import { createSecretStorageKey, resetCrossSigningKeys } from "./crypto-utils";
|
||||
import { logger } from "../../../src/logger";
|
||||
import { ClientEvent, ICreateClientOpts, MatrixClient } from "../../../src/client";
|
||||
@@ -612,7 +612,7 @@ describe("Secrets", function () {
|
||||
type: "m.megolm_backup.v1",
|
||||
content: {
|
||||
encrypted: {
|
||||
key_id: await encryptAES(
|
||||
key_id: await encryptAESSecretStorageItem(
|
||||
"123,45,6,7,89,1,234,56,78,90,12,34,5,67,8,90",
|
||||
secretStorageKeys.key_id,
|
||||
"m.megolm_backup.v1",
|
||||
|
||||
@@ -454,7 +454,7 @@ describe("SAS verification", function () {
|
||||
let bobSasEvent: ISasEvent | null;
|
||||
let aliceVerifier: SAS;
|
||||
let bobPromise: Promise<void>;
|
||||
let clearTestClientTimeouts: Function;
|
||||
let clearTestClientTimeouts: () => void;
|
||||
|
||||
beforeEach(async function () {
|
||||
[[alice, bob], clearTestClientTimeouts] = await makeTestClients(
|
||||
|
||||
+129
-11
@@ -102,7 +102,7 @@ type HttpLookup = {
|
||||
error?: object;
|
||||
expectBody?: Record<string, any>;
|
||||
expectQueryParams?: QueryDict;
|
||||
thenCall?: Function;
|
||||
thenCall?: () => void;
|
||||
};
|
||||
|
||||
interface Options extends ICreateRoomOpts {
|
||||
@@ -403,7 +403,7 @@ describe("MatrixClient", function () {
|
||||
async function assertRequestsMade(
|
||||
responses: {
|
||||
prefix?: string;
|
||||
error?: { httpStatus: Number; errcode: string };
|
||||
error?: { httpStatus: number; errcode: string };
|
||||
data?: { event_id: string };
|
||||
}[],
|
||||
expectRejects = false,
|
||||
@@ -1029,6 +1029,124 @@ describe("MatrixClient", function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe("extended profiles", () => {
|
||||
const unstableMSC4133Prefix = `${ClientPrefix.Unstable}/uk.tcpip.msc4133`;
|
||||
const userId = "@profile_user:example.org";
|
||||
|
||||
beforeEach(() => {
|
||||
unstableFeatures["uk.tcpip.msc4133"] = true;
|
||||
});
|
||||
|
||||
it("throws when unsupported by server", async () => {
|
||||
unstableFeatures["uk.tcpip.msc4133"] = false;
|
||||
const errorMessage = "Server does not support extended profiles";
|
||||
|
||||
await expect(client.doesServerSupportExtendedProfiles()).resolves.toEqual(false);
|
||||
|
||||
await expect(client.getExtendedProfile(userId)).rejects.toThrow(errorMessage);
|
||||
await expect(client.getExtendedProfileProperty(userId, "test_key")).rejects.toThrow(errorMessage);
|
||||
await expect(client.setExtendedProfileProperty("test_key", "foo")).rejects.toThrow(errorMessage);
|
||||
await expect(client.deleteExtendedProfileProperty("test_key")).rejects.toThrow(errorMessage);
|
||||
await expect(client.patchExtendedProfile({ test_key: "foo" })).rejects.toThrow(errorMessage);
|
||||
await expect(client.setExtendedProfile({ test_key: "foo" })).rejects.toThrow(errorMessage);
|
||||
});
|
||||
|
||||
it("can fetch a extended user profile", async () => {
|
||||
const testProfile = {
|
||||
test_key: "foo",
|
||||
};
|
||||
httpLookups = [
|
||||
{
|
||||
method: "GET",
|
||||
prefix: unstableMSC4133Prefix,
|
||||
path: "/profile/" + encodeURIComponent(userId),
|
||||
data: testProfile,
|
||||
},
|
||||
];
|
||||
await expect(client.getExtendedProfile(userId)).resolves.toEqual(testProfile);
|
||||
expect(httpLookups).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("can fetch a property from a extended user profile", async () => {
|
||||
const testProfile = {
|
||||
test_key: "foo",
|
||||
};
|
||||
httpLookups = [
|
||||
{
|
||||
method: "GET",
|
||||
prefix: unstableMSC4133Prefix,
|
||||
path: "/profile/" + encodeURIComponent(userId) + "/test_key",
|
||||
data: testProfile,
|
||||
},
|
||||
];
|
||||
await expect(client.getExtendedProfileProperty(userId, "test_key")).resolves.toEqual("foo");
|
||||
expect(httpLookups).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("can set a property in our extended profile", async () => {
|
||||
httpLookups = [
|
||||
{
|
||||
method: "PUT",
|
||||
prefix: unstableMSC4133Prefix,
|
||||
path: "/profile/" + encodeURIComponent(client.credentials.userId!) + "/test_key",
|
||||
expectBody: {
|
||||
test_key: "foo",
|
||||
},
|
||||
},
|
||||
];
|
||||
await expect(client.setExtendedProfileProperty("test_key", "foo")).resolves.toEqual(undefined);
|
||||
expect(httpLookups).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("can delete a property in our extended profile", async () => {
|
||||
httpLookups = [
|
||||
{
|
||||
method: "DELETE",
|
||||
prefix: unstableMSC4133Prefix,
|
||||
path: "/profile/" + encodeURIComponent(client.credentials.userId!) + "/test_key",
|
||||
},
|
||||
];
|
||||
await expect(client.deleteExtendedProfileProperty("test_key")).resolves.toEqual(undefined);
|
||||
expect(httpLookups).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("can patch our extended profile", async () => {
|
||||
const testProfile = {
|
||||
test_key: "foo",
|
||||
};
|
||||
const patchedProfile = {
|
||||
existing: "key",
|
||||
test_key: "foo",
|
||||
};
|
||||
httpLookups = [
|
||||
{
|
||||
method: "PATCH",
|
||||
prefix: unstableMSC4133Prefix,
|
||||
path: "/profile/" + encodeURIComponent(client.credentials.userId!),
|
||||
data: patchedProfile,
|
||||
expectBody: testProfile,
|
||||
},
|
||||
];
|
||||
await expect(client.patchExtendedProfile(testProfile)).resolves.toEqual(patchedProfile);
|
||||
});
|
||||
|
||||
it("can replace our extended profile", async () => {
|
||||
const testProfile = {
|
||||
test_key: "foo",
|
||||
};
|
||||
httpLookups = [
|
||||
{
|
||||
method: "PUT",
|
||||
prefix: unstableMSC4133Prefix,
|
||||
path: "/profile/" + encodeURIComponent(client.credentials.userId!),
|
||||
data: testProfile,
|
||||
expectBody: testProfile,
|
||||
},
|
||||
];
|
||||
await expect(client.setExtendedProfile(testProfile)).resolves.toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
it("should create (unstable) file trees", async () => {
|
||||
const userId = "@test:example.org";
|
||||
const roomId = "!room:example.org";
|
||||
@@ -1397,7 +1515,7 @@ describe("MatrixClient", function () {
|
||||
});
|
||||
|
||||
describe("emitted sync events", function () {
|
||||
function syncChecker(expectedStates: [string, string | null][], done: Function) {
|
||||
function syncChecker(expectedStates: [string, string | null][], done: () => void) {
|
||||
return function syncListener(state: SyncState, old: SyncState | null) {
|
||||
const expected = expectedStates.shift();
|
||||
logger.log("'sync' curr=%s old=%s EXPECT=%s", state, old, expected);
|
||||
@@ -1419,7 +1537,7 @@ describe("MatrixClient", function () {
|
||||
it("should transition null -> PREPARED after the first /sync", async () => {
|
||||
const expectedStates: [string, string | null][] = [];
|
||||
expectedStates.push(["PREPARED", null]);
|
||||
const didSyncPromise = new Promise((resolve) => {
|
||||
const didSyncPromise = new Promise<void>((resolve) => {
|
||||
client.on(ClientEvent.Sync, syncChecker(expectedStates, resolve));
|
||||
});
|
||||
await client.startClient();
|
||||
@@ -1436,7 +1554,7 @@ describe("MatrixClient", function () {
|
||||
error: { errcode: "NOPE_NOPE_NOPE" },
|
||||
});
|
||||
expectedStates.push(["ERROR", null]);
|
||||
const didSyncPromise = new Promise((resolve) => {
|
||||
const didSyncPromise = new Promise<void>((resolve) => {
|
||||
client.on(ClientEvent.Sync, syncChecker(expectedStates, resolve));
|
||||
});
|
||||
await client.startClient();
|
||||
@@ -1476,7 +1594,7 @@ describe("MatrixClient", function () {
|
||||
expectedStates.push(["RECONNECTING", null]);
|
||||
expectedStates.push(["ERROR", "RECONNECTING"]);
|
||||
expectedStates.push(["CATCHUP", "ERROR"]);
|
||||
const didSyncPromise = new Promise((resolve) => {
|
||||
const didSyncPromise = new Promise<void>((resolve) => {
|
||||
client.on(ClientEvent.Sync, syncChecker(expectedStates, resolve));
|
||||
});
|
||||
await client.startClient();
|
||||
@@ -1487,7 +1605,7 @@ describe("MatrixClient", function () {
|
||||
const expectedStates: [string, string | null][] = [];
|
||||
expectedStates.push(["PREPARED", null]);
|
||||
expectedStates.push(["SYNCING", "PREPARED"]);
|
||||
const didSyncPromise = new Promise((resolve) => {
|
||||
const didSyncPromise = new Promise<void>((resolve) => {
|
||||
client.on(ClientEvent.Sync, syncChecker(expectedStates, resolve));
|
||||
});
|
||||
await client.startClient();
|
||||
@@ -1512,7 +1630,7 @@ describe("MatrixClient", function () {
|
||||
expectedStates.push(["SYNCING", "PREPARED"]);
|
||||
expectedStates.push(["RECONNECTING", "SYNCING"]);
|
||||
expectedStates.push(["ERROR", "RECONNECTING"]);
|
||||
const didSyncPromise = new Promise((resolve) => {
|
||||
const didSyncPromise = new Promise<void>((resolve) => {
|
||||
client.on(ClientEvent.Sync, syncChecker(expectedStates, resolve));
|
||||
});
|
||||
await client.startClient();
|
||||
@@ -1531,7 +1649,7 @@ describe("MatrixClient", function () {
|
||||
expectedStates.push(["PREPARED", null]);
|
||||
expectedStates.push(["SYNCING", "PREPARED"]);
|
||||
expectedStates.push(["ERROR", "SYNCING"]);
|
||||
const didSyncPromise = new Promise((resolve) => {
|
||||
const didSyncPromise = new Promise<void>((resolve) => {
|
||||
client.on(ClientEvent.Sync, syncChecker(expectedStates, resolve));
|
||||
});
|
||||
await client.startClient();
|
||||
@@ -1546,7 +1664,7 @@ describe("MatrixClient", function () {
|
||||
expectedStates.push(["PREPARED", null]);
|
||||
expectedStates.push(["SYNCING", "PREPARED"]);
|
||||
expectedStates.push(["SYNCING", "SYNCING"]);
|
||||
const didSyncPromise = new Promise((resolve) => {
|
||||
const didSyncPromise = new Promise<void>((resolve) => {
|
||||
client.on(ClientEvent.Sync, syncChecker(expectedStates, resolve));
|
||||
});
|
||||
await client.startClient();
|
||||
@@ -1577,7 +1695,7 @@ describe("MatrixClient", function () {
|
||||
expectedStates.push(["RECONNECTING", "SYNCING"]);
|
||||
expectedStates.push(["ERROR", "RECONNECTING"]);
|
||||
expectedStates.push(["ERROR", "ERROR"]);
|
||||
const didSyncPromise = new Promise((resolve) => {
|
||||
const didSyncPromise = new Promise<void>((resolve) => {
|
||||
client.on(ClientEvent.Sync, syncChecker(expectedStates, resolve));
|
||||
});
|
||||
await client.startClient();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -107,7 +107,7 @@ describe("MSC3089TreeSpace", () => {
|
||||
return Promise.resolve();
|
||||
});
|
||||
client.invite = fn;
|
||||
await tree.invite(target, false, false);
|
||||
await tree.invite(target, false);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -120,7 +120,7 @@ describe("MSC3089TreeSpace", () => {
|
||||
return Promise.resolve();
|
||||
});
|
||||
client.invite = fn;
|
||||
await tree.invite(target, false, false);
|
||||
await tree.invite(target, false);
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
@@ -133,7 +133,7 @@ describe("MSC3089TreeSpace", () => {
|
||||
});
|
||||
client.invite = fn;
|
||||
|
||||
await expect(tree.invite(target, false, false)).rejects.toThrow("MatrixError: Sample Failure");
|
||||
await expect(tree.invite(target, false)).rejects.toThrow("MatrixError: Sample Failure");
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -155,61 +155,10 @@ describe("MSC3089TreeSpace", () => {
|
||||
{ invite: (userId) => fn(tree.roomId, userId) } as MSC3089TreeSpace,
|
||||
];
|
||||
|
||||
await tree.invite(target, true, false);
|
||||
await tree.invite(target, true);
|
||||
expect(fn).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
it("should share keys with invitees", async () => {
|
||||
const target = targetUser;
|
||||
const sendKeysFn = jest.fn().mockImplementation((inviteRoomId: string, userIds: string[]) => {
|
||||
expect(inviteRoomId).toEqual(roomId);
|
||||
expect(userIds).toMatchObject([target]);
|
||||
return Promise.resolve();
|
||||
});
|
||||
client.invite = () => Promise.resolve({}); // we're not testing this here - see other tests
|
||||
client.sendSharedHistoryKeys = sendKeysFn;
|
||||
|
||||
// Mock the history check as best as possible
|
||||
const historyVis = "shared";
|
||||
const historyFn = jest.fn().mockImplementation((eventType: string, stateKey?: string) => {
|
||||
// We're not expecting a super rigid test: the function that calls this internally isn't
|
||||
// really being tested here.
|
||||
expect(eventType).toEqual(EventType.RoomHistoryVisibility);
|
||||
expect(stateKey).toEqual("");
|
||||
return { getContent: () => ({ history_visibility: historyVis }) }; // eslint-disable-line camelcase
|
||||
});
|
||||
room.currentState.getStateEvents = historyFn;
|
||||
|
||||
// Note: inverse test is implicit from other tests, which disable the call stack of this
|
||||
// test in order to pass.
|
||||
await tree.invite(target, false, true);
|
||||
expect(sendKeysFn).toHaveBeenCalledTimes(1);
|
||||
expect(historyFn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should not share keys with invitees if inappropriate history visibility", async () => {
|
||||
const target = targetUser;
|
||||
const sendKeysFn = jest.fn().mockImplementation((inviteRoomId: string, userIds: string[]) => {
|
||||
expect(inviteRoomId).toEqual(roomId);
|
||||
expect(userIds).toMatchObject([target]);
|
||||
return Promise.resolve();
|
||||
});
|
||||
client.invite = () => Promise.resolve({}); // we're not testing this here - see other tests
|
||||
client.sendSharedHistoryKeys = sendKeysFn;
|
||||
|
||||
const historyVis = "joined"; // NOTE: Changed.
|
||||
const historyFn = jest.fn().mockImplementation((eventType: string, stateKey?: string) => {
|
||||
expect(eventType).toEqual(EventType.RoomHistoryVisibility);
|
||||
expect(stateKey).toEqual("");
|
||||
return { getContent: () => ({ history_visibility: historyVis }) }; // eslint-disable-line camelcase
|
||||
});
|
||||
room.currentState.getStateEvents = historyFn;
|
||||
|
||||
await tree.invite(target, false, true);
|
||||
expect(sendKeysFn).toHaveBeenCalledTimes(0);
|
||||
expect(historyFn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
async function evaluatePowerLevels(pls: any, role: TreePermissions, expectedPl: number) {
|
||||
makePowerLevels(pls);
|
||||
const fn = jest
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 "../../olm-loader";
|
||||
import { LegacyRendezvousFailureReason as RendezvousFailureReason, RendezvousIntent } from "../../../src/rendezvous";
|
||||
import { MSC3903ECDHPayload, MSC3903ECDHv2RendezvousChannel } from "../../../src/rendezvous/channels";
|
||||
import { decodeBase64 } from "../../../src/base64";
|
||||
import { DummyTransport } from "./DummyTransport";
|
||||
|
||||
function makeTransport(name: string) {
|
||||
return new DummyTransport<any, MSC3903ECDHPayload>(name, { type: "dummy" });
|
||||
}
|
||||
|
||||
describe("ECDHv2", function () {
|
||||
beforeAll(async function () {
|
||||
await global.Olm.init();
|
||||
});
|
||||
|
||||
describe("with crypto", () => {
|
||||
it("initiator wants to sign in", async function () {
|
||||
const aliceTransport = makeTransport("Alice");
|
||||
const bobTransport = makeTransport("Bob");
|
||||
aliceTransport.otherParty = bobTransport;
|
||||
bobTransport.otherParty = aliceTransport;
|
||||
|
||||
// alice is signing in initiates and generates a code
|
||||
const alice = new MSC3903ECDHv2RendezvousChannel(aliceTransport);
|
||||
const aliceCode = await alice.generateCode(RendezvousIntent.LOGIN_ON_NEW_DEVICE);
|
||||
const bob = new MSC3903ECDHv2RendezvousChannel(bobTransport, decodeBase64(aliceCode.rendezvous.key));
|
||||
|
||||
const bobChecksum = await bob.connect();
|
||||
const aliceChecksum = await alice.connect();
|
||||
|
||||
expect(aliceChecksum).toEqual(bobChecksum);
|
||||
|
||||
const message = { key: "xxx" };
|
||||
await alice.send(message);
|
||||
const bobReceive = await bob.receive();
|
||||
expect(bobReceive).toEqual(message);
|
||||
|
||||
await alice.cancel(RendezvousFailureReason.Unknown);
|
||||
await bob.cancel(RendezvousFailureReason.Unknown);
|
||||
});
|
||||
|
||||
it("initiator wants to reciprocate", async function () {
|
||||
const aliceTransport = makeTransport("Alice");
|
||||
const bobTransport = makeTransport("Bob");
|
||||
aliceTransport.otherParty = bobTransport;
|
||||
bobTransport.otherParty = aliceTransport;
|
||||
|
||||
// alice is signing in initiates and generates a code
|
||||
const alice = new MSC3903ECDHv2RendezvousChannel(aliceTransport);
|
||||
const aliceCode = await alice.generateCode(RendezvousIntent.LOGIN_ON_NEW_DEVICE);
|
||||
const bob = new MSC3903ECDHv2RendezvousChannel(bobTransport, decodeBase64(aliceCode.rendezvous.key));
|
||||
|
||||
const bobChecksum = await bob.connect();
|
||||
const aliceChecksum = await alice.connect();
|
||||
|
||||
expect(aliceChecksum).toEqual(bobChecksum);
|
||||
|
||||
const message = { key: "xxx" };
|
||||
await bob.send(message);
|
||||
const aliceReceive = await alice.receive();
|
||||
expect(aliceReceive).toEqual(message);
|
||||
|
||||
await alice.cancel(RendezvousFailureReason.Unknown);
|
||||
await bob.cancel(RendezvousFailureReason.Unknown);
|
||||
});
|
||||
|
||||
it("double connect", async function () {
|
||||
const aliceTransport = makeTransport("Alice");
|
||||
const bobTransport = makeTransport("Bob");
|
||||
aliceTransport.otherParty = bobTransport;
|
||||
bobTransport.otherParty = aliceTransport;
|
||||
|
||||
// alice is signing in initiates and generates a code
|
||||
const alice = new MSC3903ECDHv2RendezvousChannel(aliceTransport);
|
||||
const aliceCode = await alice.generateCode(RendezvousIntent.LOGIN_ON_NEW_DEVICE);
|
||||
const bob = new MSC3903ECDHv2RendezvousChannel(bobTransport, decodeBase64(aliceCode.rendezvous.key));
|
||||
|
||||
const bobChecksum = await bob.connect();
|
||||
const aliceChecksum = await alice.connect();
|
||||
|
||||
expect(aliceChecksum).toEqual(bobChecksum);
|
||||
|
||||
await expect(alice.connect()).rejects.toThrow();
|
||||
|
||||
await alice.cancel(RendezvousFailureReason.Unknown);
|
||||
await bob.cancel(RendezvousFailureReason.Unknown);
|
||||
});
|
||||
|
||||
it("closed", async function () {
|
||||
const aliceTransport = makeTransport("Alice");
|
||||
const bobTransport = makeTransport("Bob");
|
||||
aliceTransport.otherParty = bobTransport;
|
||||
bobTransport.otherParty = aliceTransport;
|
||||
|
||||
// alice is signing in initiates and generates a code
|
||||
const alice = new MSC3903ECDHv2RendezvousChannel(aliceTransport);
|
||||
const aliceCode = await alice.generateCode(RendezvousIntent.LOGIN_ON_NEW_DEVICE);
|
||||
const bob = new MSC3903ECDHv2RendezvousChannel(bobTransport, decodeBase64(aliceCode.rendezvous.key));
|
||||
|
||||
const bobChecksum = await bob.connect();
|
||||
const aliceChecksum = await alice.connect();
|
||||
|
||||
expect(aliceChecksum).toEqual(bobChecksum);
|
||||
|
||||
alice.close();
|
||||
|
||||
await expect(alice.connect()).rejects.toThrow();
|
||||
await expect(alice.send({})).rejects.toThrow();
|
||||
await expect(alice.receive()).rejects.toThrow();
|
||||
|
||||
await alice.cancel(RendezvousFailureReason.Unknown);
|
||||
await bob.cancel(RendezvousFailureReason.Unknown);
|
||||
});
|
||||
|
||||
it("require ciphertext", async function () {
|
||||
const aliceTransport = makeTransport("Alice");
|
||||
const bobTransport = makeTransport("Bob");
|
||||
aliceTransport.otherParty = bobTransport;
|
||||
bobTransport.otherParty = aliceTransport;
|
||||
|
||||
// alice is signing in initiates and generates a code
|
||||
const alice = new MSC3903ECDHv2RendezvousChannel(aliceTransport);
|
||||
const aliceCode = await alice.generateCode(RendezvousIntent.LOGIN_ON_NEW_DEVICE);
|
||||
const bob = new MSC3903ECDHv2RendezvousChannel(bobTransport, decodeBase64(aliceCode.rendezvous.key));
|
||||
|
||||
const bobChecksum = await bob.connect();
|
||||
const aliceChecksum = await alice.connect();
|
||||
|
||||
expect(aliceChecksum).toEqual(bobChecksum);
|
||||
|
||||
// send a message without encryption
|
||||
await aliceTransport.send({ iv: "dummy", ciphertext: "dummy" });
|
||||
|
||||
await alice.cancel(RendezvousFailureReason.Unknown);
|
||||
await bob.cancel(RendezvousFailureReason.Unknown);
|
||||
await expect(bob.receive()).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("ciphertext before set up", async function () {
|
||||
const aliceTransport = makeTransport("Alice");
|
||||
const bobTransport = makeTransport("Bob");
|
||||
aliceTransport.otherParty = bobTransport;
|
||||
bobTransport.otherParty = aliceTransport;
|
||||
|
||||
// alice is signing in initiates and generates a code
|
||||
const alice = new MSC3903ECDHv2RendezvousChannel(aliceTransport);
|
||||
await alice.generateCode(RendezvousIntent.LOGIN_ON_NEW_DEVICE);
|
||||
|
||||
await bobTransport.send({ iv: "dummy", ciphertext: "dummy" });
|
||||
|
||||
await alice.cancel(RendezvousFailureReason.Unknown);
|
||||
await expect(alice.receive()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,712 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 MockHttpBackend from "matrix-mock-request";
|
||||
|
||||
import "../../olm-loader";
|
||||
import {
|
||||
MSC3906Rendezvous,
|
||||
RendezvousCode,
|
||||
LegacyRendezvousFailureReason as RendezvousFailureReason,
|
||||
RendezvousIntent,
|
||||
} from "../../../src/rendezvous";
|
||||
import {
|
||||
ECDHv2RendezvousCode as ECDHRendezvousCode,
|
||||
MSC3903ECDHPayload,
|
||||
MSC3903ECDHv2RendezvousChannel as MSC3903ECDHRendezvousChannel,
|
||||
} from "../../../src/rendezvous/channels";
|
||||
import { Device, MatrixClient } from "../../../src";
|
||||
import {
|
||||
MSC3886SimpleHttpRendezvousTransport,
|
||||
MSC3886SimpleHttpRendezvousTransportDetails,
|
||||
} from "../../../src/rendezvous/transports";
|
||||
import { DummyTransport } from "./DummyTransport";
|
||||
import { decodeBase64 } from "../../../src/base64";
|
||||
import { logger } from "../../../src/logger";
|
||||
import { CrossSigningKey, OwnDeviceKeys } from "../../../src/crypto-api";
|
||||
|
||||
type UserID = string;
|
||||
type DeviceID = string;
|
||||
type Fingerprint = string;
|
||||
type SimpleDeviceMap = Record<UserID, Record<DeviceID, Fingerprint>>;
|
||||
|
||||
function mockDevice(userId: UserID, deviceId: DeviceID, fingerprint: Fingerprint): Device {
|
||||
return {
|
||||
deviceId,
|
||||
userId,
|
||||
getFingerprint: () => fingerprint,
|
||||
} as unknown as Device;
|
||||
}
|
||||
|
||||
function mockDeviceMap(
|
||||
userId: UserID,
|
||||
deviceId: DeviceID,
|
||||
deviceKey?: Fingerprint,
|
||||
otherDevices: SimpleDeviceMap = {},
|
||||
): Map<string, Map<string, Device>> {
|
||||
const deviceMap: Map<string, Map<string, Device>> = new Map();
|
||||
|
||||
const myDevices: Map<string, Device> = new Map();
|
||||
if (deviceKey) {
|
||||
myDevices.set(deviceId, mockDevice(userId, deviceId, deviceKey));
|
||||
}
|
||||
deviceMap.set(userId, myDevices);
|
||||
|
||||
for (const u in otherDevices) {
|
||||
let userDevices = deviceMap.get(u);
|
||||
if (!userDevices) {
|
||||
userDevices = new Map();
|
||||
deviceMap.set(u, userDevices);
|
||||
}
|
||||
for (const d in otherDevices[u]) {
|
||||
userDevices.set(d, mockDevice(u, d, otherDevices[u][d]));
|
||||
}
|
||||
}
|
||||
|
||||
return deviceMap;
|
||||
}
|
||||
|
||||
function makeMockClient(opts: {
|
||||
userId: UserID;
|
||||
deviceId: DeviceID;
|
||||
deviceKey?: Fingerprint;
|
||||
getLoginTokenEnabled: boolean;
|
||||
msc3882r0Only: boolean;
|
||||
msc3886Enabled: boolean;
|
||||
devices?: SimpleDeviceMap;
|
||||
verificationFunction?: (
|
||||
userId: string,
|
||||
deviceId: string,
|
||||
verified: boolean,
|
||||
blocked: boolean,
|
||||
known: boolean,
|
||||
) => void;
|
||||
crossSigningIds?: Partial<Record<CrossSigningKey, string>>;
|
||||
}): [MatrixClient, Map<string, Map<string, Device>>] {
|
||||
const deviceMap = mockDeviceMap(opts.userId, opts.deviceId, opts.deviceKey, opts.devices);
|
||||
return [
|
||||
{
|
||||
doesServerSupportUnstableFeature: jest.fn().mockImplementation((feature) => {
|
||||
if (feature === "org.matrix.msc3886") {
|
||||
return opts.msc3886Enabled;
|
||||
} else if (feature === "org.matrix.msc3882") {
|
||||
return opts.getLoginTokenEnabled;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
getVersions() {
|
||||
return {
|
||||
unstable_features: {
|
||||
"org.matrix.msc3882": opts.getLoginTokenEnabled,
|
||||
"org.matrix.msc3886": opts.msc3886Enabled,
|
||||
},
|
||||
};
|
||||
},
|
||||
getCachedCapabilities() {
|
||||
return opts.msc3882r0Only
|
||||
? {}
|
||||
: {
|
||||
capabilities: {
|
||||
"m.get_login_token": {
|
||||
enabled: opts.getLoginTokenEnabled,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
getUserId() {
|
||||
return opts.userId;
|
||||
},
|
||||
getSafeUserId() {
|
||||
return opts.userId;
|
||||
},
|
||||
getDeviceId() {
|
||||
return opts.deviceId;
|
||||
},
|
||||
baseUrl: "https://example.com",
|
||||
getCrypto() {
|
||||
return {
|
||||
getUserDeviceInfo(
|
||||
[userId]: string[],
|
||||
downloadUncached?: boolean,
|
||||
): Promise<Map<string, Map<string, Device>>> {
|
||||
return Promise.resolve(deviceMap);
|
||||
},
|
||||
getCrossSigningKeyId(key: CrossSigningKey): string | null {
|
||||
return opts.crossSigningIds?.[key] ?? null;
|
||||
},
|
||||
setDeviceVerified(userId: string, deviceId: string, verified: boolean): Promise<void> {
|
||||
return Promise.resolve();
|
||||
},
|
||||
crossSignDevice(deviceId: string): Promise<void> {
|
||||
return Promise.resolve();
|
||||
},
|
||||
getOwnDeviceKeys(): Promise<OwnDeviceKeys> {
|
||||
return Promise.resolve({
|
||||
ed25519: opts.deviceKey!,
|
||||
curve25519: "aaaa",
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
} as unknown as MatrixClient,
|
||||
deviceMap,
|
||||
];
|
||||
}
|
||||
|
||||
function makeTransport(name: string, uri = "https://test.rz/123456") {
|
||||
return new DummyTransport<any, MSC3903ECDHPayload>(name, { type: "http.v1", uri });
|
||||
}
|
||||
|
||||
describe("Rendezvous", function () {
|
||||
beforeAll(async function () {
|
||||
await global.Olm.init();
|
||||
});
|
||||
|
||||
let httpBackend: MockHttpBackend;
|
||||
let fetchFn: typeof global.fetch;
|
||||
let transports: DummyTransport<any, MSC3903ECDHPayload>[];
|
||||
const userId: UserID = "@user:example.com";
|
||||
|
||||
beforeEach(function () {
|
||||
httpBackend = new MockHttpBackend();
|
||||
fetchFn = httpBackend.fetchFn as typeof global.fetch;
|
||||
transports = [];
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
transports.forEach((x) => x.cleanup());
|
||||
});
|
||||
|
||||
it("generate and cancel", async function () {
|
||||
const [alice] = makeMockClient({
|
||||
userId,
|
||||
deviceId: "ALICE",
|
||||
msc3886Enabled: false,
|
||||
getLoginTokenEnabled: true,
|
||||
msc3882r0Only: true,
|
||||
});
|
||||
httpBackend.when("POST", "https://fallbackserver/rz").response = {
|
||||
body: null,
|
||||
response: {
|
||||
statusCode: 201,
|
||||
headers: {
|
||||
location: "https://fallbackserver/rz/123",
|
||||
},
|
||||
},
|
||||
};
|
||||
const aliceTransport = new MSC3886SimpleHttpRendezvousTransport({
|
||||
client: alice,
|
||||
fallbackRzServer: "https://fallbackserver/rz",
|
||||
fetchFn,
|
||||
});
|
||||
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport);
|
||||
const aliceRz = new MSC3906Rendezvous(aliceEcdh, alice);
|
||||
|
||||
expect(aliceRz.code).toBeUndefined();
|
||||
|
||||
const codePromise = aliceRz.generateCode();
|
||||
await httpBackend.flush("");
|
||||
|
||||
await aliceRz.generateCode();
|
||||
|
||||
expect(typeof aliceRz.code).toBe("string");
|
||||
|
||||
await codePromise;
|
||||
|
||||
const code = JSON.parse(aliceRz.code!) as RendezvousCode;
|
||||
|
||||
expect(code.intent).toEqual(RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE);
|
||||
expect(code.rendezvous?.algorithm).toEqual("org.matrix.msc3903.rendezvous.v2.curve25519-aes-sha256");
|
||||
expect(code.rendezvous?.transport.type).toEqual("org.matrix.msc3886.http.v1");
|
||||
expect((code.rendezvous?.transport as MSC3886SimpleHttpRendezvousTransportDetails).uri).toEqual(
|
||||
"https://fallbackserver/rz/123",
|
||||
);
|
||||
|
||||
httpBackend.when("DELETE", "https://fallbackserver/rz").response = {
|
||||
body: null,
|
||||
response: {
|
||||
statusCode: 204,
|
||||
headers: {},
|
||||
},
|
||||
};
|
||||
|
||||
const cancelPromise = aliceRz.cancel(RendezvousFailureReason.UserDeclined);
|
||||
await httpBackend.flush("");
|
||||
await expect(cancelPromise).resolves.toBeUndefined();
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
httpBackend.verifyNoOutstandingRequests();
|
||||
|
||||
await aliceRz.close();
|
||||
});
|
||||
|
||||
async function testNoProtocols({
|
||||
getLoginTokenEnabled,
|
||||
msc3882r0Only,
|
||||
}: {
|
||||
getLoginTokenEnabled: boolean;
|
||||
msc3882r0Only: boolean;
|
||||
}) {
|
||||
const aliceTransport = makeTransport("Alice");
|
||||
const bobTransport = makeTransport("Bob", "https://test.rz/999999");
|
||||
transports.push(aliceTransport, bobTransport);
|
||||
aliceTransport.otherParty = bobTransport;
|
||||
bobTransport.otherParty = aliceTransport;
|
||||
|
||||
// alice is already signs in and generates a code
|
||||
const aliceOnFailure = jest.fn();
|
||||
const [alice] = makeMockClient({
|
||||
userId,
|
||||
deviceId: "ALICE",
|
||||
msc3886Enabled: false,
|
||||
getLoginTokenEnabled,
|
||||
msc3882r0Only,
|
||||
});
|
||||
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
|
||||
const aliceRz = new MSC3906Rendezvous(aliceEcdh, alice);
|
||||
aliceTransport.onCancelled = aliceOnFailure;
|
||||
await aliceRz.generateCode();
|
||||
const code = JSON.parse(aliceRz.code!) as ECDHRendezvousCode;
|
||||
|
||||
expect(code.rendezvous.key).toBeDefined();
|
||||
|
||||
const aliceStartProm = aliceRz.startAfterShowingCode();
|
||||
|
||||
// bob is try to sign in and scans the code
|
||||
const bobOnFailure = jest.fn();
|
||||
const bobEcdh = new MSC3903ECDHRendezvousChannel(
|
||||
bobTransport,
|
||||
decodeBase64(code.rendezvous.key), // alice's public key
|
||||
bobOnFailure,
|
||||
);
|
||||
|
||||
const bobStartPromise = (async () => {
|
||||
const bobChecksum = await bobEcdh.connect();
|
||||
logger.info(`Bob checksums is ${bobChecksum} now sending intent`);
|
||||
// await bobEcdh.send({ type: 'm.login.progress', intent: RendezvousIntent.LOGIN_ON_NEW_DEVICE });
|
||||
|
||||
// wait for protocols
|
||||
logger.info("Bob waiting for protocols");
|
||||
const protocols = await bobEcdh.receive();
|
||||
|
||||
logger.info(`Bob protocols: ${JSON.stringify(protocols)}`);
|
||||
|
||||
expect(protocols).toEqual({
|
||||
type: "m.login.finish",
|
||||
outcome: "unsupported",
|
||||
});
|
||||
})();
|
||||
|
||||
await aliceStartProm;
|
||||
await bobStartPromise;
|
||||
}
|
||||
|
||||
it("no protocols - r0", async function () {
|
||||
await testNoProtocols({ getLoginTokenEnabled: false, msc3882r0Only: true });
|
||||
});
|
||||
|
||||
it("no protocols - stable", async function () {
|
||||
await testNoProtocols({ getLoginTokenEnabled: false, msc3882r0Only: false });
|
||||
});
|
||||
|
||||
it("new device declines protocol with outcome unsupported", async function () {
|
||||
const aliceTransport = makeTransport("Alice", "https://test.rz/123456");
|
||||
const bobTransport = makeTransport("Bob", "https://test.rz/999999");
|
||||
transports.push(aliceTransport, bobTransport);
|
||||
aliceTransport.otherParty = bobTransport;
|
||||
bobTransport.otherParty = aliceTransport;
|
||||
|
||||
// alice is already signs in and generates a code
|
||||
const aliceOnFailure = jest.fn();
|
||||
const [alice] = makeMockClient({
|
||||
userId,
|
||||
deviceId: "ALICE",
|
||||
getLoginTokenEnabled: true,
|
||||
msc3882r0Only: false,
|
||||
msc3886Enabled: false,
|
||||
});
|
||||
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
|
||||
const aliceRz = new MSC3906Rendezvous(aliceEcdh, alice);
|
||||
aliceTransport.onCancelled = aliceOnFailure;
|
||||
await aliceRz.generateCode();
|
||||
const code = JSON.parse(aliceRz.code!) as ECDHRendezvousCode;
|
||||
|
||||
expect(code.rendezvous.key).toBeDefined();
|
||||
|
||||
const aliceStartProm = aliceRz.startAfterShowingCode();
|
||||
|
||||
// bob is try to sign in and scans the code
|
||||
const bobOnFailure = jest.fn();
|
||||
const bobEcdh = new MSC3903ECDHRendezvousChannel(
|
||||
bobTransport,
|
||||
decodeBase64(code.rendezvous.key), // alice's public key
|
||||
bobOnFailure,
|
||||
);
|
||||
|
||||
const bobStartPromise = (async () => {
|
||||
const bobChecksum = await bobEcdh.connect();
|
||||
logger.info(`Bob checksums is ${bobChecksum} now sending intent`);
|
||||
// await bobEcdh.send({ type: 'm.login.progress', intent: RendezvousIntent.LOGIN_ON_NEW_DEVICE });
|
||||
|
||||
// wait for protocols
|
||||
logger.info("Bob waiting for protocols");
|
||||
const protocols = await bobEcdh.receive();
|
||||
|
||||
logger.info(`Bob protocols: ${JSON.stringify(protocols)}`);
|
||||
|
||||
expect(protocols).toEqual({
|
||||
type: "m.login.progress",
|
||||
protocols: ["org.matrix.msc3906.login_token"],
|
||||
});
|
||||
|
||||
await bobEcdh.send({ type: "m.login.finish", outcome: "unsupported" });
|
||||
})();
|
||||
|
||||
await aliceStartProm;
|
||||
await bobStartPromise;
|
||||
|
||||
expect(aliceOnFailure).toHaveBeenCalledWith(RendezvousFailureReason.UnsupportedAlgorithm);
|
||||
});
|
||||
|
||||
it("new device requests an invalid protocol", async function () {
|
||||
const aliceTransport = makeTransport("Alice", "https://test.rz/123456");
|
||||
const bobTransport = makeTransport("Bob", "https://test.rz/999999");
|
||||
transports.push(aliceTransport, bobTransport);
|
||||
aliceTransport.otherParty = bobTransport;
|
||||
bobTransport.otherParty = aliceTransport;
|
||||
|
||||
// alice is already signs in and generates a code
|
||||
const aliceOnFailure = jest.fn();
|
||||
const [alice] = makeMockClient({
|
||||
userId,
|
||||
deviceId: "ALICE",
|
||||
getLoginTokenEnabled: true,
|
||||
msc3882r0Only: false,
|
||||
msc3886Enabled: false,
|
||||
});
|
||||
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
|
||||
const aliceRz = new MSC3906Rendezvous(aliceEcdh, alice);
|
||||
aliceTransport.onCancelled = aliceOnFailure;
|
||||
await aliceRz.generateCode();
|
||||
const code = JSON.parse(aliceRz.code!) as ECDHRendezvousCode;
|
||||
|
||||
expect(code.rendezvous.key).toBeDefined();
|
||||
|
||||
const aliceStartProm = aliceRz.startAfterShowingCode();
|
||||
|
||||
// bob is try to sign in and scans the code
|
||||
const bobOnFailure = jest.fn();
|
||||
const bobEcdh = new MSC3903ECDHRendezvousChannel(
|
||||
bobTransport,
|
||||
decodeBase64(code.rendezvous.key), // alice's public key
|
||||
bobOnFailure,
|
||||
);
|
||||
|
||||
const bobStartPromise = (async () => {
|
||||
const bobChecksum = await bobEcdh.connect();
|
||||
logger.info(`Bob checksums is ${bobChecksum} now sending intent`);
|
||||
// await bobEcdh.send({ type: 'm.login.progress', intent: RendezvousIntent.LOGIN_ON_NEW_DEVICE });
|
||||
|
||||
// wait for protocols
|
||||
logger.info("Bob waiting for protocols");
|
||||
const protocols = await bobEcdh.receive();
|
||||
|
||||
logger.info(`Bob protocols: ${JSON.stringify(protocols)}`);
|
||||
|
||||
expect(protocols).toEqual({
|
||||
type: "m.login.progress",
|
||||
protocols: ["org.matrix.msc3906.login_token"],
|
||||
});
|
||||
|
||||
await bobEcdh.send({ type: "m.login.progress", protocol: "bad protocol" });
|
||||
})();
|
||||
|
||||
await aliceStartProm;
|
||||
await bobStartPromise;
|
||||
|
||||
expect(aliceOnFailure).toHaveBeenCalledWith(RendezvousFailureReason.UnsupportedAlgorithm);
|
||||
});
|
||||
|
||||
it("decline on existing device", async function () {
|
||||
const aliceTransport = makeTransport("Alice", "https://test.rz/123456");
|
||||
const bobTransport = makeTransport("Bob", "https://test.rz/999999");
|
||||
transports.push(aliceTransport, bobTransport);
|
||||
aliceTransport.otherParty = bobTransport;
|
||||
bobTransport.otherParty = aliceTransport;
|
||||
|
||||
// alice is already signs in and generates a code
|
||||
const aliceOnFailure = jest.fn();
|
||||
const [alice] = makeMockClient({
|
||||
userId: "alice",
|
||||
deviceId: "ALICE",
|
||||
getLoginTokenEnabled: true,
|
||||
msc3882r0Only: false,
|
||||
msc3886Enabled: false,
|
||||
});
|
||||
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
|
||||
const aliceRz = new MSC3906Rendezvous(aliceEcdh, alice);
|
||||
aliceTransport.onCancelled = aliceOnFailure;
|
||||
await aliceRz.generateCode();
|
||||
const code = JSON.parse(aliceRz.code!) as ECDHRendezvousCode;
|
||||
|
||||
expect(code.rendezvous.key).toBeDefined();
|
||||
|
||||
const aliceStartProm = aliceRz.startAfterShowingCode();
|
||||
|
||||
// bob is try to sign in and scans the code
|
||||
const bobOnFailure = jest.fn();
|
||||
const bobEcdh = new MSC3903ECDHRendezvousChannel(
|
||||
bobTransport,
|
||||
decodeBase64(code.rendezvous.key), // alice's public key
|
||||
bobOnFailure,
|
||||
);
|
||||
|
||||
const bobStartPromise = (async () => {
|
||||
const bobChecksum = await bobEcdh.connect();
|
||||
logger.info(`Bob checksums is ${bobChecksum} now sending intent`);
|
||||
// await bobEcdh.send({ type: 'm.login.progress', intent: RendezvousIntent.LOGIN_ON_NEW_DEVICE });
|
||||
|
||||
// wait for protocols
|
||||
logger.info("Bob waiting for protocols");
|
||||
const protocols = await bobEcdh.receive();
|
||||
|
||||
logger.info(`Bob protocols: ${JSON.stringify(protocols)}`);
|
||||
|
||||
expect(protocols).toEqual({
|
||||
type: "m.login.progress",
|
||||
protocols: ["org.matrix.msc3906.login_token"],
|
||||
});
|
||||
|
||||
await bobEcdh.send({ type: "m.login.progress", protocol: "org.matrix.msc3906.login_token" });
|
||||
})();
|
||||
|
||||
await aliceStartProm;
|
||||
await bobStartPromise;
|
||||
|
||||
await aliceRz.declineLoginOnExistingDevice();
|
||||
const loginToken = await bobEcdh.receive();
|
||||
expect(loginToken).toEqual({ type: "m.login.finish", outcome: "declined" });
|
||||
});
|
||||
|
||||
it("approve on existing device + no verification", async function () {
|
||||
const aliceTransport = makeTransport("Alice", "https://test.rz/123456");
|
||||
const bobTransport = makeTransport("Bob", "https://test.rz/999999");
|
||||
transports.push(aliceTransport, bobTransport);
|
||||
aliceTransport.otherParty = bobTransport;
|
||||
bobTransport.otherParty = aliceTransport;
|
||||
|
||||
// alice is already signs in and generates a code
|
||||
const aliceOnFailure = jest.fn();
|
||||
const [alice] = makeMockClient({
|
||||
userId: "alice",
|
||||
deviceId: "ALICE",
|
||||
getLoginTokenEnabled: true,
|
||||
msc3882r0Only: false,
|
||||
msc3886Enabled: false,
|
||||
});
|
||||
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
|
||||
const aliceRz = new MSC3906Rendezvous(aliceEcdh, alice);
|
||||
aliceTransport.onCancelled = aliceOnFailure;
|
||||
await aliceRz.generateCode();
|
||||
const code = JSON.parse(aliceRz.code!) as ECDHRendezvousCode;
|
||||
|
||||
expect(code.rendezvous.key).toBeDefined();
|
||||
|
||||
const aliceStartProm = aliceRz.startAfterShowingCode();
|
||||
|
||||
// bob is try to sign in and scans the code
|
||||
const bobOnFailure = jest.fn();
|
||||
const bobEcdh = new MSC3903ECDHRendezvousChannel(
|
||||
bobTransport,
|
||||
decodeBase64(code.rendezvous.key), // alice's public key
|
||||
bobOnFailure,
|
||||
);
|
||||
|
||||
const bobStartPromise = (async () => {
|
||||
const bobChecksum = await bobEcdh.connect();
|
||||
logger.info(`Bob checksums is ${bobChecksum} now sending intent`);
|
||||
// await bobEcdh.send({ type: 'm.login.progress', intent: RendezvousIntent.LOGIN_ON_NEW_DEVICE });
|
||||
|
||||
// wait for protocols
|
||||
logger.info("Bob waiting for protocols");
|
||||
const protocols = await bobEcdh.receive();
|
||||
|
||||
logger.info(`Bob protocols: ${JSON.stringify(protocols)}`);
|
||||
|
||||
expect(protocols).toEqual({
|
||||
type: "m.login.progress",
|
||||
protocols: ["org.matrix.msc3906.login_token"],
|
||||
});
|
||||
|
||||
await bobEcdh.send({ type: "m.login.progress", protocol: "org.matrix.msc3906.login_token" });
|
||||
})();
|
||||
|
||||
await aliceStartProm;
|
||||
await bobStartPromise;
|
||||
|
||||
const confirmProm = aliceRz.approveLoginOnExistingDevice("token");
|
||||
|
||||
const bobCompleteProm = (async () => {
|
||||
const loginToken = await bobEcdh.receive();
|
||||
expect(loginToken).toEqual({ type: "m.login.progress", login_token: "token", homeserver: alice.baseUrl });
|
||||
await bobEcdh.send({ type: "m.login.finish", outcome: "success" });
|
||||
})();
|
||||
|
||||
await confirmProm;
|
||||
await bobCompleteProm;
|
||||
});
|
||||
|
||||
async function completeLogin(devices: SimpleDeviceMap) {
|
||||
const aliceTransport = makeTransport("Alice", "https://test.rz/123456");
|
||||
const bobTransport = makeTransport("Bob", "https://test.rz/999999");
|
||||
transports.push(aliceTransport, bobTransport);
|
||||
aliceTransport.otherParty = bobTransport;
|
||||
bobTransport.otherParty = aliceTransport;
|
||||
|
||||
// alice is already signs in and generates a code
|
||||
const aliceOnFailure = jest.fn();
|
||||
const aliceVerification = jest.fn();
|
||||
const [alice, deviceMap] = makeMockClient({
|
||||
userId,
|
||||
deviceId: "ALICE",
|
||||
getLoginTokenEnabled: true,
|
||||
msc3882r0Only: false,
|
||||
msc3886Enabled: false,
|
||||
devices,
|
||||
deviceKey: "aaaa",
|
||||
verificationFunction: aliceVerification,
|
||||
crossSigningIds: {
|
||||
master: "mmmmm",
|
||||
},
|
||||
});
|
||||
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
|
||||
const aliceRz = new MSC3906Rendezvous(aliceEcdh, alice);
|
||||
aliceTransport.onCancelled = aliceOnFailure;
|
||||
await aliceRz.generateCode();
|
||||
const code = JSON.parse(aliceRz.code!) as ECDHRendezvousCode;
|
||||
|
||||
expect(code.rendezvous.key).toBeDefined();
|
||||
|
||||
const aliceStartProm = aliceRz.startAfterShowingCode();
|
||||
|
||||
// bob is try to sign in and scans the code
|
||||
const bobOnFailure = jest.fn();
|
||||
const bobEcdh = new MSC3903ECDHRendezvousChannel(
|
||||
bobTransport,
|
||||
decodeBase64(code.rendezvous.key), // alice's public key
|
||||
bobOnFailure,
|
||||
);
|
||||
|
||||
const bobStartPromise = (async () => {
|
||||
const bobChecksum = await bobEcdh.connect();
|
||||
logger.info(`Bob checksums is ${bobChecksum} now sending intent`);
|
||||
// await bobEcdh.send({ type: 'm.login.progress', intent: RendezvousIntent.LOGIN_ON_NEW_DEVICE });
|
||||
|
||||
// wait for protocols
|
||||
logger.info("Bob waiting for protocols");
|
||||
const protocols = await bobEcdh.receive();
|
||||
|
||||
logger.info(`Bob protocols: ${JSON.stringify(protocols)}`);
|
||||
|
||||
expect(protocols).toEqual({
|
||||
type: "m.login.progress",
|
||||
protocols: ["org.matrix.msc3906.login_token"],
|
||||
});
|
||||
|
||||
await bobEcdh.send({ type: "m.login.progress", protocol: "org.matrix.msc3906.login_token" });
|
||||
})();
|
||||
|
||||
await aliceStartProm;
|
||||
await bobStartPromise;
|
||||
|
||||
const confirmProm = aliceRz.approveLoginOnExistingDevice("token");
|
||||
|
||||
const bobLoginProm = (async () => {
|
||||
const loginToken = await bobEcdh.receive();
|
||||
expect(loginToken).toEqual({ type: "m.login.progress", login_token: "token", homeserver: alice.baseUrl });
|
||||
await bobEcdh.send({ type: "m.login.finish", outcome: "success", device_id: "BOB", device_key: "bbbb" });
|
||||
})();
|
||||
|
||||
expect(await confirmProm).toEqual("BOB");
|
||||
await bobLoginProm;
|
||||
|
||||
return {
|
||||
aliceTransport,
|
||||
aliceEcdh,
|
||||
aliceRz,
|
||||
bobTransport,
|
||||
bobEcdh,
|
||||
deviceMap,
|
||||
};
|
||||
}
|
||||
|
||||
it("approve on existing device + verification", async function () {
|
||||
const { bobEcdh, aliceRz } = await completeLogin({
|
||||
[userId]: {
|
||||
BOB: "bbbb",
|
||||
},
|
||||
});
|
||||
const verifyProm = aliceRz.verifyNewDeviceOnExistingDevice();
|
||||
|
||||
const bobVerifyProm = (async () => {
|
||||
const verified = await bobEcdh.receive();
|
||||
expect(verified).toEqual({
|
||||
type: "m.login.finish",
|
||||
outcome: "verified",
|
||||
verifying_device_id: "ALICE",
|
||||
verifying_device_key: "aaaa",
|
||||
master_key: "mmmmm",
|
||||
});
|
||||
})();
|
||||
|
||||
await verifyProm;
|
||||
await bobVerifyProm;
|
||||
});
|
||||
|
||||
it("device not online within timeout", async function () {
|
||||
const { aliceRz } = await completeLogin({});
|
||||
await expect(aliceRz.verifyNewDeviceOnExistingDevice(1000)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("device appears online within timeout", async function () {
|
||||
const devices: SimpleDeviceMap = {};
|
||||
const { aliceRz, deviceMap } = await completeLogin(devices);
|
||||
// device appears before the timeout
|
||||
setTimeout(() => {
|
||||
deviceMap.get(userId)!.set("BOB", mockDevice(userId, "BOB", "bbbb"));
|
||||
}, 1000);
|
||||
await aliceRz.verifyNewDeviceOnExistingDevice(2000);
|
||||
});
|
||||
|
||||
it("device appears online after timeout", async function () {
|
||||
const devices: SimpleDeviceMap = {};
|
||||
const { aliceRz, deviceMap } = await completeLogin(devices);
|
||||
// device appears after the timeout
|
||||
setTimeout(() => {
|
||||
deviceMap.get(userId)!.set("BOB", mockDevice(userId, "BOB", "bbbb"));
|
||||
}, 1500);
|
||||
await expect(aliceRz.verifyNewDeviceOnExistingDevice(1000)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("mismatched device key", async function () {
|
||||
const { aliceRz } = await completeLogin({
|
||||
[userId]: {
|
||||
BOB: "XXXX",
|
||||
},
|
||||
});
|
||||
await expect(aliceRz.verifyNewDeviceOnExistingDevice(1000)).rejects.toThrow(/different key/);
|
||||
});
|
||||
});
|
||||
@@ -1,453 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 MockHttpBackend from "matrix-mock-request";
|
||||
|
||||
import type { MatrixClient } from "../../../src";
|
||||
import { LegacyRendezvousFailureReason as RendezvousFailureReason } from "../../../src/rendezvous";
|
||||
import { MSC3886SimpleHttpRendezvousTransport } from "../../../src/rendezvous/transports";
|
||||
|
||||
function makeMockClient(opts: { userId: string; deviceId: string; msc3886Enabled: boolean }): MatrixClient {
|
||||
return {
|
||||
doesServerSupportUnstableFeature(feature: string) {
|
||||
return Promise.resolve(opts.msc3886Enabled && feature === "org.matrix.msc3886");
|
||||
},
|
||||
getUserId() {
|
||||
return opts.userId;
|
||||
},
|
||||
getDeviceId() {
|
||||
return opts.deviceId;
|
||||
},
|
||||
requestLoginToken() {
|
||||
return Promise.resolve({ login_token: "token" });
|
||||
},
|
||||
baseUrl: "https://example.com",
|
||||
} as unknown as MatrixClient;
|
||||
}
|
||||
|
||||
describe("SimpleHttpRendezvousTransport", function () {
|
||||
let httpBackend: MockHttpBackend;
|
||||
let fetchFn: typeof global.fetch;
|
||||
|
||||
beforeEach(function () {
|
||||
httpBackend = new MockHttpBackend();
|
||||
fetchFn = httpBackend.fetchFn as typeof global.fetch;
|
||||
});
|
||||
|
||||
async function postAndCheckLocation(
|
||||
msc3886Enabled: boolean,
|
||||
fallbackRzServer: string,
|
||||
locationResponse: string,
|
||||
expectedFinalLocation: string,
|
||||
) {
|
||||
const client = makeMockClient({ userId: "@alice:example.com", deviceId: "DEVICEID", msc3886Enabled });
|
||||
const simpleHttpTransport = new MSC3886SimpleHttpRendezvousTransport({ client, fallbackRzServer, fetchFn });
|
||||
{
|
||||
// initial POST
|
||||
const expectedPostLocation = msc3886Enabled
|
||||
? `${client.baseUrl}/_matrix/client/unstable/org.matrix.msc3886/rendezvous`
|
||||
: fallbackRzServer;
|
||||
|
||||
const prom = simpleHttpTransport.send({});
|
||||
httpBackend.when("POST", expectedPostLocation).response = {
|
||||
body: null,
|
||||
response: {
|
||||
statusCode: 201,
|
||||
headers: {
|
||||
location: locationResponse,
|
||||
},
|
||||
},
|
||||
};
|
||||
await httpBackend.flush("");
|
||||
await prom;
|
||||
}
|
||||
const details = await simpleHttpTransport.details();
|
||||
expect(details.uri).toBe(expectedFinalLocation);
|
||||
|
||||
{
|
||||
// first GET without etag
|
||||
const prom = simpleHttpTransport.receive();
|
||||
httpBackend.when("GET", expectedFinalLocation).response = {
|
||||
body: {},
|
||||
response: {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
},
|
||||
};
|
||||
await httpBackend.flush("");
|
||||
expect(await prom).toEqual({});
|
||||
httpBackend.verifyNoOutstandingRequests();
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
}
|
||||
}
|
||||
it("should throw an error when no server available", async function () {
|
||||
const client = makeMockClient({ userId: "@alice:example.com", deviceId: "DEVICEID", msc3886Enabled: false });
|
||||
const simpleHttpTransport = new MSC3886SimpleHttpRendezvousTransport({ client, fetchFn });
|
||||
await expect(simpleHttpTransport.send({})).rejects.toThrow("Invalid rendezvous URI");
|
||||
});
|
||||
|
||||
it("POST to fallback server", async function () {
|
||||
const client = makeMockClient({ userId: "@alice:example.com", deviceId: "DEVICEID", msc3886Enabled: false });
|
||||
const simpleHttpTransport = new MSC3886SimpleHttpRendezvousTransport({
|
||||
client,
|
||||
fallbackRzServer: "https://fallbackserver/rz",
|
||||
fetchFn,
|
||||
});
|
||||
const prom = simpleHttpTransport.send({});
|
||||
httpBackend.when("POST", "https://fallbackserver/rz").response = {
|
||||
body: null,
|
||||
response: {
|
||||
statusCode: 201,
|
||||
headers: {
|
||||
location: "https://fallbackserver/rz/123",
|
||||
},
|
||||
},
|
||||
};
|
||||
await httpBackend.flush("");
|
||||
expect(await prom).toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it("POST with no location", async function () {
|
||||
const client = makeMockClient({ userId: "@alice:example.com", deviceId: "DEVICEID", msc3886Enabled: false });
|
||||
const simpleHttpTransport = new MSC3886SimpleHttpRendezvousTransport({
|
||||
client,
|
||||
fallbackRzServer: "https://fallbackserver/rz",
|
||||
fetchFn,
|
||||
});
|
||||
const prom = simpleHttpTransport.send({});
|
||||
httpBackend.when("POST", "https://fallbackserver/rz").response = {
|
||||
body: null,
|
||||
response: {
|
||||
statusCode: 201,
|
||||
headers: {},
|
||||
},
|
||||
};
|
||||
await Promise.all([expect(prom).rejects.toThrow(), httpBackend.flush("")]);
|
||||
});
|
||||
|
||||
it("POST with absolute path response", async function () {
|
||||
await postAndCheckLocation(false, "https://fallbackserver/rz", "/123", "https://fallbackserver/123");
|
||||
});
|
||||
|
||||
it("POST to built-in MSC3886 implementation", async function () {
|
||||
await postAndCheckLocation(
|
||||
true,
|
||||
"https://fallbackserver/rz",
|
||||
"123",
|
||||
"https://example.com/_matrix/client/unstable/org.matrix.msc3886/rendezvous/123",
|
||||
);
|
||||
});
|
||||
|
||||
it("POST with relative path response including parent", async function () {
|
||||
await postAndCheckLocation(
|
||||
false,
|
||||
"https://fallbackserver/rz/abc",
|
||||
"../xyz/123",
|
||||
"https://fallbackserver/rz/xyz/123",
|
||||
);
|
||||
});
|
||||
|
||||
it("POST to follow 307 to other server", async function () {
|
||||
const client = makeMockClient({ userId: "@alice:example.com", deviceId: "DEVICEID", msc3886Enabled: false });
|
||||
const simpleHttpTransport = new MSC3886SimpleHttpRendezvousTransport({
|
||||
client,
|
||||
fallbackRzServer: "https://fallbackserver/rz",
|
||||
fetchFn,
|
||||
});
|
||||
const prom = simpleHttpTransport.send({});
|
||||
httpBackend.when("POST", "https://fallbackserver/rz").response = {
|
||||
body: null,
|
||||
response: {
|
||||
statusCode: 307,
|
||||
headers: {
|
||||
location: "https://redirected.fallbackserver/rz",
|
||||
},
|
||||
},
|
||||
};
|
||||
httpBackend.when("POST", "https://redirected.fallbackserver/rz").response = {
|
||||
body: null,
|
||||
response: {
|
||||
statusCode: 201,
|
||||
headers: {
|
||||
location: "https://redirected.fallbackserver/rz/123",
|
||||
etag: "aaa",
|
||||
},
|
||||
},
|
||||
};
|
||||
await httpBackend.flush("");
|
||||
expect(await prom).toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it("POST and GET", async function () {
|
||||
const client = makeMockClient({ userId: "@alice:example.com", deviceId: "DEVICEID", msc3886Enabled: false });
|
||||
const simpleHttpTransport = new MSC3886SimpleHttpRendezvousTransport({
|
||||
client,
|
||||
fallbackRzServer: "https://fallbackserver/rz",
|
||||
fetchFn,
|
||||
});
|
||||
{
|
||||
// initial POST
|
||||
const prom = simpleHttpTransport.send({ foo: "baa" });
|
||||
httpBackend.when("POST", "https://fallbackserver/rz").check(({ headers, data }) => {
|
||||
expect(headers["content-type"]).toEqual("application/json");
|
||||
expect(data).toEqual({ foo: "baa" });
|
||||
}).response = {
|
||||
body: null,
|
||||
response: {
|
||||
statusCode: 201,
|
||||
headers: {
|
||||
location: "https://fallbackserver/rz/123",
|
||||
},
|
||||
},
|
||||
};
|
||||
await httpBackend.flush("");
|
||||
expect(await prom).toStrictEqual(undefined);
|
||||
}
|
||||
{
|
||||
// first GET without etag
|
||||
const prom = simpleHttpTransport.receive();
|
||||
httpBackend.when("GET", "https://fallbackserver/rz/123").response = {
|
||||
body: { foo: "baa" },
|
||||
response: {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"etag": "aaa",
|
||||
},
|
||||
},
|
||||
};
|
||||
await httpBackend.flush("");
|
||||
expect(await prom).toEqual({ foo: "baa" });
|
||||
}
|
||||
{
|
||||
// subsequent GET which should have etag from previous request
|
||||
const prom = simpleHttpTransport.receive();
|
||||
httpBackend.when("GET", "https://fallbackserver/rz/123").check(({ headers }) => {
|
||||
expect(headers["if-none-match"]).toEqual("aaa");
|
||||
}).response = {
|
||||
body: { foo: "baa" },
|
||||
response: {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"etag": "bbb",
|
||||
},
|
||||
},
|
||||
};
|
||||
await httpBackend.flush("");
|
||||
expect(await prom).toEqual({ foo: "baa" });
|
||||
}
|
||||
});
|
||||
|
||||
it("POST and PUTs", async function () {
|
||||
const client = makeMockClient({ userId: "@alice:example.com", deviceId: "DEVICEID", msc3886Enabled: false });
|
||||
const simpleHttpTransport = new MSC3886SimpleHttpRendezvousTransport({
|
||||
client,
|
||||
fallbackRzServer: "https://fallbackserver/rz",
|
||||
fetchFn,
|
||||
});
|
||||
{
|
||||
// initial POST
|
||||
const prom = simpleHttpTransport.send({ foo: "baa" });
|
||||
httpBackend.when("POST", "https://fallbackserver/rz").check(({ headers, data }) => {
|
||||
expect(headers["content-type"]).toEqual("application/json");
|
||||
expect(data).toEqual({ foo: "baa" });
|
||||
}).response = {
|
||||
body: null,
|
||||
response: {
|
||||
statusCode: 201,
|
||||
headers: {
|
||||
location: "https://fallbackserver/rz/123",
|
||||
},
|
||||
},
|
||||
};
|
||||
await httpBackend.flush("", 1);
|
||||
await prom;
|
||||
}
|
||||
{
|
||||
// first PUT without etag
|
||||
const prom = simpleHttpTransport.send({ a: "b" });
|
||||
httpBackend.when("PUT", "https://fallbackserver/rz/123").check(({ headers, data }) => {
|
||||
expect(headers["if-match"]).toBeUndefined();
|
||||
expect(data).toEqual({ a: "b" });
|
||||
}).response = {
|
||||
body: null,
|
||||
response: {
|
||||
statusCode: 202,
|
||||
headers: {
|
||||
etag: "aaa",
|
||||
},
|
||||
},
|
||||
};
|
||||
await httpBackend.flush("", 1);
|
||||
await prom;
|
||||
}
|
||||
{
|
||||
// subsequent PUT which should have etag from previous request
|
||||
const prom = simpleHttpTransport.send({ c: "d" });
|
||||
httpBackend.when("PUT", "https://fallbackserver/rz/123").check(({ headers }) => {
|
||||
expect(headers["if-match"]).toEqual("aaa");
|
||||
}).response = {
|
||||
body: null,
|
||||
response: {
|
||||
statusCode: 202,
|
||||
headers: {
|
||||
etag: "bbb",
|
||||
},
|
||||
},
|
||||
};
|
||||
await httpBackend.flush("", 1);
|
||||
await prom;
|
||||
}
|
||||
});
|
||||
|
||||
it("POST and DELETE", async function () {
|
||||
const client = makeMockClient({ userId: "@alice:example.com", deviceId: "DEVICEID", msc3886Enabled: false });
|
||||
const simpleHttpTransport = new MSC3886SimpleHttpRendezvousTransport({
|
||||
client,
|
||||
fallbackRzServer: "https://fallbackserver/rz",
|
||||
fetchFn,
|
||||
});
|
||||
{
|
||||
// Create
|
||||
const prom = simpleHttpTransport.send({ foo: "baa" });
|
||||
httpBackend.when("POST", "https://fallbackserver/rz").check(({ headers, data }) => {
|
||||
expect(headers["content-type"]).toEqual("application/json");
|
||||
expect(data).toEqual({ foo: "baa" });
|
||||
}).response = {
|
||||
body: null,
|
||||
response: {
|
||||
statusCode: 201,
|
||||
headers: {
|
||||
location: "https://fallbackserver/rz/123",
|
||||
},
|
||||
},
|
||||
};
|
||||
await httpBackend.flush("");
|
||||
expect(await prom).toStrictEqual(undefined);
|
||||
}
|
||||
{
|
||||
// Cancel
|
||||
const prom = simpleHttpTransport.cancel(RendezvousFailureReason.UserDeclined);
|
||||
httpBackend.when("DELETE", "https://fallbackserver/rz/123").response = {
|
||||
body: null,
|
||||
response: {
|
||||
statusCode: 204,
|
||||
headers: {},
|
||||
},
|
||||
};
|
||||
await httpBackend.flush("");
|
||||
await prom;
|
||||
}
|
||||
});
|
||||
|
||||
it("details before ready", async function () {
|
||||
const client = makeMockClient({ userId: "@alice:example.com", deviceId: "DEVICEID", msc3886Enabled: false });
|
||||
const simpleHttpTransport = new MSC3886SimpleHttpRendezvousTransport({
|
||||
client,
|
||||
fallbackRzServer: "https://fallbackserver/rz",
|
||||
fetchFn,
|
||||
});
|
||||
await expect(simpleHttpTransport.details()).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("send after cancelled", async function () {
|
||||
const client = makeMockClient({ userId: "@alice:example.com", deviceId: "DEVICEID", msc3886Enabled: false });
|
||||
const simpleHttpTransport = new MSC3886SimpleHttpRendezvousTransport({
|
||||
client,
|
||||
fallbackRzServer: "https://fallbackserver/rz",
|
||||
fetchFn,
|
||||
});
|
||||
await simpleHttpTransport.cancel(RendezvousFailureReason.UserDeclined);
|
||||
await expect(simpleHttpTransport.send({})).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("receive before ready", async function () {
|
||||
const client = makeMockClient({ userId: "@alice:example.com", deviceId: "DEVICEID", msc3886Enabled: false });
|
||||
const simpleHttpTransport = new MSC3886SimpleHttpRendezvousTransport({
|
||||
client,
|
||||
fallbackRzServer: "https://fallbackserver/rz",
|
||||
fetchFn,
|
||||
});
|
||||
await expect(simpleHttpTransport.receive()).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("404 failure callback", async function () {
|
||||
const client = makeMockClient({ userId: "@alice:example.com", deviceId: "DEVICEID", msc3886Enabled: false });
|
||||
const onFailure = jest.fn();
|
||||
const simpleHttpTransport = new MSC3886SimpleHttpRendezvousTransport({
|
||||
client,
|
||||
fallbackRzServer: "https://fallbackserver/rz",
|
||||
fetchFn,
|
||||
onFailure,
|
||||
});
|
||||
|
||||
httpBackend.when("POST", "https://fallbackserver/rz").response = {
|
||||
body: null,
|
||||
response: {
|
||||
statusCode: 404,
|
||||
headers: {},
|
||||
},
|
||||
};
|
||||
await Promise.all([
|
||||
expect(simpleHttpTransport.send({ foo: "baa" })).resolves.toBeUndefined(),
|
||||
httpBackend.flush("", 1),
|
||||
]);
|
||||
expect(onFailure).toHaveBeenCalledWith(RendezvousFailureReason.Unknown);
|
||||
});
|
||||
|
||||
it("404 failure callback mapped to expired", async function () {
|
||||
const client = makeMockClient({ userId: "@alice:example.com", deviceId: "DEVICEID", msc3886Enabled: false });
|
||||
const onFailure = jest.fn();
|
||||
const simpleHttpTransport = new MSC3886SimpleHttpRendezvousTransport({
|
||||
client,
|
||||
fallbackRzServer: "https://fallbackserver/rz",
|
||||
fetchFn,
|
||||
onFailure,
|
||||
});
|
||||
|
||||
{
|
||||
// initial POST
|
||||
const prom = simpleHttpTransport.send({ foo: "baa" });
|
||||
httpBackend.when("POST", "https://fallbackserver/rz").response = {
|
||||
body: null,
|
||||
response: {
|
||||
statusCode: 201,
|
||||
headers: {
|
||||
location: "https://fallbackserver/rz/123",
|
||||
expires: "Thu, 01 Jan 1970 00:00:00 GMT",
|
||||
},
|
||||
},
|
||||
};
|
||||
await httpBackend.flush("");
|
||||
await prom;
|
||||
}
|
||||
{
|
||||
// GET with 404 to simulate expiry
|
||||
httpBackend.when("GET", "https://fallbackserver/rz/123").response = {
|
||||
body: { foo: "baa" },
|
||||
response: {
|
||||
statusCode: 404,
|
||||
headers: {},
|
||||
},
|
||||
};
|
||||
await Promise.all([expect(simpleHttpTransport.receive()).resolves.toBeUndefined(), httpBackend.flush("")]);
|
||||
expect(onFailure).toHaveBeenCalledWith(RendezvousFailureReason.Expired);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -208,7 +208,7 @@ describe("OutgoingRequestProcessor", () => {
|
||||
const outgoingRequest = new UploadSigningKeysRequest(JSON.stringify(testReq));
|
||||
|
||||
// also create a UIA callback
|
||||
const authCallback: UIAuthCallback<Object> = async (makeRequest) => {
|
||||
const authCallback: UIAuthCallback<object> = async (makeRequest) => {
|
||||
return await makeRequest({ type: "test" });
|
||||
};
|
||||
|
||||
@@ -458,7 +458,7 @@ describe("OutgoingRequestProcessor", () => {
|
||||
throw new Error("Failed to fetch");
|
||||
}
|
||||
});
|
||||
const authCallback: UIAuthCallback<Object> = async (makeRequest) => {
|
||||
const authCallback: UIAuthCallback<object> = async (makeRequest) => {
|
||||
return await makeRequest({ type: "test" });
|
||||
};
|
||||
const requestPromise = processor.makeOutgoingRequest(outgoingRequest, authCallback);
|
||||
|
||||
@@ -26,7 +26,6 @@ import { RustBackupCryptoEventMap, RustBackupCryptoEvents, RustBackupManager } f
|
||||
import * as TestData from "../../test-utils/test-data";
|
||||
import {
|
||||
ConnectionError,
|
||||
CryptoEvent,
|
||||
HttpApiEvent,
|
||||
HttpApiEventHandlerMap,
|
||||
IHttpOpts,
|
||||
@@ -37,6 +36,7 @@ import {
|
||||
import * as testData from "../../test-utils/test-data";
|
||||
import { BackupDecryptor } from "../../../src/common-crypto/CryptoBackend";
|
||||
import { KeyBackupSession } from "../../../src/crypto-api/keybackup";
|
||||
import { CryptoEvent } from "../../../src/crypto-api/index.ts";
|
||||
|
||||
describe("PerSessionKeyBackupDownloader", () => {
|
||||
/** The downloader under test */
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
CollectStrategy,
|
||||
Curve25519PublicKey,
|
||||
Ed25519PublicKey,
|
||||
HistoryVisibility as RustHistoryVisibility,
|
||||
@@ -31,6 +32,7 @@ import { KeyClaimManager } from "../../../src/rust-crypto/KeyClaimManager";
|
||||
import { defer } from "../../../src/utils";
|
||||
import { OutgoingRequestsManager } from "../../../src/rust-crypto/OutgoingRequestsManager";
|
||||
import { KnownMembership } from "../../../src/@types/membership";
|
||||
import { DeviceIsolationMode, AllDevicesIsolationMode, OnlySignedDevicesIsolationMode } from "../../../src/crypto-api";
|
||||
|
||||
describe("RoomEncryptor", () => {
|
||||
describe("History Visibility", () => {
|
||||
@@ -99,7 +101,7 @@ describe("RoomEncryptor", () => {
|
||||
getEncryptionTargetMembers: jest.fn().mockReturnValue([mockRoomMember]),
|
||||
shouldEncryptForInvitedMembers: jest.fn().mockReturnValue(true),
|
||||
getHistoryVisibility: jest.fn().mockReturnValue(HistoryVisibility.Invited),
|
||||
getBlacklistUnverifiedDevices: jest.fn().mockReturnValue(false),
|
||||
getBlacklistUnverifiedDevices: jest.fn().mockReturnValue(null),
|
||||
} as unknown as Mocked<Room>;
|
||||
|
||||
roomEncryptor = new RoomEncryptor(
|
||||
@@ -111,6 +113,8 @@ describe("RoomEncryptor", () => {
|
||||
);
|
||||
});
|
||||
|
||||
const defaultDevicesIsolationMode = new AllDevicesIsolationMode(false);
|
||||
|
||||
it("should ensure that there is only one shareRoomKey at a time", async () => {
|
||||
const deferredShare = defer<void>();
|
||||
const insideOlmShareRoom = defer<void>();
|
||||
@@ -120,19 +124,19 @@ describe("RoomEncryptor", () => {
|
||||
await deferredShare.promise;
|
||||
});
|
||||
|
||||
roomEncryptor.prepareForEncryption(false);
|
||||
roomEncryptor.prepareForEncryption(false, defaultDevicesIsolationMode);
|
||||
await insideOlmShareRoom.promise;
|
||||
|
||||
// call several times more
|
||||
roomEncryptor.prepareForEncryption(false);
|
||||
roomEncryptor.encryptEvent(createMockEvent("Hello"), false);
|
||||
roomEncryptor.prepareForEncryption(false);
|
||||
roomEncryptor.encryptEvent(createMockEvent("World"), false);
|
||||
roomEncryptor.prepareForEncryption(false, defaultDevicesIsolationMode);
|
||||
roomEncryptor.encryptEvent(createMockEvent("Hello"), false, defaultDevicesIsolationMode);
|
||||
roomEncryptor.prepareForEncryption(false, defaultDevicesIsolationMode);
|
||||
roomEncryptor.encryptEvent(createMockEvent("World"), false, defaultDevicesIsolationMode);
|
||||
|
||||
expect(mockOlmMachine.shareRoomKey).toHaveBeenCalledTimes(1);
|
||||
|
||||
deferredShare.resolve();
|
||||
await roomEncryptor.prepareForEncryption(false);
|
||||
await roomEncryptor.prepareForEncryption(false, defaultDevicesIsolationMode);
|
||||
|
||||
// should have been called again
|
||||
expect(mockOlmMachine.shareRoomKey).toHaveBeenCalledTimes(6);
|
||||
@@ -158,8 +162,16 @@ describe("RoomEncryptor", () => {
|
||||
|
||||
let firstMessageFinished: string | null = null;
|
||||
|
||||
const firstRequest = roomEncryptor.encryptEvent(createMockEvent("Hello"), false);
|
||||
const secondRequest = roomEncryptor.encryptEvent(createMockEvent("Edit of Hello"), false);
|
||||
const firstRequest = roomEncryptor.encryptEvent(
|
||||
createMockEvent("Hello"),
|
||||
false,
|
||||
defaultDevicesIsolationMode,
|
||||
);
|
||||
const secondRequest = roomEncryptor.encryptEvent(
|
||||
createMockEvent("Edit of Hello"),
|
||||
false,
|
||||
defaultDevicesIsolationMode,
|
||||
);
|
||||
|
||||
firstRequest.then(() => {
|
||||
if (firstMessageFinished === null) {
|
||||
@@ -181,5 +193,96 @@ describe("RoomEncryptor", () => {
|
||||
|
||||
expect(firstMessageFinished).toBe("hello");
|
||||
});
|
||||
|
||||
describe("DeviceIsolationMode", () => {
|
||||
type TestCase = [
|
||||
string,
|
||||
{
|
||||
mode: DeviceIsolationMode;
|
||||
expectedStrategy: CollectStrategy;
|
||||
globalBlacklistUnverifiedDevices: boolean;
|
||||
},
|
||||
];
|
||||
|
||||
const testCases: TestCase[] = [
|
||||
[
|
||||
"Share AllDevicesIsolationMode",
|
||||
{
|
||||
mode: new AllDevicesIsolationMode(false),
|
||||
expectedStrategy: CollectStrategy.deviceBasedStrategy(false, false),
|
||||
globalBlacklistUnverifiedDevices: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Share AllDevicesIsolationMode - with blacklist unverified",
|
||||
{
|
||||
mode: new AllDevicesIsolationMode(false),
|
||||
expectedStrategy: CollectStrategy.deviceBasedStrategy(true, false),
|
||||
globalBlacklistUnverifiedDevices: true,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Share OnlySigned - blacklist true",
|
||||
{
|
||||
mode: new OnlySignedDevicesIsolationMode(),
|
||||
expectedStrategy: CollectStrategy.identityBasedStrategy(),
|
||||
globalBlacklistUnverifiedDevices: true,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Share OnlySigned",
|
||||
{
|
||||
mode: new OnlySignedDevicesIsolationMode(),
|
||||
expectedStrategy: CollectStrategy.identityBasedStrategy(),
|
||||
globalBlacklistUnverifiedDevices: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Share AllDevicesIsolationMode - Verified user problems true",
|
||||
{
|
||||
mode: new AllDevicesIsolationMode(true),
|
||||
expectedStrategy: CollectStrategy.deviceBasedStrategy(false, true),
|
||||
globalBlacklistUnverifiedDevices: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
'Share AllDevicesIsolationMode - with blacklist unverified - Verified user problems true"',
|
||||
{
|
||||
mode: new AllDevicesIsolationMode(true),
|
||||
expectedStrategy: CollectStrategy.deviceBasedStrategy(true, true),
|
||||
globalBlacklistUnverifiedDevices: true,
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
let capturedSettings: CollectStrategy | undefined = undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
capturedSettings = undefined;
|
||||
mockOlmMachine.shareRoomKey.mockImplementationOnce(async (roomId, users, encryptionSettings) => {
|
||||
capturedSettings = encryptionSettings.sharingStrategy;
|
||||
});
|
||||
});
|
||||
|
||||
it.each(testCases)(
|
||||
"prepareForEncryption should properly set sharing strategy based on crypto mode: %s",
|
||||
async (_, { mode, expectedStrategy, globalBlacklistUnverifiedDevices }) => {
|
||||
await roomEncryptor.prepareForEncryption(globalBlacklistUnverifiedDevices, mode);
|
||||
expect(mockOlmMachine.shareRoomKey).toHaveBeenCalled();
|
||||
expect(capturedSettings).toBeDefined();
|
||||
expect(expectedStrategy.eq(capturedSettings!)).toBeTruthy();
|
||||
},
|
||||
);
|
||||
|
||||
it.each(testCases)(
|
||||
"encryptEvent should properly set sharing strategy based on crypto mode: %s",
|
||||
async (_, { mode, expectedStrategy, globalBlacklistUnverifiedDevices }) => {
|
||||
await roomEncryptor.encryptEvent(createMockEvent("Hello"), globalBlacklistUnverifiedDevices, mode);
|
||||
expect(mockOlmMachine.shareRoomKey).toHaveBeenCalled();
|
||||
expect(capturedSettings).toBeDefined();
|
||||
expect(expectedStrategy.eq(capturedSettings!)).toBeTruthy();
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,8 @@ import { Mocked } from "jest-mock";
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-wasm";
|
||||
|
||||
import { CryptoEvent, HttpApiEvent, HttpApiEventHandlerMap, MatrixHttpApi, TypedEventEmitter } from "../../../src";
|
||||
import { HttpApiEvent, HttpApiEventHandlerMap, MatrixHttpApi, TypedEventEmitter } from "../../../src";
|
||||
import { CryptoEvent } from "../../../src/crypto-api/index.ts";
|
||||
import { OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor";
|
||||
import * as testData from "../../test-utils/test-data";
|
||||
import * as TestData from "../../test-utils/test-data";
|
||||
|
||||
@@ -30,7 +30,6 @@ import fetchMock from "fetch-mock-jest";
|
||||
import { RustCrypto } from "../../../src/rust-crypto/rust-crypto";
|
||||
import { initRustCrypto } from "../../../src/rust-crypto";
|
||||
import {
|
||||
CryptoEvent,
|
||||
Device,
|
||||
DeviceVerification,
|
||||
encodeBase64,
|
||||
@@ -69,8 +68,9 @@ import { logger } from "../../../src/logger";
|
||||
import { OutgoingRequestsManager } from "../../../src/rust-crypto/OutgoingRequestsManager";
|
||||
import { ClientEvent, ClientEventHandlerMap } from "../../../src/client";
|
||||
import { Curve25519AuthData } from "../../../src/crypto-api/keybackup";
|
||||
import { encryptAES } from "../../../src/crypto/aes";
|
||||
import encryptAESSecretStorageItem from "../../../src/utils/encryptAESSecretStorageItem.ts";
|
||||
import { CryptoStore, SecretStorePrivateKeys } from "../../../src/crypto/store/base";
|
||||
import { CryptoEvent } from "../../../src/crypto-api/index.ts";
|
||||
|
||||
const TEST_USER = "@alice:example.com";
|
||||
const TEST_DEVICE_ID = "TEST_DEVICE";
|
||||
@@ -425,7 +425,7 @@ describe("initRustCrypto", () => {
|
||||
}, 10000);
|
||||
|
||||
async function encryptAndStoreSecretKey(type: string, key: Uint8Array, pickleKey: string, store: CryptoStore) {
|
||||
const encryptedKey = await encryptAES(encodeBase64(key), Buffer.from(pickleKey), type);
|
||||
const encryptedKey = await encryptAESSecretStorageItem(encodeBase64(key), Buffer.from(pickleKey), type);
|
||||
store.storeSecretStorePrivateKey(undefined, type as keyof SecretStorePrivateKeys, encryptedKey);
|
||||
}
|
||||
|
||||
@@ -1362,13 +1362,52 @@ describe("RustCrypto", () => {
|
||||
});
|
||||
|
||||
it("returns a verified UserVerificationStatus when the UserIdentity is verified", async () => {
|
||||
olmMachine.getIdentity.mockResolvedValue({ free: jest.fn(), isVerified: jest.fn().mockReturnValue(true) });
|
||||
olmMachine.getIdentity.mockResolvedValue({
|
||||
free: jest.fn(),
|
||||
isVerified: jest.fn().mockReturnValue(true),
|
||||
wasPreviouslyVerified: jest.fn().mockReturnValue(true),
|
||||
});
|
||||
|
||||
const userVerificationStatus = await rustCrypto.getUserVerificationStatus(testData.TEST_USER_ID);
|
||||
expect(userVerificationStatus.isVerified()).toBeTruthy();
|
||||
expect(userVerificationStatus.isTofu()).toBeFalsy();
|
||||
expect(userVerificationStatus.isCrossSigningVerified()).toBeTruthy();
|
||||
expect(userVerificationStatus.wasCrossSigningVerified()).toBeFalsy();
|
||||
expect(userVerificationStatus.wasCrossSigningVerified()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("pinCurrentIdentity", () => {
|
||||
let rustCrypto: RustCrypto;
|
||||
let olmMachine: Mocked<RustSdkCryptoJs.OlmMachine>;
|
||||
|
||||
beforeEach(() => {
|
||||
olmMachine = {
|
||||
getIdentity: jest.fn(),
|
||||
} as unknown as Mocked<RustSdkCryptoJs.OlmMachine>;
|
||||
rustCrypto = new RustCrypto(
|
||||
logger,
|
||||
olmMachine,
|
||||
{} as MatrixClient["http"],
|
||||
TEST_USER,
|
||||
TEST_DEVICE_ID,
|
||||
{} as ServerSideSecretStorage,
|
||||
{} as CryptoCallbacks,
|
||||
);
|
||||
});
|
||||
|
||||
it("throws an error for an unknown user", async () => {
|
||||
await expect(rustCrypto.pinCurrentUserIdentity("@alice:example.com")).rejects.toThrow(
|
||||
"Cannot pin identity of unknown user",
|
||||
);
|
||||
});
|
||||
|
||||
it("throws an error for our own user", async () => {
|
||||
const ownIdentity = new RustSdkCryptoJs.OwnUserIdentity();
|
||||
olmMachine.getIdentity.mockResolvedValue(ownIdentity);
|
||||
|
||||
await expect(rustCrypto.pinCurrentUserIdentity("@alice:example.com")).rejects.toThrow(
|
||||
"Cannot pin identity of own user",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1554,6 +1593,124 @@ describe("RustCrypto", () => {
|
||||
await expect(rustCrypto.exportSecretsBundle()).resolves.toEqual(expect.objectContaining(bundle));
|
||||
});
|
||||
});
|
||||
|
||||
describe("encryptToDeviceMessages", () => {
|
||||
let rustCrypto: RustCrypto;
|
||||
let testOlmMachine: RustSdkCryptoJs.OlmMachine;
|
||||
|
||||
beforeEach(async () => {
|
||||
testOlmMachine = await OlmMachine.initialize(
|
||||
new RustSdkCryptoJs.UserId(testData.TEST_USER_ID),
|
||||
new RustSdkCryptoJs.DeviceId(testData.TEST_DEVICE_ID),
|
||||
);
|
||||
jest.spyOn(OlmMachine, "initFromStore").mockResolvedValue(testOlmMachine);
|
||||
rustCrypto = await makeTestRustCrypto();
|
||||
expect(OlmMachine.initFromStore).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
testOlmMachine?.free();
|
||||
});
|
||||
|
||||
const payload = { hello: "world" };
|
||||
|
||||
it("returns empty batch if devices not known", async () => {
|
||||
const getMissingSessions = jest.spyOn(testOlmMachine, "getMissingSessions");
|
||||
const getDevice = jest.spyOn(testOlmMachine, "getDevice");
|
||||
const batch = await rustCrypto.encryptToDeviceMessages(
|
||||
"m.test.type",
|
||||
[
|
||||
{ deviceId: "AAA", userId: "@user1:domain" },
|
||||
{ deviceId: "BBB", userId: "@user1:domain" },
|
||||
{ deviceId: "CCC", userId: "@user2:domain" },
|
||||
],
|
||||
payload,
|
||||
);
|
||||
expect(getMissingSessions.mock.calls[0][0].length).toBe(2);
|
||||
expect(getDevice).toHaveBeenCalledTimes(3);
|
||||
expect(batch?.eventType).toEqual("m.room.encrypted");
|
||||
expect(batch?.batch).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns encrypted batch for known devices", async () => {
|
||||
// Make m aware of another device, and get some OTK to be able to establish a session.
|
||||
await testOlmMachine.markRequestAsSent(
|
||||
"foo",
|
||||
RustSdkCryptoJs.RequestType.KeysQuery,
|
||||
JSON.stringify({
|
||||
device_keys: {
|
||||
"@example:localhost": {
|
||||
AFGUOBTZWM: {
|
||||
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
device_id: "AFGUOBTZWM",
|
||||
keys: {
|
||||
"curve25519:AFGUOBTZWM": "boYjDpaC+7NkECQEeMh5dC+I1+AfriX0VXG2UV7EUQo",
|
||||
"ed25519:AFGUOBTZWM": "NayrMQ33ObqMRqz6R9GosmHdT6HQ6b/RX/3QlZ2yiec",
|
||||
},
|
||||
signatures: {
|
||||
"@example:localhost": {
|
||||
"ed25519:AFGUOBTZWM":
|
||||
"RoSWvru1jj6fs2arnTedWsyIyBmKHMdOu7r9gDi0BZ61h9SbCK2zLXzuJ9ZFLao2VvA0yEd7CASCmDHDLYpXCA",
|
||||
},
|
||||
},
|
||||
user_id: "@example:localhost",
|
||||
unsigned: {
|
||||
device_display_name: "rust-sdk",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
failures: {},
|
||||
}),
|
||||
);
|
||||
|
||||
await testOlmMachine.markRequestAsSent(
|
||||
"bar",
|
||||
RustSdkCryptoJs.RequestType.KeysClaim,
|
||||
JSON.stringify({
|
||||
one_time_keys: {
|
||||
"@example:localhost": {
|
||||
AFGUOBTZWM: {
|
||||
"signed_curve25519:AAAABQ": {
|
||||
key: "9IGouMnkB6c6HOd4xUsNv4i3Dulb4IS96TzDordzOws",
|
||||
signatures: {
|
||||
"@example:localhost": {
|
||||
"ed25519:AFGUOBTZWM":
|
||||
"2bvUbbmJegrV0eVP/vcJKuIWC3kud+V8+C0dZtg4dVovOSJdTP/iF36tQn2bh5+rb9xLlSeztXBdhy4c+LiOAg",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
failures: {},
|
||||
}),
|
||||
);
|
||||
|
||||
const batch = await rustCrypto.encryptToDeviceMessages(
|
||||
"m.test.type",
|
||||
[
|
||||
{ deviceId: "AAA", userId: "@user1:domain" },
|
||||
{ deviceId: "BBB", userId: "@user1:domain" },
|
||||
{ deviceId: "CCC", userId: "@user2:domain" },
|
||||
{ deviceId: "AFGUOBTZWM", userId: "@example:localhost" },
|
||||
],
|
||||
payload,
|
||||
);
|
||||
expect(batch?.eventType).toEqual("m.room.encrypted");
|
||||
expect(batch?.batch.length).toEqual(1);
|
||||
expect(batch?.batch[0].deviceId).toEqual("AFGUOBTZWM");
|
||||
expect(batch?.batch[0].userId).toEqual("@example:localhost");
|
||||
expect(batch?.batch[0].payload).toEqual(
|
||||
expect.objectContaining({
|
||||
"algorithm": "m.olm.v1.curve25519-aes-sha2",
|
||||
"ciphertext": expect.any(Object),
|
||||
"org.matrix.msgid": expect.any(String),
|
||||
"sender_key": expect.any(String),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/** Build a MatrixHttpApi instance */
|
||||
|
||||
@@ -98,7 +98,7 @@ describe("secret-storage", () => {
|
||||
} else if (secretName == "secretE") {
|
||||
return { aaaa: {}, bbbb: {} };
|
||||
} else {
|
||||
null;
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
getDefaultKeyId: jest.fn().mockResolvedValue("aaaa"),
|
||||
|
||||
@@ -12,7 +12,7 @@ jest.useFakeTimers();
|
||||
|
||||
describe("MatrixScheduler", function () {
|
||||
let scheduler: MatrixScheduler<Record<string, boolean>>;
|
||||
let retryFn: Function | null;
|
||||
let retryFn: ((event: MatrixEvent | null, attempt: number, err: MatrixError) => number) | null;
|
||||
let queueFn: ((event: MatrixEvent) => string | null) | null;
|
||||
let deferred: IDeferred<Record<string, boolean>>;
|
||||
const roomId = "!foo:bar";
|
||||
|
||||
@@ -18,6 +18,7 @@ import { Mocked } from "jest-mock";
|
||||
|
||||
import {
|
||||
AccountDataClient,
|
||||
calculateKeyCheck,
|
||||
PassphraseInfo,
|
||||
SecretStorageCallbacks,
|
||||
SecretStorageKeyDescriptionAesV1,
|
||||
@@ -25,7 +26,6 @@ import {
|
||||
ServerSideSecretStorageImpl,
|
||||
trimTrailingEquals,
|
||||
} from "../../src/secret-storage";
|
||||
import { calculateKeyCheck } from "../../src/crypto/aes";
|
||||
import { randomString } from "../../src/randomstring";
|
||||
|
||||
describe("ServerSideSecretStorageImpl", function () {
|
||||
|
||||
@@ -1228,7 +1228,7 @@ describe("Call", function () {
|
||||
});
|
||||
|
||||
describe("Screen sharing", () => {
|
||||
const waitNegotiateFunc = (resolve: Function): void => {
|
||||
const waitNegotiateFunc = (resolve: () => void): void => {
|
||||
mockSendEvent.mockImplementationOnce(() => {
|
||||
// Note that the peer connection here is a dummy one and always returns
|
||||
// dummy SDP, so there's not much point returning the content: the SDP will
|
||||
|
||||
@@ -101,7 +101,7 @@ describe("CallFeed", () => {
|
||||
it.each([
|
||||
[CallState.Connected, true],
|
||||
[CallState.Connecting, false],
|
||||
])("should react to call state, when !isLocal()", (state: CallState, expected: Boolean) => {
|
||||
])("should react to call state, when !isLocal()", (state: CallState, expected: boolean) => {
|
||||
call.emit(CallEvent.State, state, CallState.InviteSent, call.typed());
|
||||
|
||||
expect(feed.connected).toBe(expected);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* An AES-encrypted secret storage payload.
|
||||
* See https://spec.matrix.org/v1.11/client-server-api/#msecret_storagev1aes-hmac-sha2-1
|
||||
*/
|
||||
export interface AESEncryptedSecretStoragePayload {
|
||||
[key: string]: any; // extensible
|
||||
/** the initialization vector in base64 */
|
||||
iv: string;
|
||||
/** the ciphertext in base64 */
|
||||
ciphertext: string;
|
||||
/** the HMAC in base64 */
|
||||
mac: string;
|
||||
}
|
||||
+2
-2
@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { UnstableValue } from "../NamespacedValue";
|
||||
import { IClientWellKnown } from "../client";
|
||||
import { UnstableValue } from "../NamespacedValue.ts";
|
||||
import { IClientWellKnown } from "../client.ts";
|
||||
|
||||
// disable lint because these are wire responses
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
@@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { RelatesToRelationship, REFERENCE_RELATION } from "./extensible_events";
|
||||
import { UnstableValue } from "../NamespacedValue";
|
||||
import { MAssetEvent, MLocationEvent, MTimestampEvent } from "./location";
|
||||
import { RelatesToRelationship, REFERENCE_RELATION } from "./extensible_events.ts";
|
||||
import { UnstableValue } from "../NamespacedValue.ts";
|
||||
import { MAssetEvent, MLocationEvent, MTimestampEvent } from "./location.ts";
|
||||
|
||||
/**
|
||||
* Beacon info and beacon event types as described in MSC3672
|
||||
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ISignatures } from "./signed";
|
||||
import type { ISignatures } from "./signed.ts";
|
||||
|
||||
export type OlmGroupSessionExtraData = {
|
||||
untrusted?: boolean;
|
||||
@@ -22,7 +22,7 @@ export type OlmGroupSessionExtraData = {
|
||||
};
|
||||
|
||||
// Backwards compatible re-export
|
||||
export type { EventDecryptionResult as IEventDecryptionResult } from "../common-crypto/CryptoBackend";
|
||||
export type { EventDecryptionResult as IEventDecryptionResult } from "../common-crypto/CryptoBackend.ts";
|
||||
|
||||
interface Extensible {
|
||||
[key: string]: any;
|
||||
|
||||
+11
-11
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { NamespacedValue, UnstableValue } from "../NamespacedValue";
|
||||
import { NamespacedValue, UnstableValue } from "../NamespacedValue.ts";
|
||||
import {
|
||||
PolicyRuleEventContent,
|
||||
RoomAvatarEventContent,
|
||||
@@ -34,16 +34,16 @@ import {
|
||||
RoomTopicEventContent,
|
||||
SpaceChildEventContent,
|
||||
SpaceParentEventContent,
|
||||
} from "./state_events";
|
||||
} from "./state_events.ts";
|
||||
import {
|
||||
ExperimentalGroupCallRoomMemberState,
|
||||
IGroupCallRoomMemberState,
|
||||
IGroupCallRoomState,
|
||||
} from "../webrtc/groupCall";
|
||||
import { MSC3089EventContent } from "../models/MSC3089Branch";
|
||||
import { M_BEACON, M_BEACON_INFO, MBeaconEventContent, MBeaconInfoEventContent } from "./beacon";
|
||||
import { XOR } from "./common";
|
||||
import { ReactionEventContent, RoomMessageEventContent, StickerEventContent } from "./events";
|
||||
} from "../webrtc/groupCall.ts";
|
||||
import { MSC3089EventContent } from "../models/MSC3089Branch.ts";
|
||||
import { M_BEACON, M_BEACON_INFO, MBeaconEventContent, MBeaconInfoEventContent } from "./beacon.ts";
|
||||
import { XOR } from "./common.ts";
|
||||
import { ReactionEventContent, RoomMessageEventContent, StickerEventContent } from "./events.ts";
|
||||
import {
|
||||
MCallAnswer,
|
||||
MCallBase,
|
||||
@@ -54,10 +54,10 @@ import {
|
||||
MCallSelectAnswer,
|
||||
SDPStreamMetadata,
|
||||
SDPStreamMetadataKey,
|
||||
} from "../webrtc/callEventTypes";
|
||||
import { EncryptionKeysEventContent, ICallNotifyContent } from "../matrixrtc/types";
|
||||
import { M_POLL_END, M_POLL_START, PollEndEventContent, PollStartEventContent } from "./polls";
|
||||
import { SessionMembershipData } from "../matrixrtc/CallMembership";
|
||||
} from "../webrtc/callEventTypes.ts";
|
||||
import { EncryptionKeysEventContent, ICallNotifyContent } from "../matrixrtc/types.ts";
|
||||
import { M_POLL_END, M_POLL_START, PollEndEventContent, PollStartEventContent } from "./polls.ts";
|
||||
import { SessionMembershipData } from "../matrixrtc/CallMembership.ts";
|
||||
|
||||
export enum EventType {
|
||||
// Room state events
|
||||
|
||||
@@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MsgType, RelationType } from "./event";
|
||||
import { FileInfo, ImageInfo, MediaEventContent } from "./media";
|
||||
import { XOR } from "./common";
|
||||
import { MsgType, RelationType } from "./event.ts";
|
||||
import { FileInfo, ImageInfo, MediaEventContent } from "./media.ts";
|
||||
import { XOR } from "./common.ts";
|
||||
|
||||
interface BaseTimelineEvent {
|
||||
"body": string;
|
||||
|
||||
@@ -16,7 +16,7 @@ limitations under the License.
|
||||
|
||||
import { EitherAnd, NamespacedValue, Optional, UnstableValue } from "matrix-events-sdk";
|
||||
|
||||
import { isProvided } from "../extensible_events_v1/utilities";
|
||||
import { isProvided } from "../extensible_events_v1/utilities.ts";
|
||||
|
||||
// Types and utilities for MSC1767: Extensible events (version 1) in Matrix
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ limitations under the License.
|
||||
// Types for MSC3488 - m.location: Extending events with location data
|
||||
import { EitherAnd } from "matrix-events-sdk";
|
||||
|
||||
import { UnstableValue } from "../NamespacedValue";
|
||||
import { M_TEXT } from "./extensible_events";
|
||||
import { UnstableValue } from "../NamespacedValue.ts";
|
||||
import { M_TEXT } from "./extensible_events.ts";
|
||||
|
||||
export enum LocationAssetType {
|
||||
Self = "m.self",
|
||||
|
||||
+30
-37
@@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MsgType } from "../@types/event";
|
||||
import { MsgType } from "../@types/event.ts";
|
||||
|
||||
/**
|
||||
* Information on encrypted media attachments.
|
||||
*
|
||||
* Used within `m.room.message` events that reference files, such as `m.file` and `m.image`.
|
||||
*
|
||||
* @see https://spec.matrix.org/v1.9/client-server-api/#extensions-to-mroommessage-msgtypes
|
||||
* @see https://spec.matrix.org/v1.11/client-server-api/#extensions-to-mroommessage-msgtypes
|
||||
*/
|
||||
export interface EncryptedFile {
|
||||
/**
|
||||
@@ -84,13 +84,13 @@ interface BaseInfo {
|
||||
*
|
||||
* Used within `m.room.message` events that reference files.
|
||||
*
|
||||
* @see https://spec.matrix.org/v1.9/client-server-api/#mfile
|
||||
* @see https://spec.matrix.org/v1.11/client-server-api/#mfile
|
||||
*/
|
||||
export interface FileInfo extends BaseInfo {
|
||||
/**
|
||||
* Information on the encrypted thumbnail file, as specified in End-to-end encryption.
|
||||
* Only present if the thumbnail is encrypted.
|
||||
* @see https://spec.matrix.org/v1.9/client-server-api/#sending-encrypted-attachments
|
||||
* @see https://spec.matrix.org/v1.11/client-server-api/#sending-encrypted-attachments
|
||||
*/
|
||||
thumbnail_file?: EncryptedFile;
|
||||
/**
|
||||
@@ -108,7 +108,7 @@ export interface FileInfo extends BaseInfo {
|
||||
*
|
||||
* Used within `m.room.message` events that reference images.
|
||||
*
|
||||
* @see https://spec.matrix.org/v1.9/client-server-api/#mimage
|
||||
* @see https://spec.matrix.org/v1.11/client-server-api/#mimage
|
||||
*/
|
||||
export interface ImageInfo extends FileInfo, ThumbnailInfo {}
|
||||
|
||||
@@ -117,7 +117,7 @@ export interface ImageInfo extends FileInfo, ThumbnailInfo {}
|
||||
*
|
||||
* Used within `m.room.message` events that reference audio files.
|
||||
*
|
||||
* @see https://spec.matrix.org/v1.9/client-server-api/#maudio
|
||||
* @see https://spec.matrix.org/v1.11/client-server-api/#maudio
|
||||
*/
|
||||
export interface AudioInfo extends BaseInfo {
|
||||
/**
|
||||
@@ -131,7 +131,7 @@ export interface AudioInfo extends BaseInfo {
|
||||
*
|
||||
* Used within `m.room.message` events that reference video files.
|
||||
*
|
||||
* @see https://spec.matrix.org/v1.9/client-server-api/#mvideo
|
||||
* @see https://spec.matrix.org/v1.11/client-server-api/#mvideo
|
||||
*/
|
||||
export interface VideoInfo extends AudioInfo, ImageInfo {
|
||||
/**
|
||||
@@ -148,30 +148,39 @@ export type MediaEventInfo = FileInfo | ImageInfo | AudioInfo | VideoInfo;
|
||||
interface BaseContent {
|
||||
/**
|
||||
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
|
||||
* @see https://spec.matrix.org/v1.9/client-server-api/#sending-encrypted-attachments
|
||||
* @see https://spec.matrix.org/v1.11/client-server-api/#sending-encrypted-attachments
|
||||
*/
|
||||
file?: EncryptedFile;
|
||||
/**
|
||||
* Required if the file is unencrypted. The URL (typically mxc:// URI) to the file.
|
||||
*/
|
||||
url?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Content format of media events with msgtype `m.file`
|
||||
*
|
||||
* @see https://spec.matrix.org/v1.9/client-server-api/#mfile
|
||||
*/
|
||||
export interface FileContent extends BaseContent {
|
||||
/**
|
||||
* A human-readable description of the file.
|
||||
* This is recommended to be the filename of the original upload.
|
||||
* If filename is not set or the value of both properties are identical,
|
||||
* this is the filename of the original upload. Otherwise, this is a
|
||||
* caption for the file.
|
||||
*/
|
||||
body: string;
|
||||
/**
|
||||
* The original filename of the uploaded file.
|
||||
*/
|
||||
filename?: string;
|
||||
/**
|
||||
* The format used in the `formatted_body`.
|
||||
*/
|
||||
format?: "org.matrix.custom.html";
|
||||
/**
|
||||
* The formatted version of the `body`, when it acts as a caption. This is required if `format` is specified.
|
||||
*/
|
||||
formatted_body?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Content format of media events with msgtype `m.file`
|
||||
*
|
||||
* @see https://spec.matrix.org/v1.11/client-server-api/#mfile
|
||||
*/
|
||||
export interface FileContent extends BaseContent {
|
||||
/**
|
||||
* Information about the file referred to in url.
|
||||
*/
|
||||
@@ -185,15 +194,9 @@ export interface FileContent extends BaseContent {
|
||||
/**
|
||||
* Content format of media events with msgtype `m.image`
|
||||
*
|
||||
* @see https://spec.matrix.org/v1.9/client-server-api/#mimage
|
||||
* @see https://spec.matrix.org/v1.11/client-server-api/#mimage
|
||||
*/
|
||||
export interface ImageContent extends BaseContent {
|
||||
/**
|
||||
* A textual representation of the image.
|
||||
* This could be the alt text of the image, the filename of the image,
|
||||
* or some kind of content description for accessibility e.g. ‘image attachment’.
|
||||
*/
|
||||
body: string;
|
||||
/**
|
||||
* Metadata about the image referred to in url.
|
||||
*/
|
||||
@@ -207,14 +210,9 @@ export interface ImageContent extends BaseContent {
|
||||
/**
|
||||
* Content format of media events with msgtype `m.audio`
|
||||
*
|
||||
* @see https://spec.matrix.org/v1.9/client-server-api/#maudio
|
||||
* @see https://spec.matrix.org/v1.11/client-server-api/#maudio
|
||||
*/
|
||||
export interface AudioContent extends BaseContent {
|
||||
/**
|
||||
* A description of the audio e.g. ‘Bee Gees - Stayin’ Alive’,
|
||||
* or some kind of content description for accessibility e.g. ‘audio attachment’.
|
||||
*/
|
||||
body: string;
|
||||
/**
|
||||
* Metadata for the audio clip referred to in url.
|
||||
*/
|
||||
@@ -228,14 +226,9 @@ export interface AudioContent extends BaseContent {
|
||||
/**
|
||||
* Content format of media events with msgtype `m.video`
|
||||
*
|
||||
* @see https://spec.matrix.org/v1.9/client-server-api/#mvideo
|
||||
* @see https://spec.matrix.org/v1.11/client-server-api/#mvideo
|
||||
*/
|
||||
export interface VideoContent extends BaseContent {
|
||||
/**
|
||||
* A description of the video e.g. ‘Gangnam style’,
|
||||
* or some kind of content description for accessibility e.g. ‘video attachment’.
|
||||
*/
|
||||
body: string;
|
||||
/**
|
||||
* Metadata about the video clip referred to in url.
|
||||
*/
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ import {
|
||||
REFERENCE_RELATION,
|
||||
RelatesToRelationship,
|
||||
TSNamespace,
|
||||
} from "./extensible_events";
|
||||
} from "./extensible_events.ts";
|
||||
|
||||
/**
|
||||
* Identifier for a disclosed poll.
|
||||
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { AuthDict } from "../interactive-auth";
|
||||
import { AuthDict } from "../interactive-auth.ts";
|
||||
|
||||
/**
|
||||
* The request body of a call to `POST /_matrix/client/v3/register`.
|
||||
|
||||
+11
-13
@@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { IContent, IEvent } from "../models/event";
|
||||
import { Preset, Visibility } from "./partials";
|
||||
import { IEventWithRoomId, SearchKey } from "./search";
|
||||
import { IRoomEventFilter } from "../filter";
|
||||
import { Direction } from "../models/event-timeline";
|
||||
import { PushRuleAction } from "./PushRules";
|
||||
import { IRoomEvent } from "../sync-accumulator";
|
||||
import { EventType, RelationType, RoomType } from "./event";
|
||||
import { IContent, IEvent } from "../models/event.ts";
|
||||
import { Preset, Visibility } from "./partials.ts";
|
||||
import { IEventWithRoomId, SearchKey } from "./search.ts";
|
||||
import { IRoomEventFilter } from "../filter.ts";
|
||||
import { Direction } from "../models/event-timeline.ts";
|
||||
import { PushRuleAction } from "./PushRules.ts";
|
||||
import { IRoomEvent } from "../sync-accumulator.ts";
|
||||
import { EventType, RelationType, RoomType } from "./event.ts";
|
||||
|
||||
// allow camelcase as these are things that go onto the wire
|
||||
/* eslint-disable camelcase */
|
||||
@@ -111,18 +111,16 @@ type DelayedPartialTimelineEvent = {
|
||||
|
||||
type DelayedPartialStateEvent = DelayedPartialTimelineEvent & {
|
||||
state_key: string;
|
||||
transaction_id: string;
|
||||
};
|
||||
|
||||
type DelayedPartialEvent = DelayedPartialTimelineEvent | DelayedPartialStateEvent;
|
||||
|
||||
export type DelayedEventInfo = {
|
||||
delayed_events: DelayedPartialEvent &
|
||||
delayed_events: (DelayedPartialEvent &
|
||||
SendDelayedEventResponse &
|
||||
SendDelayedEventRequestOpts &
|
||||
{
|
||||
SendDelayedEventRequestOpts & {
|
||||
running_since: number;
|
||||
}[];
|
||||
})[];
|
||||
next_batch?: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@ limitations under the License.
|
||||
|
||||
// Types relating to the /search API
|
||||
|
||||
import { IRoomEvent, IStateEvent } from "../sync-accumulator";
|
||||
import { IRoomEventFilter } from "../filter";
|
||||
import { SearchResult } from "../models/search-result";
|
||||
import { IRoomEvent, IStateEvent } from "../sync-accumulator.ts";
|
||||
import { IRoomEventFilter } from "../filter.ts";
|
||||
import { SearchResult } from "../models/search-result.ts";
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
export interface IEventWithRoomId extends IRoomEvent {
|
||||
|
||||
@@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { IPublicRoomsChunkRoom } from "../client";
|
||||
import { RoomType } from "./event";
|
||||
import { IStrippedState } from "../sync-accumulator";
|
||||
import { IPublicRoomsChunkRoom } from "../client.ts";
|
||||
import { RoomType } from "./event.ts";
|
||||
import { IStrippedState } from "../sync-accumulator.ts";
|
||||
|
||||
// Types relating to Rooms of type `m.space` and related APIs
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { RoomType } from "./event";
|
||||
import { GuestAccess, HistoryVisibility, JoinRule, RestrictedAllowType } from "./partials";
|
||||
import { ImageInfo } from "./media";
|
||||
import { PolicyRecommendation } from "../models/invites-ignorer";
|
||||
import { RoomType } from "./event.ts";
|
||||
import { GuestAccess, HistoryVisibility, JoinRule, RestrictedAllowType } from "./partials.ts";
|
||||
import { ImageInfo } from "./media.ts";
|
||||
import { PolicyRecommendation } from "../models/invites-ignorer.ts";
|
||||
|
||||
export interface RoomCanonicalAliasEventContent {
|
||||
alias?: string;
|
||||
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { IdServerUnbindResult } from "./partials";
|
||||
import { IdServerUnbindResult } from "./partials.ts";
|
||||
|
||||
// Types relating to Synapse Admin APIs
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { ServerControlledNamespacedValue } from "../NamespacedValue";
|
||||
import { ServerControlledNamespacedValue } from "../NamespacedValue.ts";
|
||||
|
||||
/**
|
||||
* https://github.com/matrix-org/matrix-doc/pull/3773
|
||||
|
||||
+2
-2
@@ -16,8 +16,8 @@ limitations under the License.
|
||||
|
||||
import { EitherAnd } from "matrix-events-sdk";
|
||||
|
||||
import { UnstableValue } from "../NamespacedValue";
|
||||
import { IMessageRendering } from "./extensible_events";
|
||||
import { UnstableValue } from "../NamespacedValue.ts";
|
||||
import { IMessageRendering } from "./extensible_events.ts";
|
||||
|
||||
/**
|
||||
* Extensible topic event type based on MSC3765
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { AuthDict, IAuthData } from "../interactive-auth";
|
||||
import { AuthDict, IAuthData } from "../interactive-auth.ts";
|
||||
|
||||
/**
|
||||
* Helper type to represent HTTP request body for a UIA enabled endpoint
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ limitations under the License.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
import { ListenerMap, TypedEventEmitter } from "./models/typed-event-emitter";
|
||||
import { ListenerMap, TypedEventEmitter } from "./models/typed-event-emitter.ts";
|
||||
|
||||
export class ReEmitter {
|
||||
public constructor(private readonly target: EventEmitter) {}
|
||||
|
||||
@@ -14,14 +14,19 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { ToDeviceMessageId } from "./@types/event";
|
||||
import { logger } from "./logger";
|
||||
import { MatrixClient, ClientEvent } from "./client";
|
||||
import { MatrixError } from "./http-api";
|
||||
import { IndexedToDeviceBatch, ToDeviceBatch, ToDeviceBatchWithTxnId, ToDevicePayload } from "./models/ToDeviceMessage";
|
||||
import { MatrixScheduler } from "./scheduler";
|
||||
import { SyncState } from "./sync";
|
||||
import { MapWithDefault } from "./utils";
|
||||
import { ToDeviceMessageId } from "./@types/event.ts";
|
||||
import { logger } from "./logger.ts";
|
||||
import { MatrixClient, ClientEvent } from "./client.ts";
|
||||
import { MatrixError } from "./http-api/index.ts";
|
||||
import {
|
||||
IndexedToDeviceBatch,
|
||||
ToDeviceBatch,
|
||||
ToDeviceBatchWithTxnId,
|
||||
ToDevicePayload,
|
||||
} from "./models/ToDeviceMessage.ts";
|
||||
import { MatrixScheduler } from "./scheduler.ts";
|
||||
import { SyncState } from "./sync.ts";
|
||||
import { MapWithDefault } from "./utils.ts";
|
||||
|
||||
const MAX_BATCH_SIZE = 20;
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { IClientWellKnown, IWellKnownConfig, IServerVersions } from "./client";
|
||||
import { logger } from "./logger";
|
||||
import { MatrixError, Method, timeoutSignal } from "./http-api";
|
||||
import { SUPPORTED_MATRIX_VERSIONS } from "./version-support";
|
||||
import { IClientWellKnown, IWellKnownConfig, IServerVersions } from "./client.ts";
|
||||
import { logger } from "./logger.ts";
|
||||
import { MatrixError, Method, timeoutSignal } from "./http-api/index.ts";
|
||||
import { SUPPORTED_MATRIX_VERSIONS } from "./version-support.ts";
|
||||
|
||||
// Dev note: Auto discovery is part of the spec.
|
||||
// See: https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
|
||||
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import * as matrixcs from "./matrix";
|
||||
import * as matrixcs from "./matrix.ts";
|
||||
|
||||
type BrowserMatrix = typeof matrixcs;
|
||||
declare global {
|
||||
@@ -33,12 +33,12 @@ globalThis.__js_sdk_entrypoint = true;
|
||||
let indexedDB: IDBFactory | undefined;
|
||||
try {
|
||||
indexedDB = globalThis.indexedDB;
|
||||
} catch (e) {}
|
||||
} catch {}
|
||||
|
||||
// if our browser (appears to) support indexeddb, use an indexeddb crypto store.
|
||||
if (indexedDB) {
|
||||
matrixcs.setCryptoStoreFactory(() => new matrixcs.IndexedDBCryptoStore(indexedDB!, "matrix-js-sdk:crypto"));
|
||||
}
|
||||
|
||||
export * from "./matrix";
|
||||
export * from "./matrix.ts";
|
||||
globalThis.matrixcs = matrixcs;
|
||||
|
||||
+389
-191
@@ -20,8 +20,8 @@ limitations under the License.
|
||||
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
|
||||
import type { IDeviceKeys, IMegolmSessionData, IOneTimeKey } from "./@types/crypto";
|
||||
import { ISyncStateData, SetPresence, SyncApi, SyncApiOptions, SyncState } from "./sync";
|
||||
import type { IDeviceKeys, IMegolmSessionData, IOneTimeKey } from "./@types/crypto.ts";
|
||||
import { ISyncStateData, SetPresence, SyncApi, SyncApiOptions, SyncState } from "./sync.ts";
|
||||
import {
|
||||
EventStatus,
|
||||
IContent,
|
||||
@@ -31,29 +31,29 @@ import {
|
||||
MatrixEventEvent,
|
||||
MatrixEventHandlerMap,
|
||||
PushDetails,
|
||||
} from "./models/event";
|
||||
import { StubStore } from "./store/stub";
|
||||
import { CallEvent, CallEventHandlerMap, createNewMatrixCall, MatrixCall, supportsMatrixCall } from "./webrtc/call";
|
||||
import { Filter, IFilterDefinition, IRoomEventFilter } from "./filter";
|
||||
import { CallEventHandler, CallEventHandlerEvent, CallEventHandlerEventHandlerMap } from "./webrtc/callEventHandler";
|
||||
} from "./models/event.ts";
|
||||
import { StubStore } from "./store/stub.ts";
|
||||
import { CallEvent, CallEventHandlerMap, createNewMatrixCall, MatrixCall, supportsMatrixCall } from "./webrtc/call.ts";
|
||||
import { Filter, IFilterDefinition, IRoomEventFilter } from "./filter.ts";
|
||||
import { CallEventHandler, CallEventHandlerEvent, CallEventHandlerEventHandlerMap } from "./webrtc/callEventHandler.ts";
|
||||
import {
|
||||
GroupCallEventHandler,
|
||||
GroupCallEventHandlerEvent,
|
||||
GroupCallEventHandlerEventHandlerMap,
|
||||
} from "./webrtc/groupCallEventHandler";
|
||||
import * as utils from "./utils";
|
||||
import { noUnsafeEventProps, QueryDict, replaceParam, safeSet, sleep } from "./utils";
|
||||
import { Direction, EventTimeline } from "./models/event-timeline";
|
||||
import { IActionsObject, PushProcessor } from "./pushprocessor";
|
||||
import { AutoDiscovery, AutoDiscoveryAction } from "./autodiscovery";
|
||||
import * as olmlib from "./crypto/olmlib";
|
||||
import { decodeBase64, encodeBase64, encodeUnpaddedBase64Url } from "./base64";
|
||||
import { IExportedDevice as IExportedOlmDevice } from "./crypto/OlmDevice";
|
||||
import { IOlmDevice } from "./crypto/algorithms/megolm";
|
||||
import { TypedReEmitter } from "./ReEmitter";
|
||||
import { IRoomEncryption } from "./crypto/RoomList";
|
||||
import { logger, Logger } from "./logger";
|
||||
import { SERVICE_TYPES } from "./service-types";
|
||||
} from "./webrtc/groupCallEventHandler.ts";
|
||||
import * as utils from "./utils.ts";
|
||||
import { noUnsafeEventProps, QueryDict, replaceParam, safeSet, sleep } from "./utils.ts";
|
||||
import { Direction, EventTimeline } from "./models/event-timeline.ts";
|
||||
import { IActionsObject, PushProcessor } from "./pushprocessor.ts";
|
||||
import { AutoDiscovery, AutoDiscoveryAction } from "./autodiscovery.ts";
|
||||
import * as olmlib from "./crypto/olmlib.ts";
|
||||
import { decodeBase64, encodeBase64, encodeUnpaddedBase64Url } from "./base64.ts";
|
||||
import { IExportedDevice as IExportedOlmDevice } from "./crypto/OlmDevice.ts";
|
||||
import { IOlmDevice } from "./crypto/algorithms/megolm.ts";
|
||||
import { TypedReEmitter } from "./ReEmitter.ts";
|
||||
import { IRoomEncryption } from "./crypto/RoomList.ts";
|
||||
import { logger, Logger } from "./logger.ts";
|
||||
import { SERVICE_TYPES } from "./service-types.ts";
|
||||
import {
|
||||
Body,
|
||||
ClientPrefix,
|
||||
@@ -73,24 +73,22 @@ import {
|
||||
Upload,
|
||||
UploadOpts,
|
||||
UploadResponse,
|
||||
} from "./http-api";
|
||||
} from "./http-api/index.ts";
|
||||
import {
|
||||
Crypto,
|
||||
CryptoEvent,
|
||||
CryptoEventHandlerMap,
|
||||
CryptoEvent as LegacyCryptoEvent,
|
||||
CryptoEventHandlerMap as LegacyCryptoEventHandlerMap,
|
||||
fixBackupKey,
|
||||
ICheckOwnCrossSigningTrustOpts,
|
||||
ICryptoCallbacks,
|
||||
IRoomKeyRequestBody,
|
||||
isCryptoAvailable,
|
||||
} from "./crypto";
|
||||
import { DeviceInfo } from "./crypto/deviceinfo";
|
||||
import { decodeRecoveryKey } from "./crypto/recoverykey";
|
||||
import { keyFromAuthData } from "./crypto/key_passphrase";
|
||||
import { User, UserEvent, UserEventHandlerMap } from "./models/user";
|
||||
import { getHttpUriForMxc } from "./content-repo";
|
||||
import { SearchResult } from "./models/search-result";
|
||||
import { DEHYDRATION_ALGORITHM, IDehydratedDevice, IDehydratedDeviceKeyInfo } from "./crypto/dehydration";
|
||||
} from "./crypto/index.ts";
|
||||
import { DeviceInfo } from "./crypto/deviceinfo.ts";
|
||||
import { User, UserEvent, UserEventHandlerMap } from "./models/user.ts";
|
||||
import { getHttpUriForMxc } from "./content-repo.ts";
|
||||
import { SearchResult } from "./models/search-result.ts";
|
||||
import { DEHYDRATION_ALGORITHM, IDehydratedDevice, IDehydratedDeviceKeyInfo } from "./crypto/dehydration.ts";
|
||||
import {
|
||||
IKeyBackupInfo,
|
||||
IKeyBackupPrepareOpts,
|
||||
@@ -98,21 +96,21 @@ import {
|
||||
IKeyBackupRestoreResult,
|
||||
IKeyBackupRoomSessions,
|
||||
IKeyBackupSession,
|
||||
} from "./crypto/keybackup";
|
||||
import { IIdentityServerProvider } from "./@types/IIdentityServerProvider";
|
||||
import { MatrixScheduler } from "./scheduler";
|
||||
import { BeaconEvent, BeaconEventHandlerMap } from "./models/beacon";
|
||||
import { AuthDict } from "./interactive-auth";
|
||||
import { IMinimalEvent, IRoomEvent, IStateEvent } from "./sync-accumulator";
|
||||
import { CrossSigningKey, ICreateSecretStorageOpts, IEncryptedEventInfo, IRecoveryKey } from "./crypto/api";
|
||||
import { EventTimelineSet } from "./models/event-timeline-set";
|
||||
import { VerificationRequest } from "./crypto/verification/request/VerificationRequest";
|
||||
import { VerificationBase as Verification } from "./crypto/verification/Base";
|
||||
import * as ContentHelpers from "./content-helpers";
|
||||
import { CrossSigningInfo, DeviceTrustLevel, ICacheCallbacks, UserTrustLevel } from "./crypto/CrossSigning";
|
||||
import { NotificationCountType, Room, RoomEvent, RoomEventHandlerMap, RoomNameState } from "./models/room";
|
||||
import { RoomMemberEvent, RoomMemberEventHandlerMap } from "./models/room-member";
|
||||
import { IPowerLevelsContent, RoomStateEvent, RoomStateEventHandlerMap } from "./models/room-state";
|
||||
} from "./crypto/keybackup.ts";
|
||||
import { IIdentityServerProvider } from "./@types/IIdentityServerProvider.ts";
|
||||
import { MatrixScheduler } from "./scheduler.ts";
|
||||
import { BeaconEvent, BeaconEventHandlerMap } from "./models/beacon.ts";
|
||||
import { AuthDict } from "./interactive-auth.ts";
|
||||
import { IMinimalEvent, IRoomEvent, IStateEvent } from "./sync-accumulator.ts";
|
||||
import { CrossSigningKey, ICreateSecretStorageOpts, IEncryptedEventInfo, IRecoveryKey } from "./crypto/api.ts";
|
||||
import { EventTimelineSet } from "./models/event-timeline-set.ts";
|
||||
import { VerificationRequest } from "./crypto/verification/request/VerificationRequest.ts";
|
||||
import { VerificationBase as Verification } from "./crypto/verification/Base.ts";
|
||||
import * as ContentHelpers from "./content-helpers.ts";
|
||||
import { CrossSigningInfo, DeviceTrustLevel, ICacheCallbacks, UserTrustLevel } from "./crypto/CrossSigning.ts";
|
||||
import { NotificationCountType, Room, RoomEvent, RoomEventHandlerMap, RoomNameState } from "./models/room.ts";
|
||||
import { RoomMemberEvent, RoomMemberEventHandlerMap } from "./models/room-member.ts";
|
||||
import { IPowerLevelsContent, RoomStateEvent, RoomStateEventHandlerMap } from "./models/room-state.ts";
|
||||
import {
|
||||
DelayedEventInfo,
|
||||
IAddThreePidOnlyBody,
|
||||
@@ -138,7 +136,7 @@ import {
|
||||
SendDelayedEventRequestOpts,
|
||||
SendDelayedEventResponse,
|
||||
UpdateDelayedEventAction,
|
||||
} from "./@types/requests";
|
||||
} from "./@types/requests.ts";
|
||||
import {
|
||||
EventType,
|
||||
LOCAL_NOTIFICATION_SETTINGS_PREFIX,
|
||||
@@ -153,15 +151,22 @@ import {
|
||||
UNSTABLE_MSC3088_ENABLED,
|
||||
UNSTABLE_MSC3088_PURPOSE,
|
||||
UNSTABLE_MSC3089_TREE_SUBTYPE,
|
||||
} from "./@types/event";
|
||||
import { GuestAccess, HistoryVisibility, IdServerUnbindResult, JoinRule, Preset, Visibility } from "./@types/partials";
|
||||
import { EventMapper, eventMapperFor, MapperOpts } from "./event-mapper";
|
||||
import { randomString } from "./randomstring";
|
||||
import { BackupManager, IKeyBackup, IKeyBackupCheck, IPreparedKeyBackupVersion, TrustInfo } from "./crypto/backup";
|
||||
import { DEFAULT_TREE_POWER_LEVELS_TEMPLATE, MSC3089TreeSpace } from "./models/MSC3089TreeSpace";
|
||||
import { ISignatures } from "./@types/signed";
|
||||
import { IStore } from "./store";
|
||||
import { ISecretRequest } from "./crypto/SecretStorage";
|
||||
} from "./@types/event.ts";
|
||||
import {
|
||||
GuestAccess,
|
||||
HistoryVisibility,
|
||||
IdServerUnbindResult,
|
||||
JoinRule,
|
||||
Preset,
|
||||
Visibility,
|
||||
} from "./@types/partials.ts";
|
||||
import { EventMapper, eventMapperFor, MapperOpts } from "./event-mapper.ts";
|
||||
import { randomString } from "./randomstring.ts";
|
||||
import { BackupManager, IKeyBackup, IKeyBackupCheck, IPreparedKeyBackupVersion, TrustInfo } from "./crypto/backup.ts";
|
||||
import { DEFAULT_TREE_POWER_LEVELS_TEMPLATE, MSC3089TreeSpace } from "./models/MSC3089TreeSpace.ts";
|
||||
import { ISignatures } from "./@types/signed.ts";
|
||||
import { IStore } from "./store/index.ts";
|
||||
import { ISecretRequest } from "./crypto/SecretStorage.ts";
|
||||
import {
|
||||
IEventWithRoomId,
|
||||
ISearchRequestBody,
|
||||
@@ -169,9 +174,9 @@ import {
|
||||
ISearchResults,
|
||||
IStateEventWithRoomId,
|
||||
SearchOrderBy,
|
||||
} from "./@types/search";
|
||||
import { ISynapseAdminDeactivateResponse, ISynapseAdminWhoisResponse } from "./@types/synapse";
|
||||
import { IHierarchyRoom } from "./@types/spaces";
|
||||
} from "./@types/search.ts";
|
||||
import { ISynapseAdminDeactivateResponse, ISynapseAdminWhoisResponse } from "./@types/synapse.ts";
|
||||
import { IHierarchyRoom } from "./@types/spaces.ts";
|
||||
import {
|
||||
IPusher,
|
||||
IPusherRequest,
|
||||
@@ -181,11 +186,11 @@ import {
|
||||
PushRuleActionName,
|
||||
PushRuleKind,
|
||||
RuleId,
|
||||
} from "./@types/PushRules";
|
||||
import { IThreepid } from "./@types/threepids";
|
||||
import { CryptoStore, OutgoingRoomKeyRequest } from "./crypto/store/base";
|
||||
import { GroupCall, GroupCallIntent, GroupCallType, IGroupCallDataChannelOptions } from "./webrtc/groupCall";
|
||||
import { MediaHandler } from "./webrtc/mediaHandler";
|
||||
} from "./@types/PushRules.ts";
|
||||
import { IThreepid } from "./@types/threepids.ts";
|
||||
import { CryptoStore, OutgoingRoomKeyRequest } from "./crypto/store/base.ts";
|
||||
import { GroupCall, GroupCallIntent, GroupCallType, IGroupCallDataChannelOptions } from "./webrtc/groupCall.ts";
|
||||
import { MediaHandler } from "./webrtc/mediaHandler.ts";
|
||||
import {
|
||||
ILoginFlowsResponse,
|
||||
IRefreshTokenResponse,
|
||||
@@ -193,11 +198,11 @@ import {
|
||||
LoginResponse,
|
||||
LoginTokenPostResponse,
|
||||
SSOAction,
|
||||
} from "./@types/auth";
|
||||
import { TypedEventEmitter } from "./models/typed-event-emitter";
|
||||
import { MAIN_ROOM_TIMELINE, ReceiptType } from "./@types/read_receipts";
|
||||
import { MSC3575SlidingSyncRequest, MSC3575SlidingSyncResponse, SlidingSync } from "./sliding-sync";
|
||||
import { SlidingSyncSdk } from "./sliding-sync-sdk";
|
||||
} from "./@types/auth.ts";
|
||||
import { TypedEventEmitter } from "./models/typed-event-emitter.ts";
|
||||
import { MAIN_ROOM_TIMELINE, ReceiptType } from "./@types/read_receipts.ts";
|
||||
import { MSC3575SlidingSyncRequest, MSC3575SlidingSyncResponse, SlidingSync } from "./sliding-sync.ts";
|
||||
import { SlidingSyncSdk } from "./sliding-sync-sdk.ts";
|
||||
import {
|
||||
determineFeatureSupport,
|
||||
FeatureSupport,
|
||||
@@ -205,33 +210,42 @@ import {
|
||||
THREAD_RELATION_TYPE,
|
||||
ThreadFilterType,
|
||||
threadFilterTypeToFilter,
|
||||
} from "./models/thread";
|
||||
import { M_BEACON_INFO, MBeaconInfoEventContent } from "./@types/beacon";
|
||||
import { NamespacedValue, UnstableValue } from "./NamespacedValue";
|
||||
import { ToDeviceMessageQueue } from "./ToDeviceMessageQueue";
|
||||
import { ToDeviceBatch } from "./models/ToDeviceMessage";
|
||||
import { IgnoredInvites } from "./models/invites-ignorer";
|
||||
import { UIARequest, UIAResponse } from "./@types/uia";
|
||||
import { LocalNotificationSettings } from "./@types/local_notifications";
|
||||
import { buildFeatureSupportMap, Feature, ServerSupport } from "./feature";
|
||||
import { BackupDecryptor, CryptoBackend } from "./common-crypto/CryptoBackend";
|
||||
import { RUST_SDK_STORE_PREFIX } from "./rust-crypto/constants";
|
||||
import { BootstrapCrossSigningOpts, CrossSigningKeyInfo, CryptoApi, ImportRoomKeysOpts } from "./crypto-api";
|
||||
import { DeviceInfoMap } from "./crypto/DeviceList";
|
||||
} from "./models/thread.ts";
|
||||
import { M_BEACON_INFO, MBeaconInfoEventContent } from "./@types/beacon.ts";
|
||||
import { NamespacedValue, UnstableValue } from "./NamespacedValue.ts";
|
||||
import { ToDeviceMessageQueue } from "./ToDeviceMessageQueue.ts";
|
||||
import { ToDeviceBatch } from "./models/ToDeviceMessage.ts";
|
||||
import { IgnoredInvites } from "./models/invites-ignorer.ts";
|
||||
import { UIARequest, UIAResponse } from "./@types/uia.ts";
|
||||
import { LocalNotificationSettings } from "./@types/local_notifications.ts";
|
||||
import { buildFeatureSupportMap, Feature, ServerSupport } from "./feature.ts";
|
||||
import { BackupDecryptor, CryptoBackend } from "./common-crypto/CryptoBackend.ts";
|
||||
import { RUST_SDK_STORE_PREFIX } from "./rust-crypto/constants.ts";
|
||||
import {
|
||||
BootstrapCrossSigningOpts,
|
||||
CrossSigningKeyInfo,
|
||||
CryptoApi,
|
||||
decodeRecoveryKey,
|
||||
ImportRoomKeysOpts,
|
||||
CryptoEvent,
|
||||
CryptoEventHandlerMap,
|
||||
} from "./crypto-api/index.ts";
|
||||
import { DeviceInfoMap } from "./crypto/DeviceList.ts";
|
||||
import {
|
||||
AddSecretStorageKeyOpts,
|
||||
SecretStorageKeyDescription,
|
||||
ServerSideSecretStorage,
|
||||
ServerSideSecretStorageImpl,
|
||||
} from "./secret-storage";
|
||||
import { RegisterRequest, RegisterResponse } from "./@types/registration";
|
||||
import { MatrixRTCSessionManager } from "./matrixrtc/MatrixRTCSessionManager";
|
||||
import { getRelationsThreadFilter } from "./thread-utils";
|
||||
import { KnownMembership, Membership } from "./@types/membership";
|
||||
import { RoomMessageEventContent, StickerEventContent } from "./@types/events";
|
||||
import { ImageInfo } from "./@types/media";
|
||||
import { Capabilities, ServerCapabilities } from "./serverCapabilities";
|
||||
import { sha256 } from "./digest";
|
||||
} from "./secret-storage.ts";
|
||||
import { RegisterRequest, RegisterResponse } from "./@types/registration.ts";
|
||||
import { MatrixRTCSessionManager } from "./matrixrtc/MatrixRTCSessionManager.ts";
|
||||
import { getRelationsThreadFilter } from "./thread-utils.ts";
|
||||
import { KnownMembership, Membership } from "./@types/membership.ts";
|
||||
import { RoomMessageEventContent, StickerEventContent } from "./@types/events.ts";
|
||||
import { ImageInfo } from "./@types/media.ts";
|
||||
import { Capabilities, ServerCapabilities } from "./serverCapabilities.ts";
|
||||
import { sha256 } from "./digest.ts";
|
||||
import { keyFromAuthData } from "./common-crypto/key-passphrase.ts";
|
||||
|
||||
export type Store = IStore;
|
||||
|
||||
@@ -537,6 +551,8 @@ export const UNSTABLE_MSC2666_QUERY_MUTUAL_ROOMS = "uk.half-shot.msc2666.query_m
|
||||
|
||||
export const UNSTABLE_MSC4140_DELAYED_EVENTS = "org.matrix.msc4140";
|
||||
|
||||
export const UNSTABLE_MSC4133_EXTENDED_PROFILES = "uk.tcpip.msc4133";
|
||||
|
||||
enum CrossSigningKeyType {
|
||||
MasterKey = "master_key",
|
||||
SelfSigningKey = "self_signing_key",
|
||||
@@ -925,23 +941,25 @@ type RoomStateEvents =
|
||||
| RoomStateEvent.Update
|
||||
| RoomStateEvent.Marker;
|
||||
|
||||
type CryptoEvents =
|
||||
| CryptoEvent.KeySignatureUploadFailure
|
||||
| CryptoEvent.KeyBackupStatus
|
||||
| CryptoEvent.KeyBackupFailed
|
||||
| CryptoEvent.KeyBackupSessionsRemaining
|
||||
| CryptoEvent.KeyBackupDecryptionKeyCached
|
||||
| CryptoEvent.RoomKeyRequest
|
||||
| CryptoEvent.RoomKeyRequestCancellation
|
||||
| CryptoEvent.VerificationRequest
|
||||
| CryptoEvent.VerificationRequestReceived
|
||||
| CryptoEvent.DeviceVerificationChanged
|
||||
| CryptoEvent.UserTrustStatusChanged
|
||||
| CryptoEvent.KeysChanged
|
||||
| CryptoEvent.Warning
|
||||
| CryptoEvent.DevicesUpdated
|
||||
| CryptoEvent.WillUpdateDevices
|
||||
| CryptoEvent.LegacyCryptoStoreMigrationProgress;
|
||||
type LegacyCryptoEvents =
|
||||
| LegacyCryptoEvent.KeySignatureUploadFailure
|
||||
| LegacyCryptoEvent.KeyBackupStatus
|
||||
| LegacyCryptoEvent.KeyBackupFailed
|
||||
| LegacyCryptoEvent.KeyBackupSessionsRemaining
|
||||
| LegacyCryptoEvent.KeyBackupDecryptionKeyCached
|
||||
| LegacyCryptoEvent.RoomKeyRequest
|
||||
| LegacyCryptoEvent.RoomKeyRequestCancellation
|
||||
| LegacyCryptoEvent.VerificationRequest
|
||||
| LegacyCryptoEvent.VerificationRequestReceived
|
||||
| LegacyCryptoEvent.DeviceVerificationChanged
|
||||
| LegacyCryptoEvent.UserTrustStatusChanged
|
||||
| LegacyCryptoEvent.KeysChanged
|
||||
| LegacyCryptoEvent.Warning
|
||||
| LegacyCryptoEvent.DevicesUpdated
|
||||
| LegacyCryptoEvent.WillUpdateDevices
|
||||
| LegacyCryptoEvent.LegacyCryptoStoreMigrationProgress;
|
||||
|
||||
type CryptoEvents = (typeof CryptoEvent)[keyof typeof CryptoEvent];
|
||||
|
||||
type MatrixEventEvents = MatrixEventEvent.Decrypted | MatrixEventEvent.Replaced | MatrixEventEvent.VisibilityChange;
|
||||
|
||||
@@ -962,6 +980,7 @@ export type EmittedEvents =
|
||||
| ClientEvent
|
||||
| RoomEvents
|
||||
| RoomStateEvents
|
||||
| LegacyCryptoEvents
|
||||
| CryptoEvents
|
||||
| MatrixEventEvents
|
||||
| RoomMemberEvents
|
||||
@@ -1173,6 +1192,7 @@ export type ClientEventHandlerMap = {
|
||||
[ClientEvent.TurnServersError]: (error: Error, fatal: boolean) => void;
|
||||
} & RoomEventHandlerMap &
|
||||
RoomStateEventHandlerMap &
|
||||
LegacyCryptoEventHandlerMap &
|
||||
CryptoEventHandlerMap &
|
||||
MatrixEventHandlerMap &
|
||||
RoomMemberEventHandlerMap &
|
||||
@@ -1583,6 +1603,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @returns Promise which resolves to undefined if a device could not be dehydrated, or
|
||||
* to the new device ID if the dehydration was successful.
|
||||
* @returns Rejects: with an error response.
|
||||
*
|
||||
* @deprecated MSC2697 device dehydration is not supported for rust cryptography.
|
||||
*/
|
||||
public async rehydrateDevice(): Promise<string | undefined> {
|
||||
if (this.crypto) {
|
||||
@@ -1655,6 +1677,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
/**
|
||||
* Get the current dehydrated device, if any
|
||||
* @returns A promise of an object containing the dehydrated device
|
||||
*
|
||||
* @deprecated MSC2697 device dehydration is not supported for rust cryptography.
|
||||
*/
|
||||
public async getDehydratedDevice(): Promise<IDehydratedDevice | undefined> {
|
||||
try {
|
||||
@@ -1683,6 +1707,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param deviceDisplayName - The device display name for the
|
||||
* dehydrated device.
|
||||
* @returns A promise that resolves when the dehydrated device is stored.
|
||||
*
|
||||
* @deprecated Not supported for Rust Cryptography.
|
||||
*/
|
||||
public async setDehydrationKey(
|
||||
key: Uint8Array,
|
||||
@@ -1697,13 +1723,15 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new dehydrated device (without queuing periodic dehydration)
|
||||
* Creates a new MSC2967 dehydrated device (without queuing periodic dehydration)
|
||||
* @param key - the dehydration key
|
||||
* @param keyInfo - Information about the key. Primarily for
|
||||
* information about how to generate the key from a passphrase.
|
||||
* @param deviceDisplayName - The device display name for the
|
||||
* dehydrated device.
|
||||
* @returns the device id of the newly created dehydrated device
|
||||
*
|
||||
* @deprecated Not supported for Rust Cryptography. Prefer {@link CryptoApi.startDehydration}.
|
||||
*/
|
||||
public async createDehydratedDevice(
|
||||
key: Uint8Array,
|
||||
@@ -1718,6 +1746,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
return this.crypto.dehydrationManager.dehydrateDevice();
|
||||
}
|
||||
|
||||
/** @deprecated Not supported for Rust Cryptography. */
|
||||
public async exportDevice(): Promise<IExportedDevice | undefined> {
|
||||
if (!this.crypto) {
|
||||
this.logger.warn("not exporting device if crypto is not enabled");
|
||||
@@ -1754,7 +1783,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
try {
|
||||
indexedDB = global.indexedDB;
|
||||
if (!indexedDB) return; // No indexedDB support
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// No indexedDB support
|
||||
return;
|
||||
}
|
||||
@@ -2114,6 +2143,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*
|
||||
* It will return a Promise which will resolve when the crypto layer has been
|
||||
* successfully initialised.
|
||||
*
|
||||
* @deprecated libolm is deprecated. Prefer {@link initRustCrypto}.
|
||||
*/
|
||||
public async initCrypto(): Promise<void> {
|
||||
if (!isCryptoAvailable()) {
|
||||
@@ -2153,16 +2184,16 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
const crypto = new Crypto(this, userId, this.deviceId, this.store, this.cryptoStore, this.verificationMethods!);
|
||||
|
||||
this.reEmitter.reEmit(crypto, [
|
||||
CryptoEvent.KeyBackupFailed,
|
||||
CryptoEvent.KeyBackupSessionsRemaining,
|
||||
CryptoEvent.RoomKeyRequest,
|
||||
CryptoEvent.RoomKeyRequestCancellation,
|
||||
CryptoEvent.Warning,
|
||||
CryptoEvent.DevicesUpdated,
|
||||
CryptoEvent.WillUpdateDevices,
|
||||
CryptoEvent.DeviceVerificationChanged,
|
||||
CryptoEvent.UserTrustStatusChanged,
|
||||
CryptoEvent.KeysChanged,
|
||||
LegacyCryptoEvent.KeyBackupFailed,
|
||||
LegacyCryptoEvent.KeyBackupSessionsRemaining,
|
||||
LegacyCryptoEvent.RoomKeyRequest,
|
||||
LegacyCryptoEvent.RoomKeyRequestCancellation,
|
||||
LegacyCryptoEvent.Warning,
|
||||
LegacyCryptoEvent.DevicesUpdated,
|
||||
LegacyCryptoEvent.WillUpdateDevices,
|
||||
LegacyCryptoEvent.DeviceVerificationChanged,
|
||||
LegacyCryptoEvent.UserTrustStatusChanged,
|
||||
LegacyCryptoEvent.KeysChanged,
|
||||
]);
|
||||
|
||||
this.logger.debug("Crypto: initialising crypto object...");
|
||||
@@ -2231,7 +2262,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
// importing rust-crypto will download the webassembly, so we delay it until we know it will be
|
||||
// needed.
|
||||
this.logger.debug("Downloading Rust crypto library");
|
||||
const RustCrypto = await import("./rust-crypto");
|
||||
const RustCrypto = await import("./rust-crypto/index.ts");
|
||||
|
||||
const rustCrypto = await RustCrypto.initRustCrypto({
|
||||
logger: this.logger,
|
||||
@@ -2307,7 +2338,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @returns base64-encoded ed25519 key. Null if crypto is
|
||||
* disabled.
|
||||
*
|
||||
* @deprecated Prefer {@link CryptoApi.getOwnDeviceKeys}
|
||||
* @deprecated Not supported for Rust Cryptography.Prefer {@link CryptoApi.getOwnDeviceKeys}
|
||||
*/
|
||||
public getDeviceEd25519Key(): string | null {
|
||||
return this.crypto?.getDeviceEd25519Key() ?? null;
|
||||
@@ -2319,7 +2350,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @returns base64-encoded curve25519 key. Null if crypto is
|
||||
* disabled.
|
||||
*
|
||||
* @deprecated Use {@link CryptoApi.getOwnDeviceKeys}
|
||||
* @deprecated Not supported for Rust Cryptography. Use {@link CryptoApi.getOwnDeviceKeys}
|
||||
*/
|
||||
public getDeviceCurve25519Key(): string | null {
|
||||
return this.crypto?.getDeviceCurve25519Key() ?? null;
|
||||
@@ -2340,7 +2371,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*
|
||||
* @returns A promise which resolves to a map userId-\>deviceId-\>`DeviceInfo`
|
||||
*
|
||||
* @deprecated Prefer {@link CryptoApi.getUserDeviceInfo}
|
||||
* @deprecated Not supported for Rust Cryptography. Prefer {@link CryptoApi.getUserDeviceInfo}
|
||||
*/
|
||||
public downloadKeys(userIds: string[], forceDownload?: boolean): Promise<DeviceInfoMap> {
|
||||
if (!this.crypto) {
|
||||
@@ -2355,7 +2386,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param userId - the user to list keys for.
|
||||
*
|
||||
* @returns list of devices
|
||||
* @deprecated Prefer {@link CryptoApi.getUserDeviceInfo}
|
||||
* @deprecated Not supported for Rust Cryptography. Prefer {@link CryptoApi.getUserDeviceInfo}
|
||||
*/
|
||||
public getStoredDevicesForUser(userId: string): DeviceInfo[] {
|
||||
if (!this.crypto) {
|
||||
@@ -2371,7 +2402,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param deviceId - unique identifier for the device
|
||||
*
|
||||
* @returns device or null
|
||||
* @deprecated Prefer {@link CryptoApi.getUserDeviceInfo}
|
||||
* @deprecated Not supported for Rust Cryptography. Prefer {@link CryptoApi.getUserDeviceInfo}
|
||||
*/
|
||||
public getStoredDevice(userId: string, deviceId: string): DeviceInfo | null {
|
||||
if (!this.crypto) {
|
||||
@@ -2420,7 +2451,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @returns
|
||||
*
|
||||
* @remarks
|
||||
* Fires {@link CryptoEvent.DeviceVerificationChanged}
|
||||
* Fires {@link LegacyCryptoEvent.DeviceVerificationChanged}
|
||||
*
|
||||
* @deprecated Not supported for Rust Cryptography.
|
||||
*/
|
||||
public setDeviceBlocked(userId: string, deviceId: string, blocked = true): Promise<void> {
|
||||
return this.setDeviceVerification(userId, deviceId, null, blocked, null);
|
||||
@@ -2440,6 +2473,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*
|
||||
* @remarks
|
||||
* Fires {@link CryptoEvent#DeviceVerificationChanged}
|
||||
*
|
||||
* @deprecated Not supported for Rust Cryptography.
|
||||
*/
|
||||
public setDeviceKnown(userId: string, deviceId: string, known = true): Promise<void> {
|
||||
return this.setDeviceVerification(userId, deviceId, null, null, known);
|
||||
@@ -2467,7 +2502,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @returns resolves to a VerificationRequest
|
||||
* when the request has been sent to the other party.
|
||||
*
|
||||
* @deprecated Prefer {@link CryptoApi.requestVerificationDM}.
|
||||
* @deprecated Not supported for Rust Cryptography. Prefer {@link CryptoApi.requestVerificationDM}.
|
||||
*/
|
||||
public requestVerificationDM(userId: string, roomId: string): Promise<VerificationRequest> {
|
||||
if (!this.crypto) {
|
||||
@@ -2482,7 +2517,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param roomId - the room to use for verification
|
||||
*
|
||||
* @returns the VerificationRequest that is in progress, if any
|
||||
* @deprecated Prefer {@link CryptoApi.findVerificationRequestDMInProgress}.
|
||||
* @deprecated Not supported for Rust Cryptography. Prefer {@link CryptoApi.findVerificationRequestDMInProgress}.
|
||||
*/
|
||||
public findVerificationRequestDMInProgress(roomId: string): VerificationRequest | undefined {
|
||||
if (!this.cryptoBackend) {
|
||||
@@ -2501,7 +2536,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param userId - the ID of the user to query
|
||||
*
|
||||
* @returns the VerificationRequests that are in progress
|
||||
* @deprecated Prefer {@link CryptoApi.getVerificationRequestsToDeviceInProgress}.
|
||||
* @deprecated Not supported for Rust Cryptography. Prefer {@link CryptoApi.getVerificationRequestsToDeviceInProgress}.
|
||||
*/
|
||||
public getVerificationRequestsToDeviceInProgress(userId: string): VerificationRequest[] {
|
||||
if (!this.crypto) {
|
||||
@@ -2520,7 +2555,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @returns resolves to a VerificationRequest
|
||||
* when the request has been sent to the other party.
|
||||
*
|
||||
* @deprecated Prefer {@link CryptoApi#requestOwnUserVerification} or {@link CryptoApi#requestDeviceVerification}.
|
||||
* @deprecated Not supported for Rust Cryptography. Prefer {@link CryptoApi#requestOwnUserVerification} or {@link CryptoApi#requestDeviceVerification}.
|
||||
*/
|
||||
public requestVerification(userId: string, devices?: string[]): Promise<VerificationRequest> {
|
||||
if (!this.crypto) {
|
||||
@@ -2537,7 +2572,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param deviceId - the device to verify
|
||||
*
|
||||
* @returns a verification object
|
||||
* @deprecated Use `requestVerification` instead.
|
||||
* @deprecated Prefer {@link CryptoApi#requestOwnUserVerification} or {@link CryptoApi#requestDeviceVerification}.
|
||||
*/
|
||||
public beginKeyVerification(method: string, userId: string, deviceId: string): Verification<any, any> {
|
||||
if (!this.crypto) {
|
||||
@@ -2632,7 +2667,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* "master", "self_signing", or "user_signing". Defaults to "master".
|
||||
*
|
||||
* @returns the key ID
|
||||
* @deprecated prefer {@link Crypto.CryptoApi#getCrossSigningKeyId}
|
||||
* @deprecated Not supported for Rust Cryptography. prefer {@link Crypto.CryptoApi#getCrossSigningKeyId}
|
||||
*/
|
||||
public getCrossSigningId(type: CrossSigningKey | string = CrossSigningKey.Master): string | null {
|
||||
if (!this.crypto) {
|
||||
@@ -2649,7 +2684,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param userId - the user ID to get the cross-signing info for.
|
||||
*
|
||||
* @returns the cross signing information for the user.
|
||||
* @deprecated Prefer {@link CryptoApi#userHasCrossSigningKeys}
|
||||
* @deprecated Not supported for Rust Cryptography. Prefer {@link CryptoApi#userHasCrossSigningKeys}
|
||||
*/
|
||||
public getStoredCrossSigningForUser(userId: string): CrossSigningInfo | null {
|
||||
if (!this.cryptoBackend) {
|
||||
@@ -2698,6 +2733,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param deviceId - The ID of the device to check
|
||||
*
|
||||
* @returns true if the device is cross-signed
|
||||
*
|
||||
* @deprecated Not supported for Rust Cryptography.
|
||||
*/
|
||||
public checkIfOwnDeviceCrossSigned(deviceId: string): boolean {
|
||||
if (!this.crypto) {
|
||||
@@ -2727,6 +2764,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param privateKey - The private key
|
||||
* @param expectedPublicKey - The public key
|
||||
* @returns true if the key matches, otherwise false
|
||||
*
|
||||
* @deprecated Not supported for Rust Cryptography.
|
||||
*/
|
||||
public checkCrossSigningPrivateKey(privateKey: Uint8Array, expectedPublicKey: string): boolean {
|
||||
if (!this.crypto) {
|
||||
@@ -2735,7 +2774,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
return this.crypto.checkCrossSigningPrivateKey(privateKey, expectedPublicKey);
|
||||
}
|
||||
|
||||
// deprecated: use requestVerification instead
|
||||
/**
|
||||
* @deprecated Not supported for Rust Cryptography. Prefer {@link CryptoApi#requestDeviceVerification}.
|
||||
*/
|
||||
public legacyDeviceVerification(userId: string, deviceId: string, method: string): Promise<VerificationRequest> {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
@@ -2852,6 +2893,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
/**
|
||||
* Counts the number of end to end session keys that are waiting to be backed up
|
||||
* @returns Promise which resolves to the number of sessions requiring backup
|
||||
*
|
||||
* @deprecated Not supported for Rust Cryptography.
|
||||
*/
|
||||
public countSessionsNeedingBackup(): Promise<number> {
|
||||
if (!this.crypto) {
|
||||
@@ -3033,6 +3076,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param devices - the devices to request the secret from
|
||||
*
|
||||
* @returns the secret request object
|
||||
* @deprecated Not supported for Rust Cryptography.
|
||||
*/
|
||||
public requestSecret(name: string, devices: string[]): ISecretRequest {
|
||||
if (!this.crypto) {
|
||||
@@ -3092,6 +3136,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* Get e2e information on the device that sent an event
|
||||
*
|
||||
* @param event - event to be checked
|
||||
* @deprecated Not supported for Rust Cryptography.
|
||||
*/
|
||||
public async getEventSenderDeviceInfo(event: MatrixEvent): Promise<DeviceInfo | null> {
|
||||
if (!this.crypto) {
|
||||
@@ -3121,6 +3166,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param event - The event to check for
|
||||
*
|
||||
* @returns A room key request, or null if there is none
|
||||
*
|
||||
* @deprecated Not supported for Rust Cryptography.
|
||||
*/
|
||||
public getOutgoingRoomKeyRequest(event: MatrixEvent): Promise<OutgoingRoomKeyRequest | null> {
|
||||
if (!this.crypto) {
|
||||
@@ -3145,6 +3192,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param event - event of which to cancel and resend the room
|
||||
* key request.
|
||||
* @returns A promise that will resolve when the key request is queued
|
||||
*
|
||||
* @deprecated Not supported for Rust Cryptography.
|
||||
*/
|
||||
public cancelAndResendEventRoomKeyRequest(event: MatrixEvent): Promise<void> {
|
||||
if (!this.crypto) {
|
||||
@@ -3210,6 +3259,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* resolves once the message has been encrypted and sent to the given
|
||||
* userDeviceMap, and returns the `{ contentMap, deviceInfoByDeviceId }`
|
||||
* of the successfully sent messages.
|
||||
*
|
||||
* @deprecated Instead use {@link CryptoApi.encryptToDeviceMessages} followed by {@link queueToDevice}.
|
||||
*/
|
||||
public encryptAndSendToDevices(userDeviceInfoArr: IOlmDevice<DeviceInfo>[], payload: object): Promise<void> {
|
||||
if (!this.crypto) {
|
||||
@@ -3225,7 +3276,6 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param roomId - The ID of the room to discard the session for
|
||||
*
|
||||
* @deprecated Prefer {@link CryptoApi.forceDiscardSession | `CryptoApi.forceDiscardSession`}:
|
||||
*
|
||||
*/
|
||||
public forceDiscardSession(roomId: string): void {
|
||||
if (!this.cryptoBackend) {
|
||||
@@ -3326,7 +3376,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
/**
|
||||
* @param info - key backup info dict from getKeyBackupVersion()
|
||||
*
|
||||
* @deprecated Prefer {@link CryptoApi.isKeyBackupTrusted | `CryptoApi.isKeyBackupTrusted`}.
|
||||
* @deprecated Not supported for Rust Cryptography. Prefer {@link CryptoApi.isKeyBackupTrusted | `CryptoApi.isKeyBackupTrusted`}.
|
||||
*/
|
||||
public isKeyBackupTrusted(info: IKeyBackupInfo): Promise<TrustInfo> {
|
||||
if (!this.crypto) {
|
||||
@@ -3340,7 +3390,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* the server, otherwise false. If we haven't completed a successful check
|
||||
* of key backup status yet, returns null.
|
||||
*
|
||||
* @deprecated Prefer direct access to {@link Crypto.CryptoApi.getActiveSessionBackupVersion}:
|
||||
* @deprecated Not supported for Rust Cryptography. Prefer direct access to {@link Crypto.CryptoApi.getActiveSessionBackupVersion}:
|
||||
*
|
||||
* ```javascript
|
||||
* let enabled = (await client.getCrypto().getActiveSessionBackupVersion()) !== null;
|
||||
@@ -3373,7 +3423,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
/**
|
||||
* Disable backing up of keys.
|
||||
*
|
||||
* @deprecated It should be unnecessary to disable key backup.
|
||||
* @deprecated Not supported for Rust Cryptography. It should be unnecessary to disable key backup.
|
||||
*/
|
||||
public disableKeyBackup(): void {
|
||||
if (!this.crypto) {
|
||||
@@ -3394,7 +3444,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @returns Object that can be passed to createKeyBackupVersion and
|
||||
* additionally has a 'recovery_key' member with the user-facing recovery key string.
|
||||
*
|
||||
* @deprecated Use {@link Crypto.CryptoApi.resetKeyBackup | `CryptoApi.resetKeyBackup`}.
|
||||
* @deprecated Not supported for Rust cryptography. Use {@link Crypto.CryptoApi.resetKeyBackup | `CryptoApi.resetKeyBackup`}.
|
||||
*/
|
||||
public async prepareKeyBackupVersion(
|
||||
password?: string | Uint8Array | null,
|
||||
@@ -3522,6 +3572,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param data - Object keys to send
|
||||
* @returns a promise that will resolve when the keys
|
||||
* are uploaded
|
||||
*
|
||||
* @deprecated Not supported for Rust Cryptography.
|
||||
*/
|
||||
public sendKeyBackup(
|
||||
roomId: undefined,
|
||||
@@ -3559,8 +3611,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* Marks all group sessions as needing to be backed up and schedules them to
|
||||
* upload in the background as soon as possible.
|
||||
*
|
||||
* (This is done automatically as part of {@link CryptoApi.resetKeyBackup},
|
||||
* so there is probably no need to call this manually.)
|
||||
* @deprecated Not supported for Rust Cryptography. This is done automatically as part of
|
||||
* {@link CryptoApi.resetKeyBackup}, so there is probably no need to call this manually.
|
||||
*/
|
||||
public async scheduleAllGroupSessionsForBackup(): Promise<void> {
|
||||
if (!this.crypto) {
|
||||
@@ -3578,6 +3630,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* so there is probably no need to call this manually.)
|
||||
*
|
||||
* @returns Promise which resolves to the number of sessions requiring a backup.
|
||||
* @deprecated Not supported for Rust Cryptography.
|
||||
*/
|
||||
public flagAllGroupSessionsForBackup(): Promise<number> {
|
||||
if (!this.crypto) {
|
||||
@@ -3587,11 +3640,17 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
return this.crypto.backupManager.flagAllGroupSessionsForBackup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if recovery key is valid.
|
||||
* Try to decode the recovery key and check if it's successful.
|
||||
* @param recoveryKey
|
||||
* @deprecated Use {@link decodeRecoveryKey} directly
|
||||
*/
|
||||
public isValidRecoveryKey(recoveryKey: string): boolean {
|
||||
try {
|
||||
decodeRecoveryKey(recoveryKey);
|
||||
return true;
|
||||
} catch (e) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -3605,6 +3664,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param password - Passphrase
|
||||
* @param backupInfo - Backup metadata from `checkKeyBackup`
|
||||
* @returns key backup key
|
||||
* @deprecated Deriving a backup key from a passphrase is not part of the matrix spec. Instead, a random key is generated and stored/shared via 4S.
|
||||
*/
|
||||
public keyBackupKeyFromPassword(password: string, backupInfo: IKeyBackupInfo): Promise<Uint8Array> {
|
||||
return keyFromAuthData(backupInfo.auth_data, password);
|
||||
@@ -3618,6 +3678,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*
|
||||
* @param recoveryKey - The recovery key
|
||||
* @returns key backup key
|
||||
* @deprecated Use {@link decodeRecoveryKey} directly
|
||||
*/
|
||||
public keyBackupKeyFromRecoveryKey(recoveryKey: string): Uint8Array {
|
||||
return decodeRecoveryKey(recoveryKey);
|
||||
@@ -4028,48 +4089,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
public deleteKeysFromBackup(roomId: string, sessionId: undefined, version?: string): Promise<void>;
|
||||
public deleteKeysFromBackup(roomId: string, sessionId: string, version?: string): Promise<void>;
|
||||
public async deleteKeysFromBackup(roomId?: string, sessionId?: string, version?: string): Promise<void> {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
|
||||
const path = this.makeKeyBackupPath(roomId!, sessionId!, version);
|
||||
await this.http.authedRequest(Method.Delete, path.path, path.queryData, undefined, { prefix: ClientPrefix.V3 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Share shared-history decryption keys with the given users.
|
||||
*
|
||||
* @param roomId - the room for which keys should be shared.
|
||||
* @param userIds - a list of users to share with. The keys will be sent to
|
||||
* all of the user's current devices.
|
||||
*/
|
||||
public async sendSharedHistoryKeys(roomId: string, userIds: string[]): Promise<void> {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
|
||||
const roomEncryption = this.crypto?.getRoomEncryption(roomId);
|
||||
if (!roomEncryption) {
|
||||
// unknown room, or unencrypted room
|
||||
this.logger.error("Unknown room. Not sharing decryption keys");
|
||||
return;
|
||||
}
|
||||
|
||||
const deviceInfos = await this.crypto.downloadKeys(userIds);
|
||||
const devicesByUser: Map<string, DeviceInfo[]> = new Map();
|
||||
for (const [userId, devices] of deviceInfos) {
|
||||
devicesByUser.set(userId, Array.from(devices.values()));
|
||||
}
|
||||
|
||||
// XXX: Private member access
|
||||
const alg = this.crypto.getRoomDecryptor(roomId, roomEncryption.algorithm);
|
||||
if (alg.sendSharedHistoryInboundSessions) {
|
||||
await alg.sendSharedHistoryInboundSessions(devicesByUser);
|
||||
} else {
|
||||
this.logger.warn("Algorithm does not support sharing previous keys", roomEncryption.algorithm);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the config for the media repository.
|
||||
* @returns Promise which resolves with an object containing the config.
|
||||
@@ -4285,13 +4308,11 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
signPromise = this.http.requestOtherUrl<IThirdPartySigned>(Method.Post, url);
|
||||
}
|
||||
|
||||
let queryParams: QueryDict = {};
|
||||
const queryParams: QueryDict = {};
|
||||
if (opts.viaServers) {
|
||||
// server_name has been deprecated in favour of via with Matrix >1.11 (MSC4156)
|
||||
queryParams.server_name = opts.viaServers;
|
||||
queryParams.via = opts.viaServers;
|
||||
if (this.canSupport.get(Feature.MigrateServerNameToVia) === ServerSupport.Unstable) {
|
||||
queryParams = replaceParam("via", "org.matrix.msc4156.via", queryParams);
|
||||
}
|
||||
}
|
||||
|
||||
const data: IJoinRequestBody = {};
|
||||
@@ -4334,13 +4355,11 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
|
||||
const path = utils.encodeUri("/knock/$roomIdOrAlias", { $roomIdOrAlias: roomIdOrAlias });
|
||||
|
||||
let queryParams: QueryDict = {};
|
||||
const queryParams: QueryDict = {};
|
||||
if (opts.viaServers) {
|
||||
// server_name has been deprecated in favour of via with Matrix >1.11 (MSC4156)
|
||||
queryParams.server_name = opts.viaServers;
|
||||
queryParams.via = opts.viaServers;
|
||||
if (this.canSupport.get(Feature.MigrateServerNameToVia) === ServerSupport.Unstable) {
|
||||
queryParams = replaceParam("via", "org.matrix.msc4156.via", queryParams);
|
||||
}
|
||||
}
|
||||
|
||||
const body: Record<string, string> = {};
|
||||
@@ -7811,7 +7830,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
list: determineFeatureSupport(listStable, listUnstable),
|
||||
fwdPagination: determineFeatureSupport(fwdPaginationStable, fwdPaginationUnstable),
|
||||
};
|
||||
} catch (e) {
|
||||
} catch {
|
||||
return {
|
||||
threads: FeatureSupport.None,
|
||||
list: FeatureSupport.None,
|
||||
@@ -7904,6 +7923,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
/**
|
||||
* The app may wish to see if we have a key cached without
|
||||
* triggering a user interaction.
|
||||
*
|
||||
* @deprecated Not supported for Rust Cryptography.
|
||||
*/
|
||||
public getCrossSigningCacheCallbacks(): ICacheCallbacks | undefined {
|
||||
// XXX: Private member access
|
||||
@@ -8771,6 +8792,183 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
return this.http.authedRequest(Method.Get, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the server supports extended profiles, as described by MSC4133.
|
||||
*
|
||||
* @returns `true` if supported, otherwise `false`
|
||||
*/
|
||||
public async doesServerSupportExtendedProfiles(): Promise<boolean> {
|
||||
return this.doesServerSupportUnstableFeature(UNSTABLE_MSC4133_EXTENDED_PROFILES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the prefix used for extended profile requests.
|
||||
*
|
||||
* @returns The prefix for use with `authedRequest`
|
||||
*/
|
||||
private async getExtendedProfileRequestPrefix(): Promise<string> {
|
||||
if (await this.doesServerSupportUnstableFeature("uk.tcpip.msc4133.stable")) {
|
||||
return ClientPrefix.V3;
|
||||
}
|
||||
return "/_matrix/client/unstable/uk.tcpip.msc4133";
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a user's *extended* profile, which may include additonal keys.
|
||||
*
|
||||
* @see https://github.com/tcpipuk/matrix-spec-proposals/blob/main/proposals/4133-extended-profiles.md
|
||||
* @param userId The user ID to fetch the profile of.
|
||||
* @returns A set of keys to property values.
|
||||
*
|
||||
* @throws An error if the server does not support MSC4133.
|
||||
* @throws A M_NOT_FOUND error if the profile could not be found.
|
||||
*/
|
||||
public async getExtendedProfile(userId: string): Promise<Record<string, unknown>> {
|
||||
if (!(await this.doesServerSupportExtendedProfiles())) {
|
||||
throw new Error("Server does not support extended profiles");
|
||||
}
|
||||
return this.http.authedRequest(
|
||||
Method.Get,
|
||||
utils.encodeUri("/profile/$userId", { $userId: userId }),
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
prefix: await this.getExtendedProfileRequestPrefix(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a specific key from the user's *extended* profile.
|
||||
*
|
||||
* @see https://github.com/tcpipuk/matrix-spec-proposals/blob/main/proposals/4133-extended-profiles.md
|
||||
* @param userId The user ID to fetch the profile of.
|
||||
* @param key The key of the property to fetch.
|
||||
* @returns The property value.
|
||||
*
|
||||
* @throws An error if the server does not support MSC4133.
|
||||
* @throws A M_NOT_FOUND error if the key was not set OR the profile could not be found.
|
||||
*/
|
||||
public async getExtendedProfileProperty(userId: string, key: string): Promise<unknown> {
|
||||
if (!(await this.doesServerSupportExtendedProfiles())) {
|
||||
throw new Error("Server does not support extended profiles");
|
||||
}
|
||||
const profile = (await this.http.authedRequest(
|
||||
Method.Get,
|
||||
utils.encodeUri("/profile/$userId/$key", { $userId: userId, $key: key }),
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
prefix: await this.getExtendedProfileRequestPrefix(),
|
||||
},
|
||||
)) as Record<string, unknown>;
|
||||
return profile[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a property on your *extended* profile.
|
||||
*
|
||||
* @see https://github.com/tcpipuk/matrix-spec-proposals/blob/main/proposals/4133-extended-profiles.md
|
||||
* @param key The key of the property to set.
|
||||
* @param value The value to set on the propety.
|
||||
*
|
||||
* @throws An error if the server does not support MSC4133 OR the server disallows editing the user profile.
|
||||
*/
|
||||
public async setExtendedProfileProperty(key: string, value: unknown): Promise<void> {
|
||||
if (!(await this.doesServerSupportExtendedProfiles())) {
|
||||
throw new Error("Server does not support extended profiles");
|
||||
}
|
||||
const userId = this.getUserId();
|
||||
|
||||
await this.http.authedRequest(
|
||||
Method.Put,
|
||||
utils.encodeUri("/profile/$userId/$key", { $userId: userId, $key: key }),
|
||||
undefined,
|
||||
{ [key]: value },
|
||||
{
|
||||
prefix: await this.getExtendedProfileRequestPrefix(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a property on your *extended* profile.
|
||||
*
|
||||
* @see https://github.com/tcpipuk/matrix-spec-proposals/blob/main/proposals/4133-extended-profiles.md
|
||||
* @param key The key of the property to delete.
|
||||
*
|
||||
* @throws An error if the server does not support MSC4133 OR the server disallows editing the user profile.
|
||||
*/
|
||||
public async deleteExtendedProfileProperty(key: string): Promise<void> {
|
||||
if (!(await this.doesServerSupportExtendedProfiles())) {
|
||||
throw new Error("Server does not support extended profiles");
|
||||
}
|
||||
const userId = this.getUserId();
|
||||
|
||||
await this.http.authedRequest(
|
||||
Method.Delete,
|
||||
utils.encodeUri("/profile/$userId/$key", { $userId: userId, $key: key }),
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
prefix: await this.getExtendedProfileRequestPrefix(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update multiple properties on your *extended* profile. This will
|
||||
* merge with any existing keys.
|
||||
*
|
||||
* @see https://github.com/tcpipuk/matrix-spec-proposals/blob/main/proposals/4133-extended-profiles.md
|
||||
* @param profile The profile object to merge with the existing profile.
|
||||
* @returns The newly merged profile.
|
||||
*
|
||||
* @throws An error if the server does not support MSC4133 OR the server disallows editing the user profile.
|
||||
*/
|
||||
public async patchExtendedProfile(profile: Record<string, unknown>): Promise<Record<string, unknown>> {
|
||||
if (!(await this.doesServerSupportExtendedProfiles())) {
|
||||
throw new Error("Server does not support extended profiles");
|
||||
}
|
||||
const userId = this.getUserId();
|
||||
|
||||
return this.http.authedRequest(
|
||||
Method.Patch,
|
||||
utils.encodeUri("/profile/$userId", { $userId: userId }),
|
||||
{},
|
||||
profile,
|
||||
{
|
||||
prefix: await this.getExtendedProfileRequestPrefix(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set multiple properties on your *extended* profile. This will completely
|
||||
* replace the existing profile, removing any unspecified keys.
|
||||
*
|
||||
* @see https://github.com/tcpipuk/matrix-spec-proposals/blob/main/proposals/4133-extended-profiles.md
|
||||
* @param profile The profile object to set.
|
||||
*
|
||||
* @throws An error if the server does not support MSC4133 OR the server disallows editing the user profile.
|
||||
*/
|
||||
public async setExtendedProfile(profile: Record<string, unknown>): Promise<void> {
|
||||
if (!(await this.doesServerSupportExtendedProfiles())) {
|
||||
throw new Error("Server does not support extended profiles");
|
||||
}
|
||||
const userId = this.getUserId();
|
||||
|
||||
await this.http.authedRequest(
|
||||
Method.Put,
|
||||
utils.encodeUri("/profile/$userId", { $userId: userId }),
|
||||
{},
|
||||
profile,
|
||||
{
|
||||
prefix: await this.getExtendedProfileRequestPrefix(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns Promise which resolves to a list of the user's threepids.
|
||||
* @returns Rejects: with an error response.
|
||||
|
||||
@@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IDeviceLists, IToDeviceEvent } from "../sync-accumulator";
|
||||
import { IClearEvent, MatrixEvent } from "../models/event";
|
||||
import { Room } from "../models/room";
|
||||
import { CryptoApi, DecryptionFailureCode, ImportRoomKeysOpts } from "../crypto-api";
|
||||
import { CrossSigningInfo, UserTrustLevel } from "../crypto/CrossSigning";
|
||||
import { IEncryptedEventInfo } from "../crypto/api";
|
||||
import { KeyBackupInfo, KeyBackupSession } from "../crypto-api/keybackup";
|
||||
import { IMegolmSessionData } from "../@types/crypto";
|
||||
import type { IDeviceLists, IToDeviceEvent } from "../sync-accumulator.ts";
|
||||
import { IClearEvent, MatrixEvent } from "../models/event.ts";
|
||||
import { Room } from "../models/room.ts";
|
||||
import { CryptoApi, DecryptionFailureCode, ImportRoomKeysOpts } from "../crypto-api/index.ts";
|
||||
import { CrossSigningInfo, UserTrustLevel } from "../crypto/CrossSigning.ts";
|
||||
import { IEncryptedEventInfo } from "../crypto/api.ts";
|
||||
import { KeyBackupInfo, KeyBackupSession } from "../crypto-api/keybackup.ts";
|
||||
import { IMegolmSessionData } from "../@types/crypto.ts";
|
||||
|
||||
/**
|
||||
* Common interface for the crypto implementations
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 { deriveRecoveryKeyFromPassphrase } from "../crypto-api/index.ts";
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
interface IAuthData {
|
||||
private_key_salt?: string;
|
||||
private_key_iterations?: number;
|
||||
private_key_bits?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive a backup key from a passphrase using the salt and iterations from the auth data.
|
||||
* @param authData - The auth data containing the salt and iterations
|
||||
* @param passphrase - The passphrase to derive the key from
|
||||
* @deprecated Deriving a backup key from a passphrase is not part of the matrix spec. Instead, a random key is generated and stored/shared via 4S.
|
||||
*/
|
||||
export function keyFromAuthData(authData: IAuthData, passphrase: string): Promise<Uint8Array> {
|
||||
if (!authData.private_key_salt || !authData.private_key_iterations) {
|
||||
throw new Error("Salt and/or iterations not found: " + "this backup cannot be restored with a passphrase");
|
||||
}
|
||||
|
||||
return deriveRecoveryKeyFromPassphrase(
|
||||
passphrase,
|
||||
authData.private_key_salt,
|
||||
authData.private_key_iterations,
|
||||
authData.private_key_bits,
|
||||
);
|
||||
}
|
||||
@@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MBeaconEventContent, MBeaconInfoContent, MBeaconInfoEventContent } from "./@types/beacon";
|
||||
import { MsgType } from "./@types/event";
|
||||
import { M_TEXT, REFERENCE_RELATION } from "./@types/extensible_events";
|
||||
import { isProvided } from "./extensible_events_v1/utilities";
|
||||
import { MBeaconEventContent, MBeaconInfoContent, MBeaconInfoEventContent } from "./@types/beacon.ts";
|
||||
import { MsgType } from "./@types/event.ts";
|
||||
import { M_TEXT, REFERENCE_RELATION } from "./@types/extensible_events.ts";
|
||||
import { isProvided } from "./extensible_events_v1/utilities.ts";
|
||||
import {
|
||||
M_ASSET,
|
||||
LocationAssetType,
|
||||
@@ -28,9 +28,9 @@ import {
|
||||
MLocationContent,
|
||||
MAssetContent,
|
||||
LegacyLocationEventContent,
|
||||
} from "./@types/location";
|
||||
import { MRoomTopicEventContent, MTopicContent, M_TOPIC } from "./@types/topic";
|
||||
import { RoomMessageEventContent } from "./@types/events";
|
||||
} from "./@types/location.ts";
|
||||
import { MRoomTopicEventContent, MTopicContent, M_TOPIC } from "./@types/topic.ts";
|
||||
import { RoomMessageEventContent } from "./@types/events.ts";
|
||||
|
||||
/**
|
||||
* Generates the content for a HTML Message event
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { encodeParams } from "./utils";
|
||||
import { encodeParams } from "./utils.ts";
|
||||
|
||||
/**
|
||||
* Get the HTTP URL for an MXC URI.
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Events emitted by the {@link CryptoApi}
|
||||
*/
|
||||
export enum CryptoEvent {
|
||||
/**
|
||||
* Fires when the trust status of a user changes.
|
||||
* The payload is a pair (userId, userTrustLevel). The trust level is one of the values from UserVerificationStatus.
|
||||
*/
|
||||
UserTrustStatusChanged = "userTrustStatusChanged",
|
||||
|
||||
/**
|
||||
* Fires when the key backup status changes.
|
||||
* The payload is a boolean indicating whether the key backup is enabled.
|
||||
*/
|
||||
KeyBackupStatus = "crypto.keyBackupStatus",
|
||||
|
||||
/**
|
||||
* Fires when we failed to back up the keys
|
||||
* The payload is the error code of the error that occurred.
|
||||
*/
|
||||
KeyBackupFailed = "crypto.keyBackupFailed",
|
||||
|
||||
/**
|
||||
* Fires when the number of sessions that can be backed up changes.
|
||||
* The payload is the remaining number of sessions that can be backed up.
|
||||
*/
|
||||
KeyBackupSessionsRemaining = "crypto.keyBackupSessionsRemaining",
|
||||
|
||||
/**
|
||||
* Fires when a new valid backup decryption key is in cache.
|
||||
* This will happen when a secret is received from another session, from secret storage,
|
||||
* or when a new backup is created from this session.
|
||||
*
|
||||
* The payload is the version of the backup for which we have the key for.
|
||||
*
|
||||
* This event is only fired by the rust crypto backend.
|
||||
*/
|
||||
KeyBackupDecryptionKeyCached = "crypto.keyBackupDecryptionKeyCached",
|
||||
|
||||
/**
|
||||
* Fires when a key verification request is received.
|
||||
* The payload is a VerificationRequest object representing the request.
|
||||
*/
|
||||
VerificationRequestReceived = "crypto.verificationRequestReceived",
|
||||
|
||||
/** @deprecated Use {@link DevicesUpdated} instead when using rust crypto */
|
||||
WillUpdateDevices = "crypto.willUpdateDevices",
|
||||
|
||||
/**
|
||||
* Fires whenever the stored devices for a user have been updated
|
||||
* The payload is a pair (userIds, initialFetch).
|
||||
*/
|
||||
DevicesUpdated = "crypto.devicesUpdated",
|
||||
|
||||
/**
|
||||
* Fires when the user's cross-signing keys have changed or cross-signing
|
||||
* has been enabled/disabled. The client can use getStoredCrossSigningForUser
|
||||
* with the user ID of the logged in user to check if cross-signing is
|
||||
* enabled on the account. If enabled, it can test whether the current key
|
||||
* is trusted using with checkUserTrust with the user ID of the logged
|
||||
* in user. The checkOwnCrossSigningTrust function may be used to reconcile
|
||||
* the trust in the account key.
|
||||
*
|
||||
* The cross-signing API is currently UNSTABLE and may change without notice.
|
||||
* @experimental
|
||||
*/
|
||||
KeysChanged = "crossSigning.keysChanged",
|
||||
|
||||
/**
|
||||
* Fires when data is being migrated from legacy crypto to rust crypto.
|
||||
*
|
||||
* The payload is a pair `(progress, total)`, where `progress` is the number of steps completed so far, and
|
||||
* `total` is the total number of steps. When migration is complete, a final instance of the event is emitted, with
|
||||
* `progress === total === -1`.
|
||||
*/
|
||||
LegacyCryptoStoreMigrationProgress = "crypto.legacyCryptoStoreMigrationProgress",
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 { CryptoEvent } from "./CryptoEvent.ts";
|
||||
import { VerificationRequest } from "./verification.ts";
|
||||
import { UserVerificationStatus } from "./index.ts";
|
||||
import { RustBackupCryptoEventMap } from "../rust-crypto/backup.ts";
|
||||
|
||||
/**
|
||||
* A map of the {@link CryptoEvent} fired by the {@link CryptoApi} and their payloads.
|
||||
*/
|
||||
export type CryptoEventHandlerMap = {
|
||||
[CryptoEvent.VerificationRequestReceived]: (request: VerificationRequest) => void;
|
||||
[CryptoEvent.UserTrustStatusChanged]: (userId: string, userTrustLevel: UserVerificationStatus) => void;
|
||||
[CryptoEvent.KeyBackupDecryptionKeyCached]: (version: string) => void;
|
||||
[CryptoEvent.KeysChanged]: (data: {}) => void;
|
||||
[CryptoEvent.WillUpdateDevices]: (users: string[], initialFetch: boolean) => void;
|
||||
[CryptoEvent.DevicesUpdated]: (users: string[], initialFetch: boolean) => void;
|
||||
} & RustBackupCryptoEventMap;
|
||||
+165
-24
@@ -15,15 +15,25 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import type { SecretsBundle } from "@matrix-org/matrix-sdk-crypto-wasm";
|
||||
import type { IMegolmSessionData } from "../@types/crypto";
|
||||
import { Room } from "../models/room";
|
||||
import { DeviceMap } from "../models/device";
|
||||
import { UIAuthCallback } from "../interactive-auth";
|
||||
import { PassphraseInfo, SecretStorageCallbacks, SecretStorageKeyDescription } from "../secret-storage";
|
||||
import { VerificationRequest } from "./verification";
|
||||
import { BackupTrustInfo, KeyBackupCheck, KeyBackupInfo } from "./keybackup";
|
||||
import { ISignatures } from "../@types/signed";
|
||||
import { MatrixEvent } from "../models/event";
|
||||
import type { IMegolmSessionData } from "../@types/crypto.ts";
|
||||
import type { ToDeviceBatch, ToDevicePayload } from "../models/ToDeviceMessage.ts";
|
||||
import { Room } from "../models/room.ts";
|
||||
import { DeviceMap } from "../models/device.ts";
|
||||
import { UIAuthCallback } from "../interactive-auth.ts";
|
||||
import { PassphraseInfo, SecretStorageCallbacks, SecretStorageKeyDescription } from "../secret-storage.ts";
|
||||
import { VerificationRequest } from "./verification.ts";
|
||||
import { BackupTrustInfo, KeyBackupCheck, KeyBackupInfo } from "./keybackup.ts";
|
||||
import { ISignatures } from "../@types/signed.ts";
|
||||
import { MatrixEvent } from "../models/event.ts";
|
||||
|
||||
/**
|
||||
* `matrix-js-sdk/lib/crypto-api`: End-to-end encryption support.
|
||||
*
|
||||
* The most important type is {@link CryptoApi}, an instance of which can be retrieved via
|
||||
* {@link MatrixClient.getCrypto}.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Public interface to the cryptography parts of the js-sdk
|
||||
@@ -40,6 +50,11 @@ export interface CryptoApi {
|
||||
*/
|
||||
globalBlacklistUnverifiedDevices: boolean;
|
||||
|
||||
/**
|
||||
* The {@link DeviceIsolationMode} mode to use.
|
||||
*/
|
||||
setDeviceIsolationMode(isolationMode: DeviceIsolationMode): void;
|
||||
|
||||
/**
|
||||
* Return the current version of the crypto module.
|
||||
* For example: `Rust SDK ${versions.matrix_sdk_crypto} (${versions.git_sha}), Vodozemac ${versions.vodozemac}`.
|
||||
@@ -176,7 +191,7 @@ export interface CryptoApi {
|
||||
/**
|
||||
* Return whether we trust other user's signatures of their devices.
|
||||
*
|
||||
* @see {@link Crypto.CryptoApi#setTrustCrossSignedDevices}
|
||||
* @see {@link CryptoApi.setTrustCrossSignedDevices}
|
||||
*
|
||||
* @returns `true` if we trust cross-signed devices, otherwise `false`.
|
||||
*/
|
||||
@@ -190,6 +205,16 @@ export interface CryptoApi {
|
||||
*/
|
||||
getUserVerificationStatus(userId: string): Promise<UserVerificationStatus>;
|
||||
|
||||
/**
|
||||
* "Pin" the current identity of the given user, accepting it as genuine.
|
||||
*
|
||||
* This is useful if the user has changed identity since we first saw them (leading to
|
||||
* {@link UserVerificationStatus.needsUserApproval}), and we are now accepting their new identity.
|
||||
*
|
||||
* Throws an error if called on our own user ID, or on a user ID that we don't have an identity for.
|
||||
*/
|
||||
pinCurrentUserIdentity(userId: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Get the verification status of a given device.
|
||||
*
|
||||
@@ -213,7 +238,7 @@ export interface CryptoApi {
|
||||
*
|
||||
* @throws an error if the device is unknown, or has not published any encryption keys.
|
||||
*
|
||||
* @remarks Fires {@link CryptoEvent#DeviceVerificationChanged}
|
||||
* @remarks Fires {@link matrix.CryptoEvent.DeviceVerificationChanged}
|
||||
*/
|
||||
setDeviceVerified(userId: string, deviceId: string, verified?: boolean): Promise<void>;
|
||||
|
||||
@@ -244,7 +269,7 @@ export interface CryptoApi {
|
||||
*
|
||||
* @returns True if cross-signing is ready to be used on this device
|
||||
*
|
||||
* @throws May throw {@link ClientStoppedError} if the `MatrixClient` is stopped before or during the call.
|
||||
* @throws May throw {@link matrix.ClientStoppedError} if the `MatrixClient` is stopped before or during the call.
|
||||
*/
|
||||
isCrossSigningReady(): Promise<boolean>;
|
||||
|
||||
@@ -312,7 +337,7 @@ export interface CryptoApi {
|
||||
* @returns The current status of cross-signing keys: whether we have public and private keys cached locally, and
|
||||
* whether the private keys are in secret storage.
|
||||
*
|
||||
* @throws May throw {@link ClientStoppedError} if the `MatrixClient` is stopped before or during the call.
|
||||
* @throws May throw {@link matrix.ClientStoppedError} if the `MatrixClient` is stopped before or during the call.
|
||||
*/
|
||||
getCrossSigningStatus(): Promise<CrossSigningStatus>;
|
||||
|
||||
@@ -340,6 +365,22 @@ export interface CryptoApi {
|
||||
*/
|
||||
getEncryptionInfoForEvent(event: MatrixEvent): Promise<EventEncryptionInfo | null>;
|
||||
|
||||
/**
|
||||
* Encrypts a given payload object via Olm to-device messages to a given
|
||||
* set of devices.
|
||||
*
|
||||
* @param eventType - the type of the event to send.
|
||||
* @param devices - an array of devices to encrypt the payload for.
|
||||
* @param payload - the payload to encrypt.
|
||||
*
|
||||
* @returns the batch of encrypted payloads which can then be sent via {@link matrix.MatrixClient#queueToDevice}.
|
||||
*/
|
||||
encryptToDeviceMessages(
|
||||
eventType: string,
|
||||
devices: { userId: string; deviceId: string }[],
|
||||
payload: ToDevicePayload,
|
||||
): Promise<ToDeviceBatch>;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Device/User verification
|
||||
@@ -392,8 +433,8 @@ export interface CryptoApi {
|
||||
*
|
||||
* If an all-devices verification is already in flight, returns it. Otherwise, initiates a new one.
|
||||
*
|
||||
* To control the methods offered, set {@link ICreateClientOpts.verificationMethods} when creating the
|
||||
* MatrixClient.
|
||||
* To control the methods offered, set {@link matrix.ICreateClientOpts.verificationMethods} when creating the
|
||||
* `MatrixClient`.
|
||||
*
|
||||
* @returns a VerificationRequest when the request has been sent to the other party.
|
||||
*/
|
||||
@@ -407,8 +448,8 @@ export interface CryptoApi {
|
||||
*
|
||||
* If a verification for this user/device is already in flight, returns it. Otherwise, initiates a new one.
|
||||
*
|
||||
* To control the methods offered, set {@link ICreateClientOpts.verificationMethods} when creating the
|
||||
* MatrixClient.
|
||||
* To control the methods offered, set {@link matrix.ICreateClientOpts.verificationMethods} when creating the
|
||||
* `MatrixClient`.
|
||||
*
|
||||
* @param userId - ID of the owner of the device to verify
|
||||
* @param deviceId - ID of the device to verify
|
||||
@@ -465,7 +506,7 @@ export interface CryptoApi {
|
||||
/**
|
||||
* Determine if a key backup can be trusted.
|
||||
*
|
||||
* @param info - key backup info dict from {@link MatrixClient#getKeyBackupVersion}.
|
||||
* @param info - key backup info dict from {@link matrix.MatrixClient.getKeyBackupVersion}.
|
||||
*/
|
||||
isKeyBackupTrusted(info: KeyBackupInfo): Promise<BackupTrustInfo>;
|
||||
|
||||
@@ -485,7 +526,7 @@ export interface CryptoApi {
|
||||
*
|
||||
* If there are existing backups they will be replaced.
|
||||
*
|
||||
* The decryption key will be saved in Secret Storage (the {@link SecretStorageCallbacks.getSecretStorageKey} Crypto
|
||||
* The decryption key will be saved in Secret Storage (the {@link matrix.SecretStorage.SecretStorageCallbacks.getSecretStorageKey} Crypto
|
||||
* callback will be called)
|
||||
* and the backup engine will be started.
|
||||
*/
|
||||
@@ -589,6 +630,23 @@ export enum DecryptionFailureCode {
|
||||
*/
|
||||
HISTORICAL_MESSAGE_USER_NOT_JOINED = "HISTORICAL_MESSAGE_USER_NOT_JOINED",
|
||||
|
||||
/**
|
||||
* The sender's identity is not verified, but was previously verified.
|
||||
*/
|
||||
SENDER_IDENTITY_PREVIOUSLY_VERIFIED = "SENDER_IDENTITY_PREVIOUSLY_VERIFIED",
|
||||
|
||||
/**
|
||||
* The sender device is not cross-signed. This will only be used if the
|
||||
* device isolation mode is set to `OnlySignedDevicesIsolationMode`.
|
||||
*/
|
||||
UNSIGNED_SENDER_DEVICE = "UNSIGNED_SENDER_DEVICE",
|
||||
|
||||
/**
|
||||
* We weren't able to link the message back to any known device. This will
|
||||
* only be used if the device isolation mode is set to `OnlySignedDevicesIsolationMode`.
|
||||
*/
|
||||
UNKNOWN_SENDER_DEVICE = "UNKNOWN_SENDER_DEVICE",
|
||||
|
||||
/** Unknown or unclassified error. */
|
||||
UNKNOWN_ERROR = "UNKNOWN_ERROR",
|
||||
|
||||
@@ -632,6 +690,59 @@ export enum DecryptionFailureCode {
|
||||
UNKNOWN_ENCRYPTION_ALGORITHM = "UNKNOWN_ENCRYPTION_ALGORITHM",
|
||||
}
|
||||
|
||||
/** Base {@link DeviceIsolationMode} kind. */
|
||||
export enum DeviceIsolationModeKind {
|
||||
AllDevicesIsolationMode,
|
||||
OnlySignedDevicesIsolationMode,
|
||||
}
|
||||
|
||||
/**
|
||||
* A type of {@link DeviceIsolationMode}.
|
||||
*
|
||||
* Message encryption keys are shared with all devices in the room, except in case of
|
||||
* verified user problems (see {@link errorOnVerifiedUserProblems}).
|
||||
*
|
||||
* Events from all senders are always decrypted (and should be decorated with message shields in case
|
||||
* of authenticity warnings, see {@link EventEncryptionInfo}).
|
||||
*/
|
||||
export class AllDevicesIsolationMode {
|
||||
public readonly kind = DeviceIsolationModeKind.AllDevicesIsolationMode;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param errorOnVerifiedUserProblems - Behavior when sharing keys to remote devices.
|
||||
*
|
||||
* If set to `true`, sharing keys will fail (i.e. message sending will fail) with an error if:
|
||||
* - The user was previously verified but is not anymore, or:
|
||||
* - A verified user has some unverified devices (not cross-signed).
|
||||
*
|
||||
* If `false`, the keys will be distributed as usual. In this case, the client UX should display
|
||||
* warnings to inform the user about problematic devices/users, and stop them hitting this case.
|
||||
*/
|
||||
public constructor(public readonly errorOnVerifiedUserProblems: boolean) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* A type of {@link DeviceIsolationMode}.
|
||||
*
|
||||
* Message encryption keys are only shared with devices that have been cross-signed by their owner.
|
||||
* Encryption will throw an error if a verified user replaces their identity.
|
||||
*
|
||||
* Events are decrypted only if they come from a cross-signed device. Other events will result in a decryption
|
||||
* failure. (To access the failure reason, see {@link MatrixEvent.decryptionFailureReason}.)
|
||||
*/
|
||||
export class OnlySignedDevicesIsolationMode {
|
||||
public readonly kind = DeviceIsolationModeKind.OnlySignedDevicesIsolationMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* DeviceIsolationMode represents the mode of device isolation used when encrypting or decrypting messages.
|
||||
* It can be one of two types: {@link AllDevicesIsolationMode} or {@link OnlySignedDevicesIsolationMode}.
|
||||
*
|
||||
* Only supported by rust Crypto.
|
||||
*/
|
||||
export type DeviceIsolationMode = AllDevicesIsolationMode | OnlySignedDevicesIsolationMode;
|
||||
|
||||
/**
|
||||
* Options object for `CryptoApi.bootstrapCrossSigning`.
|
||||
*/
|
||||
@@ -650,11 +761,29 @@ export interface BootstrapCrossSigningOpts {
|
||||
* Represents the ways in which we trust a user
|
||||
*/
|
||||
export class UserVerificationStatus {
|
||||
/**
|
||||
* Indicates if the identity has changed in a way that needs user approval.
|
||||
*
|
||||
* This happens if the identity has changed since we first saw it, *unless* the new identity has also been verified
|
||||
* by our user (eg via an interactive verification).
|
||||
*
|
||||
* To rectify this, either:
|
||||
*
|
||||
* * Conduct a verification of the new identity via {@link CryptoApi.requestVerificationDM}.
|
||||
* * Pin the new identity, via {@link CryptoApi.pinCurrentUserIdentity}.
|
||||
*
|
||||
* @returns true if the identity has changed in a way that needs user approval.
|
||||
*/
|
||||
public readonly needsUserApproval: boolean;
|
||||
|
||||
public constructor(
|
||||
private readonly crossSigningVerified: boolean,
|
||||
private readonly crossSigningVerifiedBefore: boolean,
|
||||
private readonly tofu: boolean,
|
||||
) {}
|
||||
needsUserApproval: boolean = false,
|
||||
) {
|
||||
this.needsUserApproval = needsUserApproval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns true if this user is verified via any means
|
||||
@@ -680,6 +809,8 @@ export class UserVerificationStatus {
|
||||
|
||||
/**
|
||||
* @returns true if this user's key is trusted on first use
|
||||
*
|
||||
* @deprecated No longer supported, with the Rust crypto stack.
|
||||
*/
|
||||
public isTofu(): boolean {
|
||||
return this.tofu;
|
||||
@@ -736,9 +867,9 @@ export class DeviceVerificationStatus {
|
||||
* Check if we should consider this device "verified".
|
||||
*
|
||||
* A device is "verified" if either:
|
||||
* * it has been manually marked as such via {@link MatrixClient#setDeviceVerified}.
|
||||
* * it has been manually marked as such via {@link matrix.MatrixClient.setDeviceVerified}.
|
||||
* * it has been cross-signed with a verified signing key, **and** the client has been configured to trust
|
||||
* cross-signed devices via {@link Crypto.CryptoApi#setTrustCrossSignedDevices}.
|
||||
* cross-signed devices via {@link CryptoApi.setTrustCrossSignedDevices}.
|
||||
*
|
||||
* @returns true if this device is verified via any means.
|
||||
*/
|
||||
@@ -809,6 +940,8 @@ export interface CryptoCallbacks extends SecretStorageCallbacks {
|
||||
* @param key - private key to store
|
||||
*/
|
||||
cacheSecretStorageKey?: (keyId: string, keyInfo: SecretStorageKeyDescription, key: Uint8Array) => void;
|
||||
|
||||
/** @deprecated: unused with the Rust crypto stack. */
|
||||
onSecretRequested?: (
|
||||
userId: string,
|
||||
deviceId: string,
|
||||
@@ -816,10 +949,13 @@ export interface CryptoCallbacks extends SecretStorageCallbacks {
|
||||
secretName: string,
|
||||
deviceTrust: DeviceVerificationStatus,
|
||||
) => Promise<string | undefined>;
|
||||
|
||||
/** @deprecated: unused with the Rust crypto stack. */
|
||||
getDehydrationKey?: (
|
||||
keyInfo: SecretStorageKeyDescription,
|
||||
checkFunc: (key: Uint8Array) => void,
|
||||
) => Promise<Uint8Array>;
|
||||
|
||||
getBackupKey?: () => Promise<Uint8Array>;
|
||||
}
|
||||
|
||||
@@ -838,6 +974,7 @@ export interface CreateSecretStorageOpts {
|
||||
/**
|
||||
* The current key backup object. If passed,
|
||||
* the passphrase and recovery key from this backup will be used.
|
||||
* @deprecated Not used by the Rust crypto stack.
|
||||
*/
|
||||
keyBackupInfo?: KeyBackupInfo;
|
||||
|
||||
@@ -960,5 +1097,9 @@ export interface OwnDeviceKeys {
|
||||
curve25519: string;
|
||||
}
|
||||
|
||||
export * from "./verification";
|
||||
export * from "./keybackup";
|
||||
export * from "./verification.ts";
|
||||
export type * from "./keybackup.ts";
|
||||
export * from "./recovery-key.ts";
|
||||
export * from "./key-passphrase.ts";
|
||||
export * from "./CryptoEvent.ts";
|
||||
export type * from "./CryptoEventHandlerMap.ts";
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const DEFAULT_BIT_SIZE = 256;
|
||||
|
||||
/**
|
||||
* Derive a recovery key from a passphrase and salt using PBKDF2.
|
||||
* @see https://spec.matrix.org/v1.11/client-server-api/#deriving-keys-from-passphrases
|
||||
*
|
||||
* @param passphrase - The passphrase to derive the key from
|
||||
* @param salt - The salt to use in the derivation
|
||||
* @param iterations - The number of iterations to use in the derivation
|
||||
* @param numBits - The number of bits to derive
|
||||
*/
|
||||
export async function deriveRecoveryKeyFromPassphrase(
|
||||
passphrase: string,
|
||||
salt: string,
|
||||
iterations: number,
|
||||
numBits = DEFAULT_BIT_SIZE,
|
||||
): Promise<Uint8Array> {
|
||||
if (!globalThis.crypto.subtle || !TextEncoder) {
|
||||
throw new Error("Password-based backup is not available on this platform");
|
||||
}
|
||||
|
||||
const key = await globalThis.crypto.subtle.importKey(
|
||||
"raw",
|
||||
new TextEncoder().encode(passphrase),
|
||||
{ name: "PBKDF2" },
|
||||
false,
|
||||
["deriveBits"],
|
||||
);
|
||||
|
||||
const keybits = await globalThis.crypto.subtle.deriveBits(
|
||||
{
|
||||
name: "PBKDF2",
|
||||
salt: new TextEncoder().encode(salt),
|
||||
iterations: iterations,
|
||||
hash: "SHA-512",
|
||||
},
|
||||
key,
|
||||
numBits,
|
||||
);
|
||||
|
||||
return new Uint8Array(keybits);
|
||||
}
|
||||
@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { ISigned } from "../@types/signed";
|
||||
import { IEncryptedPayload } from "../crypto/aes";
|
||||
import { ISigned } from "../@types/signed.ts";
|
||||
import { AESEncryptedSecretStoragePayload } from "../@types/AESEncryptedSecretStoragePayload.ts";
|
||||
|
||||
export interface Curve25519AuthData {
|
||||
public_key: string;
|
||||
@@ -35,7 +35,7 @@ export interface Aes256AuthData {
|
||||
* Information about a server-side key backup.
|
||||
*
|
||||
* Returned by [`GET /_matrix/client/v3/room_keys/version`](https://spec.matrix.org/v1.7/client-server-api/#get_matrixclientv3room_keysversion)
|
||||
* and hence {@link MatrixClient#getKeyBackupVersion}.
|
||||
* and hence {@link matrix.MatrixClient.getKeyBackupVersion}.
|
||||
*/
|
||||
export interface KeyBackupInfo {
|
||||
algorithm: string;
|
||||
@@ -63,7 +63,7 @@ export interface BackupTrustInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of {@link Crypto.CryptoApi.checkKeyBackupAndEnable}.
|
||||
* The result of {@link CryptoApi.checkKeyBackupAndEnable}.
|
||||
*/
|
||||
export interface KeyBackupCheck {
|
||||
backupInfo: KeyBackupInfo;
|
||||
@@ -77,7 +77,7 @@ export interface Curve25519SessionData {
|
||||
}
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
export interface KeyBackupSession<T = Curve25519SessionData | IEncryptedPayload> {
|
||||
export interface KeyBackupSession<T = Curve25519SessionData | AESEncryptedSecretStoragePayload> {
|
||||
first_message_index: number;
|
||||
forwarded_count: number;
|
||||
is_verified: boolean;
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 bs58 from "bs58";
|
||||
|
||||
// picked arbitrarily but to try & avoid clashing with any bitcoin ones
|
||||
// (which are also base58 encoded, but bitcoin's involve a lot more hashing)
|
||||
const OLM_RECOVERY_KEY_PREFIX = [0x8b, 0x01];
|
||||
const KEY_SIZE = 32;
|
||||
|
||||
/**
|
||||
* Encode a recovery key using the Matrix {@link https://spec.matrix.org/v1.11/appendices/#cryptographic-key-representation | Cryptographic key representation}
|
||||
* @param key
|
||||
*/
|
||||
export function encodeRecoveryKey(key: ArrayLike<number>): string | undefined {
|
||||
const buf = Buffer.alloc(OLM_RECOVERY_KEY_PREFIX.length + key.length + 1);
|
||||
buf.set(OLM_RECOVERY_KEY_PREFIX, 0);
|
||||
buf.set(key, OLM_RECOVERY_KEY_PREFIX.length);
|
||||
|
||||
let parity = 0;
|
||||
for (let i = 0; i < buf.length - 1; ++i) {
|
||||
parity ^= buf[i];
|
||||
}
|
||||
buf[buf.length - 1] = parity;
|
||||
const base58key = bs58.encode(buf);
|
||||
|
||||
return base58key.match(/.{1,4}/g)?.join(" ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a recovery key encoded with the Matrix {@link https://spec.matrix.org/v1.11/appendices/#cryptographic-key-representation | Cryptographic key representation} encoding.
|
||||
* @param recoveryKey
|
||||
*/
|
||||
export function decodeRecoveryKey(recoveryKey: string): Uint8Array {
|
||||
const result = bs58.decode(recoveryKey.replace(/ /g, ""));
|
||||
|
||||
let parity = 0;
|
||||
for (const b of result) {
|
||||
parity ^= b;
|
||||
}
|
||||
if (parity !== 0) {
|
||||
throw new Error("Incorrect parity");
|
||||
}
|
||||
|
||||
for (let i = 0; i < OLM_RECOVERY_KEY_PREFIX.length; ++i) {
|
||||
if (result[i] !== OLM_RECOVERY_KEY_PREFIX[i]) {
|
||||
throw new Error("Incorrect prefix");
|
||||
}
|
||||
}
|
||||
|
||||
if (result.length !== OLM_RECOVERY_KEY_PREFIX.length + KEY_SIZE + 1) {
|
||||
throw new Error("Incorrect length");
|
||||
}
|
||||
|
||||
return Uint8Array.from(result.slice(OLM_RECOVERY_KEY_PREFIX.length, OLM_RECOVERY_KEY_PREFIX.length + KEY_SIZE));
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user