Skip to content

Commit c4ea55b

Browse files
committed
Apply feedback from review
1 parent 066006d commit c4ea55b

File tree

3 files changed

+111
-51
lines changed

3 files changed

+111
-51
lines changed

spec/unit/rendezvous/rendezvousv2.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
RendezvousCode,
2323
RendezvousFailureReason,
2424
RendezvousIntent,
25-
SETUP_ADDITIONAL_DEVICE_FLOW_V2,
25+
SETUP_ADDITIONAL_DEVICE_FLOW,
2626
} from "../../../src/rendezvous";
2727
import {
2828
ECDHv2RendezvousCode as ECDHRendezvousCode,
@@ -245,7 +245,7 @@ describe("RendezvousV2", function () {
245245
const code = JSON.parse(aliceRz.code!) as RendezvousCode;
246246

247247
expect(code.intent).toEqual(RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE);
248-
expect(code.flow).toEqual(SETUP_ADDITIONAL_DEVICE_FLOW_V2.name);
248+
expect(code.flow).toEqual(SETUP_ADDITIONAL_DEVICE_FLOW.name);
249249
expect(code.rendezvous?.algorithm).toEqual("org.matrix.msc3903.rendezvous.v2.curve25519-aes-sha256");
250250
expect(code.rendezvous?.transport.type).toEqual("org.matrix.msc3886.http.v1");
251251
expect((code.rendezvous?.transport as MSC3886SimpleHttpRendezvousTransportDetails).uri).toEqual(

src/rendezvous/MSC3906Rendezvous.ts

+89-42
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,48 @@ import { buildFeatureSupportMap, Feature, ServerSupport } from "../feature";
3131
import { logger } from "../logger";
3232
import { sleep } from "../utils";
3333

34+
/**
35+
* These are the possible types of payload that are used in
36+
* [MSC3906](https://github.com/matrix-org/matrix-spec-proposals/pull/3906) payloads.
37+
* The values are used in the `type` field.
38+
*/
3439
enum PayloadType {
3540
/**
3641
* @deprecated Only used in MSC3906 v1
3742
*/
3843
Finish = "m.login.finish",
44+
/**
45+
* Indicates that a new device is ready to proceed with the setup process.
46+
*/
3947
Progress = "m.login.progress",
48+
/**
49+
* Used by the new device to indicate which protocol to use.
50+
*/
4051
Protocol = "m.login.protocol",
52+
/**
53+
* Used for the new device to indicate which protocols are supported by the existing device and
54+
* homeserver.
55+
*/
4156
Protocols = "m.login.protocols",
57+
/**
58+
* Indicates that the sign of the new device was approved by the user on the existing device.
59+
*/
4260
Approved = "m.login.approved",
61+
/**
62+
* Indicates that the new device has signed in successfully.
63+
*/
4364
Success = "m.login.success",
65+
/**
66+
* Indicates that the new device has been successfully verified by the existing device.
67+
*/
4468
Verified = "m.login.verified",
69+
/**
70+
* Indicates that the login failed.
71+
*/
4572
Failure = "m.login.failure",
73+
/**
74+
* Indicates that the user declined the login on the existing device.
75+
*/
4676
Declined = "m.login.declined",
4777
}
4878

@@ -57,14 +87,21 @@ enum Outcome {
5787
Unsupported = "unsupported",
5888
}
5989

90+
/**
91+
* Used in the `reason` field of the `m.login.failure` payload.
92+
*/
6093
enum FailureReason {
6194
Cancelled = "cancelled",
6295
Unsupported = "unsupported",
6396
E2EESecurityError = "e2ee_security_error",
6497
IncompatibleIntent = "incompatible_intent",
6598
}
6699

100+
/**
101+
* This represents an [MSC3906](https://github.com/matrix-org/matrix-spec-proposals/pull/3906) payload.
102+
*/
67103
export interface MSC3906RendezvousPayload {
104+
/** The type of the payload */
68105
type: PayloadType;
69106
intent?: RendezvousIntent;
70107
/**
@@ -83,57 +120,72 @@ export interface MSC3906RendezvousPayload {
83120
homeserver?: string;
84121
}
85122

123+
/**
124+
* Represents the use of an `m.login.token` obtained from an existing device to sign in on a new device.
125+
*/
86126
const LOGIN_TOKEN_PROTOCOL = new UnstableValue("login_token", "org.matrix.msc3906.login_token");
87127

88128
/**
89-
* Implements MSC3906 to allow a user to sign in on a new device using QR code.
90-
* This implementation only supports generating a QR code on a device that is already signed in.
129+
* This class can be used to complete a "rendezvous flow" as defined in MSC3906.
130+
*
131+
* Currently it only supports being used on a device that is already signed in that wishes to help sign in
132+
* another device.
133+
*
91134
* Note that this is UNSTABLE and may have breaking changes without notice.
92135
*/
93136
export class MSC3906Rendezvous {
94137
private newDeviceId?: string;
95138
private newDeviceKey?: string;
96139
private ourIntent: RendezvousIntent = RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE;
97-
private v1FallbackEnabled: boolean;
140+
// if true then we follow the v1 flow, otherwise we follow the v2 flow
141+
private usingV1Flow: boolean;
98142
private _code?: string;
99143

100144
/**
101-
* @param channel - The secure channel used for communication
102-
* @param client - The Matrix client in used on the device already logged in
103-
* @param onFailure - Callback for when the rendezvous fails
104-
* @param flow - The flow to use. Defaults to MSC3906 v1 for backwards compatibility.
145+
* Creates an instance that can be used to manage the execution of a rendezvous flow.
146+
*
147+
* @param channel - The rendezvous channel that should be used for communication with the other device
148+
* @param client - The Matrix client that should be used.
149+
* @param onFailure - Optional callback function to be notified of rendezvous failures.
150+
* @param flow - The rendezvous flow to use. Defaults to setting up an additional device using MSC3906 v1,
151+
* for backwards compatibility.
105152
*/
106153
public constructor(
107154
private channel: RendezvousChannel<MSC3906RendezvousPayload>,
108155
private client: MatrixClient,
109156
public onFailure?: RendezvousFailureListener,
110157
private flow: RendezvousFlow = SETUP_ADDITIONAL_DEVICE_FLOW_V1,
111158
) {
112-
this.v1FallbackEnabled = flow === SETUP_ADDITIONAL_DEVICE_FLOW_V1;
159+
this.usingV1Flow = flow === SETUP_ADDITIONAL_DEVICE_FLOW_V1;
113160
}
114161

115162
/**
116-
* Returns the code representing the rendezvous suitable for rendering in a QR code or undefined if not generated yet.
163+
* @returns The code representing the rendezvous suitable for rendering in a QR code or undefined if not generated yet.
117164
*/
118165
public get code(): string | undefined {
119166
return this._code;
120167
}
121168

122169
/**
123-
* Generate the code including doing partial set up of the channel where required.
170+
* Generate the code including doing partial set up of the channel where required. This code could be encoded in a QR.
124171
*/
125172
public async generateCode(): Promise<void> {
126173
if (this._code) {
127174
return;
128175
}
129176

130-
const raw = this.v1FallbackEnabled
177+
const raw = this.usingV1Flow
131178
? await this.channel.generateCode(this.ourIntent)
132179
: await this.channel.generateCode(this.ourIntent, this.flow);
133180
this._code = JSON.stringify(raw);
134181
}
135182

136183
/**
184+
* Call this after the code has been shown to the user (perhaps in a QR). It will poll for the other device
185+
* at the rendezvous point and start the process of setting up the new device.
186+
*
187+
* If successful then the user should be asked to approve the login of the other device whilst displaying the
188+
* returned checksum code which the user should verify matches the code shown on the other device.
137189
*
138190
* @returns the checksum of the secure channel if the rendezvous set up was successful, otherwise undefined
139191
*/
@@ -147,7 +199,7 @@ export class MSC3906Rendezvous {
147199
if (features.get(Feature.LoginTokenRequest) === ServerSupport.Unsupported) {
148200
logger.info("Server doesn't support MSC3882");
149201
await this.send(
150-
this.v1FallbackEnabled
202+
this.usingV1Flow
151203
? { type: PayloadType.Finish, outcome: Outcome.Unsupported }
152204
: { type: PayloadType.Failure, reason: FailureReason.Unsupported },
153205
);
@@ -156,16 +208,21 @@ export class MSC3906Rendezvous {
156208
}
157209

158210
await this.send({
159-
type: this.v1FallbackEnabled ? PayloadType.Progress : PayloadType.Protocols,
211+
type: this.usingV1Flow ? PayloadType.Progress : PayloadType.Protocols,
160212
protocols: [LOGIN_TOKEN_PROTOCOL.name],
161213
});
162214

163215
logger.info("Waiting for other device to chose protocol");
164216
const nextPayload = await this.receive();
165217

166-
this.checkForV1Fallback(nextPayload);
218+
// even if we didn't start in v1 mode we might detect that the other device is v1:
219+
// - the finish payload is only used in v1
220+
// - a progress payload is only sent at this point in v1, in v2 the use of it is different
221+
if (nextPayload.type === PayloadType.Finish || nextPayload.type === PayloadType.Progress) {
222+
this.usingV1Flow = true;
223+
}
167224

168-
const protocol = this.v1FallbackEnabled
225+
const protocol = this.usingV1Flow
169226
? await this.handleV1ProtocolPayload(nextPayload)
170227
: await this.handleV2ProtocolPayload(nextPayload);
171228

@@ -178,18 +235,6 @@ export class MSC3906Rendezvous {
178235
return checksum;
179236
}
180237

181-
private checkForV1Fallback({ type }: MSC3906RendezvousPayload): void {
182-
// even if we didn't start in v1 fallback we might detect that the other device is v1
183-
if (type === PayloadType.Finish || type === PayloadType.Progress) {
184-
// this is a PDU from a v1 flow so use fallback mode
185-
this.v1FallbackEnabled = true;
186-
}
187-
}
188-
189-
/**
190-
*
191-
* @returns true if the protocol was received successfully, false otherwise
192-
*/
193238
private async handleV1ProtocolPayload({
194239
type,
195240
protocol,
@@ -223,10 +268,6 @@ export class MSC3906Rendezvous {
223268
return protocol;
224269
}
225270

226-
/**
227-
*
228-
* @returns true if the protocol was received successfully, false otherwise
229-
*/
230271
private async handleV2ProtocolPayload({
231272
type,
232273
protocol,
@@ -275,18 +316,26 @@ export class MSC3906Rendezvous {
275316
await this.channel.send(payload);
276317
}
277318

319+
/**
320+
* Call this if the user has declined the login.
321+
*/
278322
public async declineLoginOnExistingDevice(): Promise<void> {
279323
logger.info("User declined sign in");
280324
await this.send(
281-
this.v1FallbackEnabled
282-
? { type: PayloadType.Finish, outcome: Outcome.Declined }
283-
: { type: PayloadType.Declined },
325+
this.usingV1Flow ? { type: PayloadType.Finish, outcome: Outcome.Declined } : { type: PayloadType.Declined },
284326
);
285327
}
286328

329+
/**
330+
* Call this if the user has approved the login.
331+
*
332+
* @param loginToken - the login token to send to the new device for it to complete the login flow
333+
* @returns if the new device successfully completed the login flow and provided their device id then the device id is
334+
* returned, otherwise undefined
335+
*/
287336
public async approveLoginOnExistingDevice(loginToken: string): Promise<string | undefined> {
288337
await this.channel.send({
289-
type: this.v1FallbackEnabled ? PayloadType.Progress : PayloadType.Approved,
338+
type: this.usingV1Flow ? PayloadType.Progress : PayloadType.Approved,
290339
login_token: loginToken,
291340
homeserver: this.client.baseUrl,
292341
});
@@ -298,10 +347,7 @@ export class MSC3906Rendezvous {
298347
}
299348
const { type, outcome, device_id: deviceId, device_key: deviceKey } = res;
300349

301-
if (
302-
(this.v1FallbackEnabled && outcome !== "success") ||
303-
(!this.v1FallbackEnabled && type !== PayloadType.Success)
304-
) {
350+
if ((this.usingV1Flow && outcome !== "success") || (!this.usingV1Flow && type !== PayloadType.Success)) {
305351
throw new Error("Linking failed");
306352
}
307353

@@ -339,8 +385,8 @@ export class MSC3906Rendezvous {
339385
const masterPublicKey = this.client.crypto.crossSigningInfo.getId("master")!;
340386

341387
await this.send({
342-
type: this.v1FallbackEnabled ? PayloadType.Finish : PayloadType.Verified,
343-
outcome: this.v1FallbackEnabled ? Outcome.Verified : undefined,
388+
type: this.usingV1Flow ? PayloadType.Finish : PayloadType.Verified,
389+
outcome: this.usingV1Flow ? Outcome.Verified : undefined,
344390
verifying_device_id: this.client.getDeviceId()!,
345391
verifying_device_key: this.client.getDeviceEd25519Key()!,
346392
master_key: masterPublicKey,
@@ -350,7 +396,8 @@ export class MSC3906Rendezvous {
350396
}
351397

352398
/**
353-
* Verify the device and cross-sign it.
399+
* Wait for a device to be visible via the homeserver and then verify/cross-sign it.
400+
*
354401
* @param timeout - time in milliseconds to wait for device to come online
355402
* @returns the new device info if the device was verified
356403
*/

src/rendezvous/RendezvousFlow.ts

+20-7
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,28 @@ limitations under the License.
1616

1717
import { UnstableValue } from "../NamespacedValue";
1818

19-
export const SETUP_ADDITIONAL_DEVICE_FLOW_V1 = "org.matrix.msc3906.v1";
20-
21-
export const SETUP_ADDITIONAL_DEVICE_FLOW_V2 = new UnstableValue(
22-
"m.setup.additional_device.v2",
19+
/**
20+
* A rendezvous flow which allows a user to set up a new device with the help of an existing device.
21+
* It is described in [MSC3906](https://github.com/matrix-org/matrix-spec-proposals/pull/3906)
22+
*/
23+
export const SETUP_ADDITIONAL_DEVICE_FLOW = new UnstableValue(
24+
"m.setup.additional_device",
2325
"org.matrix.msc3906.setup.additional_device.v2",
2426
);
2527

26-
// v1 is never included in the JSON, but we give it a name for the sake of determining the flow to use
28+
/**
29+
* Used to represent an older "v1" revision of the MSC3906 rendezvous flow to setup a new device.
30+
*
31+
* @deprecated Use MSC3906 v2 using {@link SETUP_ADDITIONAL_DEVICE_FLOW} instead.
32+
*/
33+
export const SETUP_ADDITIONAL_DEVICE_FLOW_V1 = "org.matrix.msc3906.v1";
34+
35+
/**
36+
* Used to identify a rendezvous flow that is being used. The identifier is transmitted in a QR code or
37+
* some other mechanism that is convenient to the user.
38+
*/
2739
export type RendezvousFlow =
28-
| typeof SETUP_ADDITIONAL_DEVICE_FLOW_V2.name
29-
| typeof SETUP_ADDITIONAL_DEVICE_FLOW_V2.altName
40+
| typeof SETUP_ADDITIONAL_DEVICE_FLOW.name
41+
| typeof SETUP_ADDITIONAL_DEVICE_FLOW.altName
42+
// v1 is never included in the JSON, but we give it a name for the sake of determining the flow to use
3043
| typeof SETUP_ADDITIONAL_DEVICE_FLOW_V1;

0 commit comments

Comments
 (0)