1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-26 17:03:12 +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:
Hugh Nimmo-Smith
2023-04-05 12:12:29 -04:00
committed by GitHub
parent 72a2b6d571
commit 59784aa9fe
6 changed files with 110 additions and 10 deletions

View File

@@ -1127,22 +1127,51 @@ describe("MatrixClient", function () {
describe("requestLoginToken", () => {
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 uiaData = {};
const prom = client!.requestLoginToken(uiaData);
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);
await httpBackend!.flush("");
expect(await prom).toStrictEqual(response);
});
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();
httpBackend!.when("POST", "/unstable/org.matrix.msc3882/login/token", {}).respond(200, response);
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 });
});
});

View File

@@ -38,6 +38,7 @@ function makeMockClient(opts: {
deviceId: string;
deviceKey?: string;
msc3882Enabled: boolean;
msc3882r0Only: boolean;
msc3886Enabled: boolean;
devices?: Record<string, Partial<DeviceInfo>>;
verificationFunction?: (
@@ -58,6 +59,17 @@ function makeMockClient(opts: {
},
};
},
getCapabilities() {
return opts.msc3882r0Only
? {}
: {
capabilities: {
"org.matrix.msc3882.get_login_token": {
enabled: opts.msc3882Enabled,
},
},
};
},
getUserId() {
return opts.userId;
},
@@ -111,6 +123,7 @@ describe("Rendezvous", function () {
deviceId: "DEVICEID",
msc3886Enabled: false,
msc3882Enabled: true,
msc3882r0Only: true,
});
httpBackend.when("POST", "https://fallbackserver/rz").response = {
body: null,
@@ -166,7 +179,13 @@ describe("Rendezvous", function () {
await aliceRz.close();
});
it("no protocols", async function () {
async function testNoProtocols({
msc3882Enabled,
msc3882r0Only,
}: {
msc3882Enabled: boolean;
msc3882r0Only: boolean;
}) {
const aliceTransport = makeTransport("Alice");
const bobTransport = makeTransport("Bob", "https://test.rz/999999");
transports.push(aliceTransport, bobTransport);
@@ -178,8 +197,9 @@ describe("Rendezvous", function () {
const alice = makeMockClient({
userId: "alice",
deviceId: "ALICE",
msc3882Enabled: false,
msc3886Enabled: false,
msc3882Enabled,
msc3882r0Only,
});
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
const aliceRz = new MSC3906Rendezvous(aliceEcdh, alice);
@@ -218,6 +238,14 @@ describe("Rendezvous", function () {
await aliceStartProm;
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 () {
@@ -233,6 +261,7 @@ describe("Rendezvous", function () {
userId: "alice",
deviceId: "ALICE",
msc3882Enabled: true,
msc3882r0Only: false,
msc3886Enabled: false,
});
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
@@ -291,6 +320,7 @@ describe("Rendezvous", function () {
userId: "alice",
deviceId: "ALICE",
msc3882Enabled: true,
msc3882r0Only: false,
msc3886Enabled: false,
});
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
@@ -349,6 +379,7 @@ describe("Rendezvous", function () {
userId: "alice",
deviceId: "ALICE",
msc3882Enabled: true,
msc3882r0Only: false,
msc3886Enabled: false,
});
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
@@ -409,6 +440,7 @@ describe("Rendezvous", function () {
userId: "alice",
deviceId: "ALICE",
msc3882Enabled: true,
msc3882r0Only: false,
msc3886Enabled: false,
});
const aliceEcdh = new MSC3903ECDHRendezvousChannel(aliceTransport, undefined, aliceOnFailure);
@@ -477,6 +509,7 @@ describe("Rendezvous", function () {
userId: "alice",
deviceId: "ALICE",
msc3882Enabled: true,
msc3882r0Only: false,
msc3886Enabled: false,
devices,
deviceKey: "aaaa",

View File

@@ -112,6 +112,12 @@ export interface LoginTokenPostResponse {
login_token: string;
/**
* Expiration in seconds.
*
* @deprecated this is only provided for compatibility with original revision of the MSC.
*/
expires_in: number;
/**
* Expiration in milliseconds.
*/
expires_in_ms: number;
}

View File

@@ -488,11 +488,17 @@ export interface IChangePasswordCapability 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 {
[key: string]: any;
"m.change_password"?: IChangePasswordCapability;
"m.room_versions"?: IRoomVersionsCapability;
"io.element.thread"?: IThreadsCapability;
[UNSTABLE_MSC3882_CAPABILITY.name]?: IMSC3882GetLoginTokenCapability;
[UNSTABLE_MSC3882_CAPABILITY.altName]?: IMSC3882GetLoginTokenCapability;
}
/* eslint-disable camelcase */
@@ -7808,15 +7814,33 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @returns Promise which resolves: On success, the token response
* 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 };
return this.http.authedRequest(
const res = await this.http.authedRequest<UIAResponse<LoginTokenPostResponse>>(
Method.Post,
"/org.matrix.msc3882/login/token",
endpoint,
undefined, // no query params
body,
{ 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;
}
/**

View File

@@ -25,6 +25,9 @@ export enum ServerSupport {
export enum Feature {
Thread = "Thread",
ThreadUnreadNotifications = "ThreadUnreadNotifications",
/**
* @deprecated this is now exposed as a capability not a feature
*/
LoginTokenRequest = "LoginTokenRequest",
RelationBasedRedactions = "RelationBasedRedactions",
AccountDataDeletion = "AccountDataDeletion",

View File

@@ -17,7 +17,7 @@ limitations under the License.
import { UnstableValue } from "matrix-events-sdk";
import { RendezvousChannel, RendezvousFailureListener, RendezvousFailureReason, RendezvousIntent } from ".";
import { MatrixClient } from "../client";
import { IMSC3882GetLoginTokenCapability, MatrixClient, UNSTABLE_MSC3882_CAPABILITY } from "../client";
import { CrossSigningInfo } from "../crypto/CrossSigning";
import { DeviceInfo } from "../crypto/deviceinfo";
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}`);
// 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 capability = UNSTABLE_MSC3882_CAPABILITY.findIn<IMSC3882GetLoginTokenCapability>(capabilities);
// 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");
await this.send({ type: PayloadType.Finish, outcome: Outcome.Unsupported });
await this.cancel(RendezvousFailureReason.HomeserverLacksSupport);