You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-25 05:23:13 +03:00
Support for MSC3882 revision 1 (#3228)
* Support for MSC3882 revision 1 * Additional comments * Revised field names * Use UnstableValue for capability
This commit is contained in:
@@ -1127,22 +1127,51 @@ describe("MatrixClient", function () {
|
|||||||
|
|
||||||
describe("requestLoginToken", () => {
|
describe("requestLoginToken", () => {
|
||||||
it("should hit the expected API endpoint with UIA", async () => {
|
it("should hit the expected API endpoint with UIA", async () => {
|
||||||
|
httpBackend!
|
||||||
|
.when("GET", "/capabilities")
|
||||||
|
.respond(200, { capabilities: { "org.matrix.msc3882.get_login_token": { enabled: true } } });
|
||||||
const response = {};
|
const response = {};
|
||||||
const uiaData = {};
|
const uiaData = {};
|
||||||
const prom = client!.requestLoginToken(uiaData);
|
const prom = client!.requestLoginToken(uiaData);
|
||||||
httpBackend!
|
httpBackend!
|
||||||
.when("POST", "/unstable/org.matrix.msc3882/login/token", { auth: uiaData })
|
.when("POST", "/unstable/org.matrix.msc3882/login/get_token", { auth: uiaData })
|
||||||
.respond(200, response);
|
.respond(200, response);
|
||||||
await httpBackend!.flush("");
|
await httpBackend!.flush("");
|
||||||
expect(await prom).toStrictEqual(response);
|
expect(await prom).toStrictEqual(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should hit the expected API endpoint without UIA", async () => {
|
it("should hit the expected API endpoint without UIA", async () => {
|
||||||
const response = {};
|
httpBackend!
|
||||||
|
.when("GET", "/capabilities")
|
||||||
|
.respond(200, { capabilities: { "org.matrix.msc3882.get_login_token": { enabled: true } } });
|
||||||
|
const response = { login_token: "xyz", expires_in_ms: 5000 };
|
||||||
|
const prom = client!.requestLoginToken();
|
||||||
|
httpBackend!.when("POST", "/unstable/org.matrix.msc3882/login/get_token", {}).respond(200, response);
|
||||||
|
await httpBackend!.flush("");
|
||||||
|
// check that expires_in has been populated for compatibility with r0
|
||||||
|
expect(await prom).toStrictEqual({ ...response, expires_in: 5 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should hit the r1 endpoint when capability is disabled", async () => {
|
||||||
|
httpBackend!
|
||||||
|
.when("GET", "/capabilities")
|
||||||
|
.respond(200, { capabilities: { "org.matrix.msc3882.get_login_token": { enabled: false } } });
|
||||||
|
const response = { login_token: "xyz", expires_in_ms: 5000 };
|
||||||
|
const prom = client!.requestLoginToken();
|
||||||
|
httpBackend!.when("POST", "/unstable/org.matrix.msc3882/login/get_token", {}).respond(200, response);
|
||||||
|
await httpBackend!.flush("");
|
||||||
|
// check that expires_in has been populated for compatibility with r0
|
||||||
|
expect(await prom).toStrictEqual({ ...response, expires_in: 5 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should hit the r0 endpoint for fallback", async () => {
|
||||||
|
httpBackend!.when("GET", "/capabilities").respond(200, {});
|
||||||
|
const response = { login_token: "xyz", expires_in: 5 };
|
||||||
const prom = client!.requestLoginToken();
|
const prom = client!.requestLoginToken();
|
||||||
httpBackend!.when("POST", "/unstable/org.matrix.msc3882/login/token", {}).respond(200, response);
|
httpBackend!.when("POST", "/unstable/org.matrix.msc3882/login/token", {}).respond(200, response);
|
||||||
await httpBackend!.flush("");
|
await httpBackend!.flush("");
|
||||||
expect(await prom).toStrictEqual(response);
|
// check that expires_in has been populated for compatibility with r1
|
||||||
|
expect(await prom).toStrictEqual({ ...response, expires_in_ms: 5000 });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ function makeMockClient(opts: {
|
|||||||
deviceId: string;
|
deviceId: string;
|
||||||
deviceKey?: string;
|
deviceKey?: string;
|
||||||
msc3882Enabled: boolean;
|
msc3882Enabled: boolean;
|
||||||
|
msc3882r0Only: boolean;
|
||||||
msc3886Enabled: boolean;
|
msc3886Enabled: boolean;
|
||||||
devices?: Record<string, Partial<DeviceInfo>>;
|
devices?: Record<string, Partial<DeviceInfo>>;
|
||||||
verificationFunction?: (
|
verificationFunction?: (
|
||||||
@@ -58,6 +59,17 @@ function makeMockClient(opts: {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
getCapabilities() {
|
||||||
|
return opts.msc3882r0Only
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
capabilities: {
|
||||||
|
"org.matrix.msc3882.get_login_token": {
|
||||||
|
enabled: opts.msc3882Enabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
getUserId() {
|
getUserId() {
|
||||||
return opts.userId;
|
return opts.userId;
|
||||||
},
|
},
|
||||||
@@ -111,6 +123,7 @@ describe("Rendezvous", function () {
|
|||||||
deviceId: "DEVICEID",
|
deviceId: "DEVICEID",
|
||||||
msc3886Enabled: false,
|
msc3886Enabled: false,
|
||||||
msc3882Enabled: true,
|
msc3882Enabled: true,
|
||||||
|
msc3882r0Only: true,
|
||||||
});
|
});
|
||||||
httpBackend.when("POST", "https://fallbackserver/rz").response = {
|
httpBackend.when("POST", "https://fallbackserver/rz").response = {
|
||||||
body: null,
|
body: null,
|
||||||
@@ -166,7 +179,13 @@ describe("Rendezvous", function () {
|
|||||||
await aliceRz.close();
|
await aliceRz.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("no protocols", async function () {
|
async function testNoProtocols({
|
||||||
|
msc3882Enabled,
|
||||||
|
msc3882r0Only,
|
||||||
|
}: {
|
||||||
|
msc3882Enabled: boolean;
|
||||||
|
msc3882r0Only: boolean;
|
||||||
|
}) {
|
||||||
const aliceTransport = makeTransport("Alice");
|
const aliceTransport = makeTransport("Alice");
|
||||||
const bobTransport = makeTransport("Bob", "https://test.rz/999999");
|
const bobTransport = makeTransport("Bob", "https://test.rz/999999");
|
||||||
transports.push(aliceTransport, bobTransport);
|
transports.push(aliceTransport, bobTransport);
|
||||||
@@ -178,8 +197,9 @@ describe("Rendezvous", function () {
|
|||||||
const alice = makeMockClient({
|
const alice = makeMockClient({
|
||||||
userId: "alice",
|
userId: "alice",
|
||||||
deviceId: "ALICE",
|
deviceId: "ALICE",
|
||||||
msc3882Enabled: false,
|
|
||||||
msc3886Enabled: false,
|
msc3886Enabled: false,
|
||||||
|
msc3882Enabled,
|
||||||
|
msc3882r0Only,
|
||||||
});
|
});
|
||||||
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
|
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
|
||||||
const aliceRz = new MSC3906Rendezvous(aliceEcdh, alice);
|
const aliceRz = new MSC3906Rendezvous(aliceEcdh, alice);
|
||||||
@@ -218,6 +238,14 @@ describe("Rendezvous", function () {
|
|||||||
|
|
||||||
await aliceStartProm;
|
await aliceStartProm;
|
||||||
await bobStartPromise;
|
await bobStartPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
it("no protocols - r0", async function () {
|
||||||
|
await testNoProtocols({ msc3882Enabled: false, msc3882r0Only: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("no protocols - r1", async function () {
|
||||||
|
await testNoProtocols({ msc3882Enabled: false, msc3882r0Only: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("new device declines protocol with outcome unsupported", async function () {
|
it("new device declines protocol with outcome unsupported", async function () {
|
||||||
@@ -233,6 +261,7 @@ describe("Rendezvous", function () {
|
|||||||
userId: "alice",
|
userId: "alice",
|
||||||
deviceId: "ALICE",
|
deviceId: "ALICE",
|
||||||
msc3882Enabled: true,
|
msc3882Enabled: true,
|
||||||
|
msc3882r0Only: false,
|
||||||
msc3886Enabled: false,
|
msc3886Enabled: false,
|
||||||
});
|
});
|
||||||
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
|
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
|
||||||
@@ -291,6 +320,7 @@ describe("Rendezvous", function () {
|
|||||||
userId: "alice",
|
userId: "alice",
|
||||||
deviceId: "ALICE",
|
deviceId: "ALICE",
|
||||||
msc3882Enabled: true,
|
msc3882Enabled: true,
|
||||||
|
msc3882r0Only: false,
|
||||||
msc3886Enabled: false,
|
msc3886Enabled: false,
|
||||||
});
|
});
|
||||||
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
|
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
|
||||||
@@ -349,6 +379,7 @@ describe("Rendezvous", function () {
|
|||||||
userId: "alice",
|
userId: "alice",
|
||||||
deviceId: "ALICE",
|
deviceId: "ALICE",
|
||||||
msc3882Enabled: true,
|
msc3882Enabled: true,
|
||||||
|
msc3882r0Only: false,
|
||||||
msc3886Enabled: false,
|
msc3886Enabled: false,
|
||||||
});
|
});
|
||||||
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
|
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
|
||||||
@@ -409,6 +440,7 @@ describe("Rendezvous", function () {
|
|||||||
userId: "alice",
|
userId: "alice",
|
||||||
deviceId: "ALICE",
|
deviceId: "ALICE",
|
||||||
msc3882Enabled: true,
|
msc3882Enabled: true,
|
||||||
|
msc3882r0Only: false,
|
||||||
msc3886Enabled: false,
|
msc3886Enabled: false,
|
||||||
});
|
});
|
||||||
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
|
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
|
||||||
@@ -477,6 +509,7 @@ describe("Rendezvous", function () {
|
|||||||
userId: "alice",
|
userId: "alice",
|
||||||
deviceId: "ALICE",
|
deviceId: "ALICE",
|
||||||
msc3882Enabled: true,
|
msc3882Enabled: true,
|
||||||
|
msc3882r0Only: false,
|
||||||
msc3886Enabled: false,
|
msc3886Enabled: false,
|
||||||
devices,
|
devices,
|
||||||
deviceKey: "aaaa",
|
deviceKey: "aaaa",
|
||||||
|
|||||||
@@ -112,6 +112,12 @@ export interface LoginTokenPostResponse {
|
|||||||
login_token: string;
|
login_token: string;
|
||||||
/**
|
/**
|
||||||
* Expiration in seconds.
|
* Expiration in seconds.
|
||||||
|
*
|
||||||
|
* @deprecated this is only provided for compatibility with original revision of the MSC.
|
||||||
*/
|
*/
|
||||||
expires_in: number;
|
expires_in: number;
|
||||||
|
/**
|
||||||
|
* Expiration in milliseconds.
|
||||||
|
*/
|
||||||
|
expires_in_ms: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -488,11 +488,17 @@ export interface IChangePasswordCapability extends ICapability {}
|
|||||||
|
|
||||||
export interface IThreadsCapability extends ICapability {}
|
export interface IThreadsCapability extends ICapability {}
|
||||||
|
|
||||||
|
export interface IMSC3882GetLoginTokenCapability extends ICapability {}
|
||||||
|
|
||||||
|
export const UNSTABLE_MSC3882_CAPABILITY = new UnstableValue("m.get_login_token", "org.matrix.msc3882.get_login_token");
|
||||||
|
|
||||||
interface ICapabilities {
|
interface ICapabilities {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
"m.change_password"?: IChangePasswordCapability;
|
"m.change_password"?: IChangePasswordCapability;
|
||||||
"m.room_versions"?: IRoomVersionsCapability;
|
"m.room_versions"?: IRoomVersionsCapability;
|
||||||
"io.element.thread"?: IThreadsCapability;
|
"io.element.thread"?: IThreadsCapability;
|
||||||
|
[UNSTABLE_MSC3882_CAPABILITY.name]?: IMSC3882GetLoginTokenCapability;
|
||||||
|
[UNSTABLE_MSC3882_CAPABILITY.altName]?: IMSC3882GetLoginTokenCapability;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
@@ -7808,15 +7814,33 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
* @returns Promise which resolves: On success, the token response
|
* @returns Promise which resolves: On success, the token response
|
||||||
* or UIA auth data.
|
* or UIA auth data.
|
||||||
*/
|
*/
|
||||||
public requestLoginToken(auth?: IAuthData): Promise<UIAResponse<LoginTokenPostResponse>> {
|
public async requestLoginToken(auth?: IAuthData): Promise<UIAResponse<LoginTokenPostResponse>> {
|
||||||
|
// use capabilities to determine which revision of the MSC is being used
|
||||||
|
const capabilities = await this.getCapabilities();
|
||||||
|
// use r1 endpoint if capability is exposed otherwise use old r0 endpoint
|
||||||
|
const endpoint = UNSTABLE_MSC3882_CAPABILITY.findIn(capabilities)
|
||||||
|
? "/org.matrix.msc3882/login/get_token" // r1 endpoint
|
||||||
|
: "/org.matrix.msc3882/login/token"; // r0 endpoint
|
||||||
|
|
||||||
const body: UIARequest<{}> = { auth };
|
const body: UIARequest<{}> = { auth };
|
||||||
return this.http.authedRequest(
|
const res = await this.http.authedRequest<UIAResponse<LoginTokenPostResponse>>(
|
||||||
Method.Post,
|
Method.Post,
|
||||||
"/org.matrix.msc3882/login/token",
|
endpoint,
|
||||||
undefined, // no query params
|
undefined, // no query params
|
||||||
body,
|
body,
|
||||||
{ prefix: ClientPrefix.Unstable },
|
{ prefix: ClientPrefix.Unstable },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// the representation of expires_in changed from revision 0 to revision 1 so we populate
|
||||||
|
if ("login_token" in res) {
|
||||||
|
if (typeof res.expires_in_ms === "number") {
|
||||||
|
res.expires_in = Math.floor(res.expires_in_ms / 1000);
|
||||||
|
} else if (typeof res.expires_in === "number") {
|
||||||
|
res.expires_in_ms = res.expires_in * 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ export enum ServerSupport {
|
|||||||
export enum Feature {
|
export enum Feature {
|
||||||
Thread = "Thread",
|
Thread = "Thread",
|
||||||
ThreadUnreadNotifications = "ThreadUnreadNotifications",
|
ThreadUnreadNotifications = "ThreadUnreadNotifications",
|
||||||
|
/**
|
||||||
|
* @deprecated this is now exposed as a capability not a feature
|
||||||
|
*/
|
||||||
LoginTokenRequest = "LoginTokenRequest",
|
LoginTokenRequest = "LoginTokenRequest",
|
||||||
RelationBasedRedactions = "RelationBasedRedactions",
|
RelationBasedRedactions = "RelationBasedRedactions",
|
||||||
AccountDataDeletion = "AccountDataDeletion",
|
AccountDataDeletion = "AccountDataDeletion",
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ limitations under the License.
|
|||||||
import { UnstableValue } from "matrix-events-sdk";
|
import { UnstableValue } from "matrix-events-sdk";
|
||||||
|
|
||||||
import { RendezvousChannel, RendezvousFailureListener, RendezvousFailureReason, RendezvousIntent } from ".";
|
import { RendezvousChannel, RendezvousFailureListener, RendezvousFailureReason, RendezvousIntent } from ".";
|
||||||
import { MatrixClient } from "../client";
|
import { IMSC3882GetLoginTokenCapability, MatrixClient, UNSTABLE_MSC3882_CAPABILITY } from "../client";
|
||||||
import { CrossSigningInfo } from "../crypto/CrossSigning";
|
import { CrossSigningInfo } from "../crypto/CrossSigning";
|
||||||
import { DeviceInfo } from "../crypto/deviceinfo";
|
import { DeviceInfo } from "../crypto/deviceinfo";
|
||||||
import { buildFeatureSupportMap, Feature, ServerSupport } from "../feature";
|
import { buildFeatureSupportMap, Feature, ServerSupport } from "../feature";
|
||||||
@@ -100,9 +100,14 @@ export class MSC3906Rendezvous {
|
|||||||
|
|
||||||
logger.info(`Connected to secure channel with checksum: ${checksum} our intent is ${this.ourIntent}`);
|
logger.info(`Connected to secure channel with checksum: ${checksum} our intent is ${this.ourIntent}`);
|
||||||
|
|
||||||
|
// in r1 of MSC3882 the availability is exposed as a capability
|
||||||
|
const capabilities = await this.client.getCapabilities();
|
||||||
|
// in r0 of MSC3882 the availability is exposed as a feature flag
|
||||||
const features = await buildFeatureSupportMap(await this.client.getVersions());
|
const features = await buildFeatureSupportMap(await this.client.getVersions());
|
||||||
|
const capability = UNSTABLE_MSC3882_CAPABILITY.findIn<IMSC3882GetLoginTokenCapability>(capabilities);
|
||||||
|
|
||||||
// determine available protocols
|
// determine available protocols
|
||||||
if (features.get(Feature.LoginTokenRequest) === ServerSupport.Unsupported) {
|
if (!capability?.enabled && features.get(Feature.LoginTokenRequest) === ServerSupport.Unsupported) {
|
||||||
logger.info("Server doesn't support MSC3882");
|
logger.info("Server doesn't support MSC3882");
|
||||||
await this.send({ type: PayloadType.Finish, outcome: Outcome.Unsupported });
|
await this.send({ type: PayloadType.Finish, outcome: Outcome.Unsupported });
|
||||||
await this.cancel(RendezvousFailureReason.HomeserverLacksSupport);
|
await this.cancel(RendezvousFailureReason.HomeserverLacksSupport);
|
||||||
|
|||||||
Reference in New Issue
Block a user