Merge pull request #849 from matrix-org/luke/new-guest-access-set-mxid

Initial implementation: SetDisplayName -> SetMxIdDialog
This commit is contained in:
Luke Barnard 2017-05-02 14:12:07 +01:00 committed by GitHub
commit 863430cf5d
8 changed files with 191 additions and 121 deletions

View file

@ -303,6 +303,12 @@ export function setLoggedIn(credentials) {
localStorage.setItem("mx_device_id", credentials.deviceId); localStorage.setItem("mx_device_id", credentials.deviceId);
} }
// The user registered as a PWLU (PassWord-Less User), the generated password
// is cached here such that the user can change it at a later time.
if (credentials.password) {
localStorage.setItem("mx_pass", credentials.password);
}
console.log("Session persisted for %s", credentials.userId); console.log("Session persisted for %s", credentials.userId);
} catch (e) { } catch (e) {
console.warn("Error using local storage: can't persist session!", e); console.warn("Error using local storage: can't persist session!", e);

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -95,8 +96,8 @@ import views$dialogs$QuestionDialog from './components/views/dialogs/QuestionDia
views$dialogs$QuestionDialog && (module.exports.components['views.dialogs.QuestionDialog'] = views$dialogs$QuestionDialog); views$dialogs$QuestionDialog && (module.exports.components['views.dialogs.QuestionDialog'] = views$dialogs$QuestionDialog);
import views$dialogs$SessionRestoreErrorDialog from './components/views/dialogs/SessionRestoreErrorDialog'; import views$dialogs$SessionRestoreErrorDialog from './components/views/dialogs/SessionRestoreErrorDialog';
views$dialogs$SessionRestoreErrorDialog && (module.exports.components['views.dialogs.SessionRestoreErrorDialog'] = views$dialogs$SessionRestoreErrorDialog); views$dialogs$SessionRestoreErrorDialog && (module.exports.components['views.dialogs.SessionRestoreErrorDialog'] = views$dialogs$SessionRestoreErrorDialog);
import views$dialogs$SetDisplayNameDialog from './components/views/dialogs/SetDisplayNameDialog'; import views$dialogs$SetMxIdDialog from './components/views/dialogs/SetMxIdDialog';
views$dialogs$SetDisplayNameDialog && (module.exports.components['views.dialogs.SetDisplayNameDialog'] = views$dialogs$SetDisplayNameDialog); views$dialogs$SetMxIdDialog && (module.exports.components['views.dialogs.SetMxIdDialog'] = views$dialogs$SetMxIdDialog);
import views$dialogs$TextInputDialog from './components/views/dialogs/TextInputDialog'; import views$dialogs$TextInputDialog from './components/views/dialogs/TextInputDialog';
views$dialogs$TextInputDialog && (module.exports.components['views.dialogs.TextInputDialog'] = views$dialogs$TextInputDialog); views$dialogs$TextInputDialog && (module.exports.components['views.dialogs.TextInputDialog'] = views$dialogs$TextInputDialog);
import views$dialogs$UnknownDeviceDialog from './components/views/dialogs/UnknownDeviceDialog'; import views$dialogs$UnknownDeviceDialog from './components/views/dialogs/UnknownDeviceDialog';

View file

@ -43,6 +43,10 @@ export default React.createClass({
onRoomCreated: React.PropTypes.func, onRoomCreated: React.PropTypes.func,
onUserSettingsClose: React.PropTypes.func, onUserSettingsClose: React.PropTypes.func,
// Called with the credentials of a registered user (if they were a ROU that
// transitioned to PWLU)
onRegistered: React.PropTypes.func,
teamToken: React.PropTypes.string, teamToken: React.PropTypes.string,
// and lots and lots of other stuff. // and lots and lots of other stuff.
@ -184,6 +188,7 @@ export default React.createClass({
roomAddress={this.props.currentRoomAlias || this.props.currentRoomId} roomAddress={this.props.currentRoomAlias || this.props.currentRoomId}
autoJoin={this.props.autoJoin} autoJoin={this.props.autoJoin}
onRoomIdResolved={this.props.onRoomIdResolved} onRoomIdResolved={this.props.onRoomIdResolved}
onRegistered={this.props.onRegistered}
eventId={this.props.initialEventId} eventId={this.props.initialEventId}
thirdPartyInvite={this.props.thirdPartyInvite} thirdPartyInvite={this.props.thirdPartyInvite}
oobData={this.props.roomOobData} oobData={this.props.roomOobData}

View file

@ -1166,6 +1166,7 @@ module.exports = React.createClass({
onRoomIdResolved={this.onRoomIdResolved} onRoomIdResolved={this.onRoomIdResolved}
onRoomCreated={this.onRoomCreated} onRoomCreated={this.onRoomCreated}
onUserSettingsClose={this.onUserSettingsClose} onUserSettingsClose={this.onUserSettingsClose}
onRegistered={this.onRegistered}
teamToken={this._teamToken} teamToken={this._teamToken}
{...this.props} {...this.props}
{...this.state} {...this.state}

View file

@ -69,6 +69,10 @@ module.exports = React.createClass({
// once it has been resolved. // once it has been resolved.
onRoomIdResolved: React.PropTypes.func, onRoomIdResolved: React.PropTypes.func,
// Called with the credentials of a registered user (if they were a ROU that
// transitioned to PWLU)
onRegistered: React.PropTypes.func,
// An object representing a third party invite to join this room // An object representing a third party invite to join this room
// Fields: // Fields:
// * inviteSignUrl (string) The URL used to join this room from an email invite // * inviteSignUrl (string) The URL used to join this room from an email invite
@ -764,38 +768,29 @@ module.exports = React.createClass({
var self = this; var self = this;
var cli = MatrixClientPeg.get(); var cli = MatrixClientPeg.get();
var display_name_promise = q(); var mxIdPromise = q();
// if this is the first room we're joining, check the user has a display name
// and if they don't, prompt them to set one. // If the user is a ROU, allow them to transition to a PWLU
// NB. This unfortunately does not re-use the ChangeDisplayName component because if (cli && cli.isGuest()) {
// it doesn't behave quite as desired here (we want an input field here rather than const SetMxIdDialog = sdk.getComponent('views.dialogs.SetMxIdDialog');
// content-editable, and we want a default). const defered = q.defer();
if (cli.getRooms().filter((r) => { mxIdPromise = defered.promise;
return r.hasMembershipState(cli.credentials.userId, "join"); Modal.createDialog(SetMxIdDialog, {
})) { onFinished: (submitted, credentials) => {
display_name_promise = cli.getProfileInfo(cli.credentials.userId).then((result) => { if (!submitted) {
if (!result.displayname) { defered.reject();
var SetDisplayNameDialog = sdk.getComponent('views.dialogs.SetDisplayNameDialog'); return;
var dialog_defer = q.defer(); }
Modal.createDialog(SetDisplayNameDialog, { this.props.onRegistered(credentials);
currentDisplayName: result.displayname, defered.resolve();
onFinished: (submitted, newDisplayName) => {
if (submitted) {
cli.setDisplayName(newDisplayName).done(() => {
dialog_defer.resolve();
});
}
else {
dialog_defer.reject();
}
}
});
return dialog_defer.promise;
} }
}); });
} }
display_name_promise.then(() => { mxIdPromise.then(() => {
this.setState({
joining: true
});
// if this is an invite and has the 'direct' hint set, mark it as a DM room now. // if this is an invite and has the 'direct' hint set, mark it as a DM room now.
if (this.state.room) { if (this.state.room) {
const me = this.state.room.getMember(MatrixClientPeg.get().credentials.userId); const me = this.state.room.getMember(MatrixClientPeg.get().credentials.userId);
@ -870,10 +865,6 @@ module.exports = React.createClass({
}); });
} }
}).done(); }).done();
this.setState({
joining: true
});
}, },
onMessageListScroll: function(ev) { onMessageListScroll: function(ev) {

View file

@ -1,87 +0,0 @@
/*
Copyright 2016 OpenMarket 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.
*/
import React from 'react';
import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
/**
* Prompt the user to set a display name.
*
* On success, `onFinished(true, newDisplayName)` is called.
*/
export default React.createClass({
displayName: 'SetDisplayNameDialog',
propTypes: {
onFinished: React.PropTypes.func.isRequired,
currentDisplayName: React.PropTypes.string,
},
getInitialState: function() {
if (this.props.currentDisplayName) {
return { value: this.props.currentDisplayName };
}
if (MatrixClientPeg.get().isGuest()) {
return { value : "Guest " + MatrixClientPeg.get().getUserIdLocalpart() };
}
else {
return { value : MatrixClientPeg.get().getUserIdLocalpart() };
}
},
componentDidMount: function() {
this.refs.input_value.select();
},
onValueChange: function(ev) {
this.setState({
value: ev.target.value
});
},
onFormSubmit: function(ev) {
ev.preventDefault();
this.props.onFinished(true, this.state.value);
return false;
},
render: function() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return (
<BaseDialog className="mx_SetDisplayNameDialog"
onFinished={this.props.onFinished}
title="Set a Display Name"
>
<div className="mx_Dialog_content">
Your display name is how you'll appear to others when you speak in rooms.<br/>
What would you like it to be?
</div>
<form onSubmit={this.onFormSubmit}>
<div className="mx_Dialog_content">
<input type="text" ref="input_value" value={this.state.value}
autoFocus={true} onChange={this.onValueChange} size="30"
className="mx_SetDisplayNameDialog_input"
/>
</div>
<div className="mx_Dialog_buttons">
<input className="mx_Dialog_primary" type="submit" value="Set" />
</div>
</form>
</BaseDialog>
);
},
});

View file

@ -0,0 +1,152 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations 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.
*/
import q from 'q';
import React from 'react';
import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
/**
* Prompt the user to set a display name.
*
* On success, `onFinished(true, newDisplayName)` is called.
*/
export default React.createClass({
displayName: 'SetMxIdDialog',
propTypes: {
onFinished: React.PropTypes.func.isRequired,
},
getInitialState: function() {
return {
username : '',
doingUIAuth: false,
}
},
componentDidMount: function() {
this.refs.input_value.select();
this._matrixClient = MatrixClientPeg.get();
},
onValueChange: function(ev) {
this.setState({
username: ev.target.value
});
},
onSubmit: function(ev) {
this.setState({
doingUIAuth: true,
});
},
_generatePassword: function() {
return Math.random().toString(36).slice(2);
},
_makeRegisterRequest: function(auth) {
// Not upgrading - changing mxids
const guestAccessToken = null;
this._generatedPassword = this._generatePassword();
return this._matrixClient.register(
this.state.username,
this._generatedPassword,
undefined, // session id: included in the auth dict already
auth,
{},
guestAccessToken,
);
},
_onUIAuthFinished: function(success, response) {
this.setState({
doingUIAuth: false,
});
console.info('Auth Finsihed', arguments);
if (!success) {
this.setState({ errorText : response.message });
return;
}
// XXX Implement RTS /register here
const teamToken = null;
this.props.onFinished(true, {
userId: response.user_id,
deviceId: response.device_id,
homeserverUrl: this._matrixClient.getHomeserverUrl(),
identityServerUrl: this._matrixClient.getIdentityServerUrl(),
accessToken: response.access_token,
password: this._generatedPassword,
teamToken: teamToken,
});
},
render: function() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth');
const Spinner = sdk.getComponent('elements.Spinner');
let auth;
if (this.state.doingUIAuth) {
auth = <InteractiveAuth
matrixClient={this._matrixClient}
makeRequest={this._makeRegisterRequest}
onAuthFinished={this._onUIAuthFinished}
inputs={{}}
poll={true}
/>;
}
return (
<BaseDialog className="mx_SetMxIdDialog"
onFinished={this.props.onFinished}
title="Choose a Username"
>
<div className="mx_Dialog_content">
<p>
Beyond this point you're going to need to pick a username - your
unique identifire in Riot.
</p>
<p>
<small>
You can't change your username, but you can always choose how you
appear to other people in Riot by changing your display name.
</small>
</p>
<input type="text" ref="input_value" value={this.state.username}
autoFocus={true} onChange={this.onValueChange} size="30"
className="mx_SetMxIdDialog_input"
/>
{ auth }
<div>
{ this.state.errorText }
</div>
</div>
<div className="mx_Dialog_buttons">
<input className="mx_Dialog_primary"
type="submit"
value="Continue"
onClick={this.onSubmit}
/>
</div>
</BaseDialog>
);
},
});

View file

@ -133,6 +133,7 @@ export function createTestClient() {
sendHtmlMessage: () => q({}), sendHtmlMessage: () => q({}),
getSyncState: () => "SYNCING", getSyncState: () => "SYNCING",
generateClientSecret: () => "t35tcl1Ent5ECr3T", generateClientSecret: () => "t35tcl1Ent5ECr3T",
isGuest: () => false,
}; };
} }