diff --git a/src/Registration.js b/src/Registration.js new file mode 100644 index 0000000000..070178fecb --- /dev/null +++ b/src/Registration.js @@ -0,0 +1,92 @@ +/* +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 + * + * @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 = {}; + 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. + 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', + 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/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 7a13067976..333e200e44 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 @@ -1423,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 ad4a1dfafe..c5e389aa06 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); } 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.",