matrix-js-sdk
Version:
Matrix Client-Server SDK for Javascript
480 lines (441 loc) • 18.1 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.RustVerificationRequest = exports.RustSASVerifier = void 0;
exports.verificationMethodIdentifierToMethod = verificationMethodIdentifierToMethod;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var RustSdkCryptoJs = _interopRequireWildcard(require("@matrix-org/matrix-sdk-crypto-js"));
var _verification = require("../crypto-api/verification");
var _typedEventEmitter = require("../models/typed-event-emitter");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/*
Copyright 2023 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 incoming, or outgoing, request to verify a user or a device via cross-signing.
*/
class RustVerificationRequest extends _typedEventEmitter.TypedEventEmitter {
/**
* Construct a new RustVerificationRequest to wrap the rust-level `VerificationRequest`.
*
* @param inner - VerificationRequest from the Rust SDK
* @param outgoingRequestProcessor - `OutgoingRequestProcessor` to use for making outgoing HTTP requests
* @param supportedVerificationMethods - Verification methods to use when `accept()` is called
*/
constructor(inner, outgoingRequestProcessor, supportedVerificationMethods) {
super();
this.inner = inner;
this.outgoingRequestProcessor = outgoingRequestProcessor;
this.supportedVerificationMethods = supportedVerificationMethods;
/** Are we in the process of sending an `m.key.verification.ready` event? */
(0, _defineProperty2.default)(this, "_accepting", false);
/** Are we in the process of sending an `m.key.verification.cancellation` event? */
(0, _defineProperty2.default)(this, "_cancelling", false);
(0, _defineProperty2.default)(this, "_verifier", void 0);
const onChange = async () => {
// if we now have a `Verification` where we lacked one before, wrap it.
// TODO: QR support
if (this._verifier === undefined) {
const verification = this.inner.getVerification();
if (verification instanceof RustSdkCryptoJs.Sas) {
this._verifier = new RustSASVerifier(verification, this, outgoingRequestProcessor);
}
}
this.emit(_verification.VerificationRequestEvent.Change);
};
inner.registerChangesCallback(onChange);
}
/**
* Unique ID for this verification request.
*
* An ID isn't assigned until the first message is sent, so this may be `undefined` in the early phases.
*/
get transactionId() {
return this.inner.flowId;
}
/**
* For an in-room verification, the ID of the room.
*
* For to-device verifications, `undefined`.
*/
get roomId() {
var _this$inner$roomId;
return (_this$inner$roomId = this.inner.roomId) === null || _this$inner$roomId === void 0 ? void 0 : _this$inner$roomId.toString();
}
/**
* True if this request was initiated by the local client.
*
* For in-room verifications, the initiator is who sent the `m.key.verification.request` event.
* For to-device verifications, the initiator is who sent the `m.key.verification.start` event.
*/
get initiatedByMe() {
return this.inner.weStarted();
}
/** The user id of the other party in this request */
get otherUserId() {
return this.inner.otherUserId.toString();
}
/** For verifications via to-device messages: the ID of the other device. Otherwise, undefined. */
get otherDeviceId() {
var _this$inner$otherDevi;
return (_this$inner$otherDevi = this.inner.otherDeviceId) === null || _this$inner$otherDevi === void 0 ? void 0 : _this$inner$otherDevi.toString();
}
/** True if the other party in this request is one of this user's own devices. */
get isSelfVerification() {
return this.inner.isSelfVerification();
}
/** current phase of the request. */
get phase() {
const phase = this.inner.phase();
switch (phase) {
case RustSdkCryptoJs.VerificationRequestPhase.Created:
case RustSdkCryptoJs.VerificationRequestPhase.Requested:
return _verification.VerificationPhase.Requested;
case RustSdkCryptoJs.VerificationRequestPhase.Ready:
// if we're still sending the `m.key.verification.ready`, that counts as "Requested" in the js-sdk's
// parlance.
return this._accepting ? _verification.VerificationPhase.Requested : _verification.VerificationPhase.Ready;
case RustSdkCryptoJs.VerificationRequestPhase.Transitioned:
return _verification.VerificationPhase.Started;
case RustSdkCryptoJs.VerificationRequestPhase.Done:
return _verification.VerificationPhase.Done;
case RustSdkCryptoJs.VerificationRequestPhase.Cancelled:
return _verification.VerificationPhase.Cancelled;
}
throw new Error(`Unknown verification phase ${phase}`);
}
/** True if the request has sent its initial event and needs more events to complete
* (ie it is in phase `Requested`, `Ready` or `Started`).
*/
get pending() {
if (this.inner.isPassive()) return false;
const phase = this.phase;
return phase !== _verification.VerificationPhase.Done && phase !== _verification.VerificationPhase.Cancelled;
}
/**
* True if we have started the process of sending an `m.key.verification.ready` (but have not necessarily received
* the remote echo which causes a transition to {@link VerificationPhase.Ready}.
*/
get accepting() {
return this._accepting;
}
/**
* True if we have started the process of sending an `m.key.verification.cancel` (but have not necessarily received
* the remote echo which causes a transition to {@link VerificationPhase.Cancelled}).
*/
get declining() {
return this._cancelling;
}
/**
* The remaining number of ms before the request will be automatically cancelled.
*
* `null` indicates that there is no timeout
*/
get timeout() {
return this.inner.timeRemainingMillis();
}
/** once the phase is Started (and !initiatedByMe) or Ready: common methods supported by both sides */
get methods() {
throw new Error("not implemented");
}
/** the method picked in the .start event */
get chosenMethod() {
const verification = this.inner.getVerification();
// TODO: this isn't quite right. The existence of a Verification doesn't prove that we have .started.
if (verification instanceof RustSdkCryptoJs.Sas) {
return "m.sas.v1";
} else {
return null;
}
}
/**
* Checks whether the other party supports a given verification method.
* This is useful when setting up the QR code UI, as it is somewhat asymmetrical:
* if the other party supports SCAN_QR, we should show a QR code in the UI, and vice versa.
* For methods that need to be supported by both ends, use the `methods` property.
*
* @param method - the method to check
* @returns true if the other party said they supported the method
*/
otherPartySupportsMethod(method) {
const theirMethods = this.inner.theirSupportedMethods;
if (theirMethods === undefined) {
// no message from the other side yet
return false;
}
const requiredMethod = verificationMethodsByIdentifier[method];
return theirMethods.some(m => m === requiredMethod);
}
/**
* Accepts the request, sending a .ready event to the other party
*
* @returns Promise which resolves when the event has been sent.
*/
async accept() {
if (this.inner.phase() !== RustSdkCryptoJs.VerificationRequestPhase.Requested || this._accepting) {
throw new Error(`Cannot accept a verification request in phase ${this.phase}`);
}
this._accepting = true;
try {
const req = this.supportedVerificationMethods === undefined ? this.inner.accept() : this.inner.acceptWithMethods(this.supportedVerificationMethods.map(verificationMethodIdentifierToMethod));
if (req) {
await this.outgoingRequestProcessor.makeOutgoingRequest(req);
}
} finally {
this._accepting = false;
}
// phase may have changed, so emit a 'change' event
this.emit(_verification.VerificationRequestEvent.Change);
}
/**
* Cancels the request, sending a cancellation to the other party
*
* @param params - Details for the cancellation, including `reason` (defaults to "User declined"), and `code`
* (defaults to `m.user`).
*
* @returns Promise which resolves when the event has been sent.
*/
async cancel(params) {
if (this._cancelling) {
// already cancelling; do nothing
return;
}
this._cancelling = true;
try {
const req = this.inner.cancel();
if (req) {
await this.outgoingRequestProcessor.makeOutgoingRequest(req);
}
} finally {
this._cancelling = false;
}
}
/**
* Create a {@link Verifier} to do this verification via a particular method.
*
* If a verifier has already been created for this request, returns that verifier.
*
* This does *not* send the `m.key.verification.start` event - to do so, call {@link Verifier#verifier} on the
* returned verifier.
*
* If no previous events have been sent, pass in `targetDevice` to set who to direct this request to.
*
* @param method - the name of the verification method to use.
* @param targetDevice - details of where to send the request to.
*
* @returns The verifier which will do the actual verification.
*/
beginKeyVerification(method, targetDevice) {
throw new Error("not implemented");
}
/**
* Send an `m.key.verification.start` event to start verification via a particular method.
*
* Implementation of {@link Crypto.VerificationRequest#startVerification}.
*
* @param method - the name of the verification method to use.
*/
async startVerification(method) {
if (method !== "m.sas.v1") {
throw new Error(`Unsupported verification method ${method}`);
}
const res = await this.inner.startSas();
if (res) {
const [, req] = res;
await this.outgoingRequestProcessor.makeOutgoingRequest(req);
}
// this should have triggered the onChange callback, and we should now have a verifier
if (!this._verifier) {
throw new Error("Still no verifier after startSas() call");
}
return this._verifier;
}
/**
* The verifier which is doing the actual verification, once the method has been established.
* Only defined when the `phase` is Started.
*/
get verifier() {
return this._verifier;
}
/**
* Stub implementation of {@link Crypto.VerificationRequest#getQRCodeBytes}.
*/
getQRCodeBytes() {
// TODO
return undefined;
}
/**
* Generate the data for a QR code allowing the other device to verify this one, if it supports it.
*
* Implementation of {@link Crypto.VerificationRequest#generateQRCode}.
*/
async generateQRCode() {
// TODO
return undefined;
}
/**
* If this request has been cancelled, the cancellation code (e.g `m.user`) which is responsible for cancelling
* this verification.
*/
get cancellationCode() {
throw new Error("not implemented");
}
/**
* The id of the user that cancelled the request.
*
* Only defined when phase is Cancelled
*/
get cancellingUserId() {
throw new Error("not implemented");
}
}
exports.RustVerificationRequest = RustVerificationRequest;
class RustSASVerifier extends _typedEventEmitter.TypedEventEmitter {
constructor(inner, _verificationRequest, outgoingRequestProcessor) {
super();
this.inner = inner;
this.outgoingRequestProcessor = outgoingRequestProcessor;
/** A promise which completes when the verification completes (or rejects when it is cancelled/fails) */
(0, _defineProperty2.default)(this, "completionPromise", void 0);
(0, _defineProperty2.default)(this, "callbacks", null);
this.completionPromise = new Promise((resolve, reject) => {
const onChange = async () => {
this.updateCallbacks();
if (this.inner.isDone()) {
resolve(undefined);
} else if (this.inner.isCancelled()) {
const cancelInfo = this.inner.cancelInfo();
reject(new Error(`Verification cancelled by ${cancelInfo.cancelledbyUs() ? "us" : "them"} with code ${cancelInfo.cancelCode()}: ${cancelInfo.reason()}`));
}
};
inner.registerChangesCallback(onChange);
});
// stop the runtime complaining if nobody catches a failure
this.completionPromise.catch(() => null);
}
/** if we can now show the callbacks, do so */
updateCallbacks() {
if (this.callbacks === null) {
const emoji = this.inner.emoji();
const decimal = this.inner.decimals();
if (emoji === undefined && decimal === undefined) {
return;
}
this.callbacks = {
sas: {
decimal: decimal,
emoji: emoji === null || emoji === void 0 ? void 0 : emoji.map(e => [e.symbol, e.description])
},
confirm: async () => {
const requests = await this.inner.confirm();
for (const m of requests) {
await this.outgoingRequestProcessor.makeOutgoingRequest(m);
}
},
mismatch: () => {
throw new Error("impl");
},
cancel: () => {
throw new Error("impl");
}
};
this.emit(_verification.VerifierEvent.ShowSas, this.callbacks);
}
}
/**
* Returns true if the verification has been cancelled, either by us or the other side.
*/
get hasBeenCancelled() {
return this.inner.isCancelled();
}
/**
* The ID of the other user in the verification process.
*/
get userId() {
return this.inner.otherUserId.toString();
}
/**
* Start the key verification, if it has not already been started.
*
* This means sending a `m.key.verification.start` if we are the first responder, or a `m.key.verification.accept`
* if the other side has already sent a start event.
*
* @returns Promise which resolves when the verification has completed, or rejects if the verification is cancelled
* or times out.
*/
async verify() {
const req = this.inner.accept();
if (req) {
await this.outgoingRequestProcessor.makeOutgoingRequest(req);
}
await this.completionPromise;
}
/**
* Cancel a verification.
*
* We will send an `m.key.verification.cancel` if the verification is still in flight. The verification promise
* will reject, and a {@link Crypto.VerifierEvent#Cancel} will be emitted.
*
* @param e - the reason for the cancellation.
*/
cancel(e) {
// TODO: something with `e`
const req = this.inner.cancel();
if (req) {
this.outgoingRequestProcessor.makeOutgoingRequest(req);
}
}
/**
* Get the details for an SAS verification, if one is in progress
*
* Returns `null`, unless this verifier is for a SAS-based verification and we are waiting for the user to confirm
* the SAS matches.
*/
getShowSasCallbacks() {
return this.callbacks;
}
/**
* Get the details for reciprocating QR code verification, if one is in progress
*
* Returns `null`, unless this verifier is for reciprocating a QR-code-based verification (ie, the other user has
* already scanned our QR code), and we are waiting for the user to confirm.
*/
getReciprocateQrCodeCallbacks() {
return null;
}
}
/** For each specced verification method, the rust-side `VerificationMethod` corresponding to it */
exports.RustSASVerifier = RustSASVerifier;
const verificationMethodsByIdentifier = {
"m.sas.v1": RustSdkCryptoJs.VerificationMethod.SasV1,
"m.qr_code.scan.v1": RustSdkCryptoJs.VerificationMethod.QrCodeScanV1,
"m.qr_code.show.v1": RustSdkCryptoJs.VerificationMethod.QrCodeShowV1,
"m.reciprocate.v1": RustSdkCryptoJs.VerificationMethod.ReciprocateV1
};
/**
* Convert a specced verification method identifier into a rust-side `VerificationMethod`.
*
* @param method - specced method identifier, for example `m.sas.v1`.
* @returns Rust-side `VerificationMethod` corresponding to `method`.
* @throws An error if the method is unknown.
*/
function verificationMethodIdentifierToMethod(method) {
const meth = verificationMethodsByIdentifier[method];
if (meth === undefined) {
throw new Error(`Unknown verification method ${method}`);
}
return meth;
}
//# sourceMappingURL=verification.js.map