@@ -31,18 +31,48 @@ import { buildFeatureSupportMap, Feature, ServerSupport } from "../feature";
3131import { logger } from "../logger" ;
3232import { 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+ */
3439enum 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+ */
6093enum 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+ */
67103export 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+ */
86126const 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 */
93136export 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 */
0 commit comments