From a042f4d0af0c0e196b557e37089f05d92eae440c Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 5 Sep 2018 17:07:39 +0100 Subject: [PATCH 1/4] Do full registration if HS doesn't support ILAG ILAG only works on HSes that allow registering without an email address, so whenever we redirect to the ILAG flow, check what registration flows the server supports, and if it doesn't offer one that's ILAG-compatible, prompt the user to go through the full registration process instead. This doesn't change all the entry points into ILAG, I'll do that in a separate commit. --- src/Registration.js | 96 +++++++++++++++++++++++++ src/components/structures/MatrixChat.js | 9 ++- src/i18n/strings/en_EN.json | 8 ++- 3 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 src/Registration.js diff --git a/src/Registration.js b/src/Registration.js new file mode 100644 index 0000000000..b3da7acb1e --- /dev/null +++ b/src/Registration.js @@ -0,0 +1,96 @@ +/* +Copyright 2018 New Vector Ltd + +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. +*/ + +/** + * Utility code for registering with a homeserver + * Note that this is currently *not* used by the actual + * registration code. + */ + +import dis from './dispatcher'; +import sdk from './index'; +import MatrixClientPeg from './MatrixClientPeg'; +import Modal from './Modal'; +import { _t } from './languageHandler'; + +/** + * Starts either the ILAG or full registration flow, depending + * on what the HS supports + */ +export async function startAnyRegistrationFlow(options) { + if (options === undefined) options = {}; + const flows = await _getRegistrationFlows(); + // look for an ILAG compatible flow. We define this as one + // which has only dummy or recaptcha flows. In practice it + // would support any stage InteractiveAuth supports, just not + // ones like email & msisdn which require the user to supply + // the relevant details in advance. We err on the side of + // caution though. + let hasIlagFlow = false; + for (const flow of flows) { + let flowSuitable = true; + for (const stage of flow.stages) { + if (!['m.login.dummy', 'm.login.recaptcha'].includes(stage)) { + flowSuitable = false; + break; + } + } + if (flowSuitable) { + hasIlagFlow = true; + break; + } + } + if (hasIlagFlow) { + dis.dispatch({ + action: 'view_set_mxid', + go_home_on_cancel: options.go_home_on_cancel, + }); + } else { + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + Modal.createTrackedDialog('Registration required', '', QuestionDialog, { + title: _t("Registration Required"), + description: _t("You need to register to do this. Would you like to register now?"), + button: _t("Register"), + onFinished: (proceed) => { + if (proceed) { + dis.dispatch({action: 'start_registration'}); + } else if (options.go_home_on_cancel) { + dis.dispatch({action: 'view_home_page'}); + } + } + }); + } +} + +async function _getRegistrationFlows() { + try { + await MatrixClientPeg.get().register( + null, + null, + undefined, + {}, + {}, + ); + console.log("Register request succeeded when it should have returned 401!"); + } catch (e) { + if (e.httpStatus === 401) { + return e.data.flows; + } + throw e; + } + throw new Error("Register request succeeded when it should have returned 401!"); +} + diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 7a13067976..002e439454 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -45,6 +45,7 @@ import createRoom from "../../createRoom"; import KeyRequestHandler from '../../KeyRequestHandler'; import { _t, getCurrentLanguage } from '../../languageHandler'; import SettingsStore, {SettingLevel} from "../../settings/SettingsStore"; +import { startAnyRegistrationFlow } from "../../Registration.js"; /** constants for MatrixChat.state.view */ const VIEWS = { @@ -471,7 +472,7 @@ export default React.createClass({ action: 'do_after_sync_prepared', deferred_action: payload, }); - dis.dispatch({action: 'view_set_mxid'}); + dis.dispatch({action: 'require_registration'}); return; } @@ -479,7 +480,11 @@ export default React.createClass({ case 'logout': Lifecycle.logout(); break; + case 'require_registration': + startAnyRegistrationFlow(payload); + break; case 'start_registration': + // This starts the full registration flow this._startRegistration(payload.params || {}); break; case 'start_login': @@ -945,7 +950,7 @@ export default React.createClass({ }); } dis.dispatch({ - action: 'view_set_mxid', + action: 'require_registration', // If the set_mxid dialog is cancelled, view /home because if the browser // was pointing at /user/@someone:domain?action=chat, the URL needs to be // reset so that they can revisit /user/.. // (and trigger diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 995614225a..d06b986474 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -87,6 +87,8 @@ "Unable to enable Notifications": "Unable to enable Notifications", "This email address was not found": "This email address was not found", "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Your email address does not appear to be associated with a Matrix ID on this Homeserver.", + "Registration Required": "Registration Required", + "You need to register to do this. Would you like to register now?": "You need to register to do this. Would you like to register now?", "Default": "Default", "Restricted": "Restricted", "Moderator": "Moderator", @@ -146,7 +148,6 @@ "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.", "Displays action": "Displays action", "Forces the current outbound group session in an encrypted room to be discarded": "Forces the current outbound group session in an encrypted room to be discarded", - "Error Discarding Session": "Error Discarding Session", "Unrecognised command:": "Unrecognised command:", "Reason": "Reason", "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", @@ -398,8 +399,6 @@ "At this time it is not possible to reply with a file so this will be sent without being a reply.": "At this time it is not possible to reply with a file so this will be sent without being a reply.", "Upload Files": "Upload Files", "Are you sure you want to upload the following files?": "Are you sure you want to upload the following files?", - "This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.", - "The conversation continues here.": "The conversation continues here.", "Encrypted room": "Encrypted room", "Unencrypted room": "Unencrypted room", "Hangup": "Hangup", @@ -411,6 +410,8 @@ "Send a reply (unencrypted)…": "Send a reply (unencrypted)…", "Send an encrypted message…": "Send an encrypted message…", "Send a message (unencrypted)…": "Send a message (unencrypted)…", + "This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.", + "The conversation continues here.": "The conversation continues here.", "You do not have permission to post to this room": "You do not have permission to post to this room", "Turn Markdown on": "Turn Markdown on", "Turn Markdown off": "Turn Markdown off", @@ -1201,6 +1202,7 @@ "Failed to fetch avatar URL": "Failed to fetch avatar URL", "Set a display name:": "Set a display name:", "Upload an avatar:": "Upload an avatar:", + "Unable to query for supported registration methods": "Unable to query for supported registration methods", "This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.", "Missing password.": "Missing password.", "Passwords don't match.": "Passwords don't match.", From 27fa21e403a2a60477ae1ad073c27280246efd05 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 5 Sep 2018 18:08:49 +0100 Subject: [PATCH 2/4] Replace view_set_mxid with require_registration To put all the other flows through the to-ilag-or-not-to-ilag flow --- src/components/structures/GroupView.js | 4 ++-- src/components/structures/MatrixChat.js | 2 +- src/components/structures/RightPanel.js | 2 +- src/components/structures/RoomDirectory.js | 2 +- src/components/structures/RoomView.js | 4 ++-- src/components/views/room_settings/ColorSettings.js | 2 +- src/components/views/rooms/MemberInfo.js | 2 +- src/components/views/rooms/MessageComposer.js | 2 +- src/createRoom.js | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 9498c2aa2a..d104019a01 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -480,7 +480,7 @@ export default React.createClass({ group_id: groupId, }, }); - dis.dispatch({action: 'view_set_mxid'}); + dis.dispatch({action: 'require_registration'}); willDoOnboarding = true; } this.setState({ @@ -724,7 +724,7 @@ export default React.createClass({ _onJoinClick: async function() { if (this._matrixClient.isGuest()) { - dis.dispatch({action: 'view_set_mxid'}); + dis.dispatch({action: 'require_registration'}); return; } diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 002e439454..333e200e44 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1428,7 +1428,7 @@ export default React.createClass({ } else if (screen == 'start') { this.showScreen('home'); dis.dispatch({ - action: 'view_set_mxid', + action: 'require_registration', }); } else if (screen == 'directory') { dis.dispatch({ diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js index bd4ed722cb..47b3df65cb 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.js @@ -160,7 +160,7 @@ module.exports = React.createClass({ onInviteButtonClick: function() { if (this.context.matrixClient.isGuest()) { - dis.dispatch({action: 'view_set_mxid'}); + dis.dispatch({action: 'require_registration'}); return; } diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js index 76360383d6..f417932fd0 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.js @@ -354,7 +354,7 @@ module.exports = React.createClass({ // to the directory. if (MatrixClientPeg.get().isGuest()) { if (!room.world_readable && !room.guest_can_join) { - dis.dispatch({action: 'view_set_mxid'}); + dis.dispatch({action: 'require_registration'}); return; } } diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index ca06243ed1..54f730de5d 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -915,7 +915,7 @@ module.exports = React.createClass({ dis.dispatch({action: 'focus_composer'}); if (MatrixClientPeg.get().isGuest()) { - dis.dispatch({action: 'view_set_mxid'}); + dis.dispatch({action: 'require_registration'}); return; } @@ -946,7 +946,7 @@ module.exports = React.createClass({ injectSticker: function(url, info, text) { if (MatrixClientPeg.get().isGuest()) { - dis.dispatch({action: 'view_set_mxid'}); + dis.dispatch({action: 'require_registration'}); return; } diff --git a/src/components/views/room_settings/ColorSettings.js b/src/components/views/room_settings/ColorSettings.js index e82d3ffb0a..30621f9c15 100644 --- a/src/components/views/room_settings/ColorSettings.js +++ b/src/components/views/room_settings/ColorSettings.js @@ -90,7 +90,7 @@ module.exports = React.createClass({ secondary_color: this.state.secondary_color, }).catch(function(err) { if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN') { - dis.dispatch({action: 'view_set_mxid'}); + dis.dispatch({action: 'require_registration'}); } }); } diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index c635f09e2c..e6e6350083 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -429,7 +429,7 @@ module.exports = withMatrixClient(React.createClass({ console.log("Mod toggle success"); }, function(err) { if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN') { - dis.dispatch({action: 'view_set_mxid'}); + dis.dispatch({action: 'require_registration'}); } else { console.error("Toggle moderator error:" + err); Modal.createTrackedDialog('Failed to toggle moderator status', '', ErrorDialog, { diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index dacd433e3e..642c939f83 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -131,7 +131,7 @@ export default class MessageComposer extends React.Component { onUploadClick(ev) { if (MatrixClientPeg.get().isGuest()) { - dis.dispatch({action: 'view_set_mxid'}); + dis.dispatch({action: 'require_registration'}); return; } diff --git a/src/createRoom.js b/src/createRoom.js index a767d09288..8b4220fc85 100644 --- a/src/createRoom.js +++ b/src/createRoom.js @@ -42,7 +42,7 @@ function createRoom(opts) { const client = MatrixClientPeg.get(); if (client.isGuest()) { - dis.dispatch({action: 'view_set_mxid'}); + dis.dispatch({action: 'require_registration'}); return Promise.resolve(null); } From eced58701dda46a1f6e24c1cfd02151645f8e543 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 5 Sep 2018 20:34:03 +0100 Subject: [PATCH 3/4] Lint --- src/Registration.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Registration.js b/src/Registration.js index b3da7acb1e..a24a2d87f6 100644 --- a/src/Registration.js +++ b/src/Registration.js @@ -29,6 +29,10 @@ import { _t } from './languageHandler'; /** * Starts either the ILAG or full registration flow, depending * on what the HS supports + * + * @param {object} options + * @param {bool} options.go_home_on_cancel If true, goes to + * the hame page if the user cancels the action */ export async function startAnyRegistrationFlow(options) { if (options === undefined) options = {}; @@ -70,7 +74,7 @@ export async function startAnyRegistrationFlow(options) { } else if (options.go_home_on_cancel) { dis.dispatch({action: 'view_home_page'}); } - } + }, }); } } From 32da44615eff23d8d48cd79887807a735741df47 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 6 Sep 2018 15:50:41 +0100 Subject: [PATCH 4/4] Use some/every instead of doing it manually --- src/Registration.js | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/Registration.js b/src/Registration.js index a24a2d87f6..070178fecb 100644 --- a/src/Registration.js +++ b/src/Registration.js @@ -43,20 +43,12 @@ export async function startAnyRegistrationFlow(options) { // ones like email & msisdn which require the user to supply // the relevant details in advance. We err on the side of // caution though. - let hasIlagFlow = false; - for (const flow of flows) { - let flowSuitable = true; - for (const stage of flow.stages) { - if (!['m.login.dummy', 'm.login.recaptcha'].includes(stage)) { - flowSuitable = false; - break; - } - } - if (flowSuitable) { - hasIlagFlow = true; - break; - } - } + const hasIlagFlow = flows.some((flow) => { + return flow.stages.every((stage) => { + return ['m.login.dummy', 'm.login.recaptcha'].includes(stage); + }); + }); + if (hasIlagFlow) { dis.dispatch({ action: 'view_set_mxid',