Merge branch 'new-guest-access' into luke/new-guest-access-change-pwd

pull/21833/head
Luke Barnard 2017-05-16 13:59:54 +01:00 committed by GitHub
commit 66985e6d08
13 changed files with 160 additions and 159 deletions

View File

@ -185,6 +185,14 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
// returns a promise which resolves to true if a session is found in
// localstorage
//
// N.B. Lifecycle.js should not maintain any further localStorage state, we
// are moving towards using SessionStore to keep track of state related
// to the current session (which is typically backed by localStorage).
//
// The plan is to gradually move the localStorage access done here into
// SessionStore to avoid bugs where the view becomes out-of-sync with
// localStorage (e.g. teamToken, isGuest etc.)
function _restoreFromLocalStorage() {
if (!localStorage) {
return q(false);
@ -289,7 +297,6 @@ export function setLoggedIn(credentials) {
// Resolves by default
let teamPromise = Promise.resolve(null);
let isPasswordStored = false;
// persist the session
if (localStorage) {
@ -312,8 +319,11 @@ export function setLoggedIn(credentials) {
// 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);
isPasswordStored = true;
// Update SessionStore
dis.dispatch({
action: 'cached_password',
cachedPassword: credentials.password,
});
}
console.log("Session persisted for %s", credentials.userId);
@ -339,10 +349,10 @@ export function setLoggedIn(credentials) {
MatrixClientPeg.replaceUsingCreds(credentials);
teamPromise.then((teamToken) => {
dis.dispatch({action: 'on_logged_in', teamToken: teamToken, isPasswordStored});
dis.dispatch({action: 'on_logged_in', teamToken: teamToken});
}, (err) => {
console.warn("Failed to get team token on login", err);
dis.dispatch({action: 'on_logged_in', teamToken: null, isPasswordStored});
dis.dispatch({action: 'on_logged_in', teamToken: null});
});
startMatrixClient();

View File

@ -23,6 +23,7 @@ import Notifier from '../../Notifier';
import PageTypes from '../../PageTypes';
import sdk from '../../index';
import dis from '../../dispatcher';
import sessionStore from '../../stores/SessionStore';
/**
* This is what our MatrixChat shows when we are logged in. The precise view is
@ -49,10 +50,6 @@ export default React.createClass({
teamToken: React.PropTypes.string,
// Has the user generated a password that is stored in local storage?
// (are they a PWLU?)
userHasGeneratedPassword: React.PropTypes.boolean,
// and lots and lots of other stuff.
},
@ -80,10 +77,19 @@ export default React.createClass({
this._scrollStateMap = {};
document.addEventListener('keydown', this._onKeyDown);
this._sessionStore = sessionStore;
this._sessionStoreToken = this._sessionStore.addListener(
this._setStateFromSessionStore,
);
this._setStateFromSessionStore();
},
componentWillUnmount: function() {
document.removeEventListener('keydown', this._onKeyDown);
if (this._sessionStoreToken) {
this._sessionStoreToken.remove();
}
},
getScrollStateForRoom: function(roomId) {
@ -97,6 +103,12 @@ export default React.createClass({
return this.refs.roomView.canResetTimeline();
},
_setStateFromSessionStore() {
this.setState({
userHasGeneratedPassword: Boolean(this._sessionStore.getCachedPassword()),
});
},
_onKeyDown: function(ev) {
/*
// Remove this for now as ctrl+alt = alt-gr so this breaks keyboards which rely on alt-gr for numbers
@ -257,7 +269,7 @@ export default React.createClass({
/>;
} else if (this.props.matrixClient.isGuest()) {
topBar = <GuestWarningBar />;
} else if (this.props.userHasGeneratedPassword) {
} else if (this.state.userHasGeneratedPassword) {
topBar = <PasswordNagBar />;
} else if (Notifier.supportsDesktopNotifications() && !Notifier.isEnabled() && !Notifier.isToolbarHidden()) {
topBar = <MatrixToolbar />;

View File

@ -138,9 +138,6 @@ module.exports = React.createClass({
register_hs_url: null,
register_is_url: null,
register_id_sid: null,
// Initially, use localStorage as source of truth
userHasGeneratedPassword: localStorage && localStorage.getItem('mx_pass'),
};
return s;
},
@ -578,7 +575,7 @@ module.exports = React.createClass({
this.setState({loggingIn: true});
break;
case 'on_logged_in':
this._onLoggedIn(payload.teamToken, payload.isPasswordStored);
this._onLoggedIn(payload.teamToken);
break;
case 'on_logged_out':
this._onLoggedOut();
@ -785,15 +782,11 @@ module.exports = React.createClass({
/**
* Called when a new logged in session has started
*/
_onLoggedIn: function(teamToken, isPasswordStored) {
_onLoggedIn: function(teamToken) {
this.setState({
guestCreds: null,
loggedIn: true,
loggingIn: false,
// isPasswordStored only true when ROU sets a username and becomes PWLU.
// (the password was randomly generated and stored in localStorage).
userHasGeneratedPassword:
this.state.userHasGeneratedPassword || isPasswordStored,
});
if (teamToken) {
@ -801,8 +794,12 @@ module.exports = React.createClass({
this._teamToken = teamToken;
dis.dispatch({action: 'view_home_page'});
} else if (this._is_registered) {
if (this.props.config.welcomeUserId) {
createRoom({dmUserId: this.props.config.welcomeUserId});
return;
}
// The user has just logged in after registering
dis.dispatch({action: 'view_user_settings'});
dis.dispatch({action: 'view_room_directory'});
} else {
this._showScreenAfterLogin();
}
@ -1202,7 +1199,6 @@ module.exports = React.createClass({
onUserSettingsClose={this.onUserSettingsClose}
onRegistered={this.onRegistered}
teamToken={this._teamToken}
userHasGeneratedPassword={this.state.userHasGeneratedPassword}
{...this.props}
{...this.state}
/>

View File

@ -869,11 +869,7 @@ module.exports = React.createClass({
MatrixClientPeg.get().isGuest()
)
) {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Failed to join the room",
description: "This room is private or inaccessible to guests. You may be able to join if you register."
});
dis.dispatch({action: 'view_set_mxid'});
} else {
var msg = error.message ? error.message : JSON.stringify(error);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@ -933,11 +929,7 @@ module.exports = React.createClass({
uploadFile: function(file) {
if (MatrixClientPeg.get().isGuest()) {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "Guest users can't upload files. Please register to upload."
});
dis.dispatch({action: 'view_set_mxid'});
return;
}

View File

@ -245,11 +245,7 @@ module.exports = React.createClass({
onAvatarPickerClick: function(ev) {
if (MatrixClientPeg.get().isGuest()) {
const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "Guests can't set avatars. Please register.",
});
dis.dispatch({action: 'view_set_mxid'});
return;
}
@ -331,6 +327,7 @@ module.exports = React.createClass({
receive push notifications on other devices until you
log back in to them.`,
});
dis.dispatch({action: 'password_changed'});
},
onUpgradeClicked: function() {
@ -700,11 +697,7 @@ module.exports = React.createClass({
onChange={(e) => {
if (MatrixClientPeg.get().isGuest()) {
e.target.checked = false;
const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "Guests can't use labs features. Please register.",
});
dis.dispatch({action: 'view_set_mxid'});
return;
}

View File

@ -284,11 +284,7 @@ module.exports = React.createClass({
_startChat: function(addrs) {
if (MatrixClientPeg.get().isGuest()) {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "Guest users can't invite users. Please register."
});
dis.dispatch({action: 'view_set_mxid'});
return;
}

View File

@ -1,78 +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.
*/
/*
* Usage:
* Modal.createDialog(NeedToRegisterDialog, {
* title: "some text", (default: "Registration required")
* description: "some more text",
* onFinished: someFunction,
* });
*/
import React from 'react';
import dis from '../../../dispatcher';
import sdk from '../../../index';
module.exports = React.createClass({
displayName: 'NeedToRegisterDialog',
propTypes: {
title: React.PropTypes.string,
description: React.PropTypes.oneOfType([
React.PropTypes.element,
React.PropTypes.string,
]),
onFinished: React.PropTypes.func.isRequired,
},
getDefaultProps: function() {
return {
title: "Registration required",
description: "A registered account is required for this action",
};
},
onRegisterClicked: function() {
dis.dispatch({
action: "start_upgrade_registration",
});
if (this.props.onFinished) {
this.props.onFinished();
}
},
render: function() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return (
<BaseDialog className="mx_NeedToRegisterDialog"
onFinished={this.props.onFinished}
title={this.props.title}
>
<div className="mx_Dialog_content">
{this.props.description}
</div>
<div className="mx_Dialog_buttons">
<button className="mx_Dialog_primary" onClick={this.props.onFinished} autoFocus={true}>
Cancel
</button>
<button onClick={this.onRegisterClicked}>
Register
</button>
</div>
</BaseDialog>
);
},
});

View File

@ -21,6 +21,8 @@ var Tinter = require('../../../Tinter');
var MatrixClientPeg = require("../../../MatrixClientPeg");
var Modal = require("../../../Modal");
import dis from '../../../dispatcher';
var ROOM_COLORS = [
// magic room default values courtesy of Ribot
["#76cfa6", "#eaf5f0"],
@ -86,11 +88,7 @@ module.exports = React.createClass({
}
).catch(function(err) {
if (err.errcode == 'M_GUEST_ACCESS_FORBIDDEN') {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "Saving room color settings is only available to registered users"
});
dis.dispatch({action: 'view_set_mxid'});
}
});
}

View File

@ -374,11 +374,7 @@ module.exports = WithMatrixClient(React.createClass({
console.log("Mod toggle success");
}, function(err) {
if (err.errcode == 'M_GUEST_ACCESS_FORBIDDEN') {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "This action cannot be performed by a guest user. Please register to be able to do this."
});
dis.dispatch({action: 'view_set_mxid'});
} else {
console.error("Toggle moderator error:" + err);
Modal.createDialog(ErrorDialog, {

View File

@ -90,11 +90,7 @@ export default class MessageComposer extends React.Component {
onUploadClick(ev) {
if (MatrixClientPeg.get().isGuest()) {
let NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "Guest users can't upload files. Please register to upload.",
});
dis.dispatch({action: 'view_set_mxid'});
return;
}

View File

@ -22,6 +22,8 @@ var Modal = require("../../../Modal");
var sdk = require("../../../index");
import AccessibleButton from '../elements/AccessibleButton';
import sessionStore from '../../../stores/SessionStore';
module.exports = React.createClass({
displayName: 'ChangePassword',
propTypes: {
@ -62,11 +64,33 @@ module.exports = React.createClass({
getInitialState: function() {
return {
phase: this.Phases.Edit
phase: this.Phases.Edit,
cachedPassword: null,
};
},
changePassword: function(oldPassword, newPassword) {
componentWillMount: function() {
this._sessionStore = sessionStore;
this._sessionStoreToken = this._sessionStore.addListener(
this._setStateFromSessionStore,
);
this._setStateFromSessionStore();
},
componentWillUnmount: function() {
if (this._sessionStoreToken) {
this._sessionStoreToken.remove();
}
},
_setStateFromSessionStore: function() {
this.setState({
cachedPassword: this._sessionStore.getCachedPassword(),
});
},
changePassword: function(old_password, new_password) {
const cli = MatrixClientPeg.get();
if (!this.props.confirm) {
@ -148,23 +172,28 @@ module.exports = React.createClass({
},
render: function() {
var rowClassName = this.props.rowClassName;
var rowLabelClassName = this.props.rowLabelClassName;
var rowInputClassName = this.props.rowInputClassName;
var buttonClassName = this.props.buttonClassName;
const rowClassName = this.props.rowClassName;
const rowLabelClassName = this.props.rowLabelClassName;
const rowInputClassName = this.props.rowInputClassName;
const buttonClassName = this.props.buttonClassName;
let currentPassword = null;
if (!this.state.cachedPassword) {
currentPassword = <div className={rowClassName}>
<div className={rowLabelClassName}>
<label htmlFor="passwordold">Current password</label>
</div>
<div className={rowInputClassName}>
<input id="passwordold" type="password" ref="old_input" />
</div>
</div>;
}
switch (this.state.phase) {
case this.Phases.Edit:
return (
<div className={this.props.className}>
<div className={rowClassName}>
<div className={rowLabelClassName}>
<label htmlFor="passwordold">Current password</label>
</div>
<div className={rowInputClassName}>
<input id="passwordold" type="password" ref="old_input" />
</div>
</div>
{ currentPassword }
<div className={rowClassName}>
<div className={rowLabelClassName}>
<label htmlFor="password1">New password</label>

View File

@ -41,12 +41,7 @@ function createRoom(opts) {
const client = MatrixClientPeg.get();
if (client.isGuest()) {
setTimeout(()=>{
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "Guest users can't create new rooms. Please register to create room and start a chat."
});
}, 0);
dis.dispatch({action: 'view_set_mxid'});
return q(null);
}

View File

@ -0,0 +1,66 @@
import dis from '../dispatcher';
import {Store} from 'flux/utils';
/**
* A class for storing application state to do with the session. This is a simple flux
* store that listens for actions and updates its state accordingly, informing any
* listeners (views) of state changes.
*
* Usage:
* ```
* sessionStore.addListener(() => {
* this.setState({ cachedPassword: sessionStore.getCachedPassword() })
* })
* ```
*/
class SessionStore extends Store {
constructor() {
super(dis);
// Initialise state
this._state = {
cachedPassword: localStorage.getItem('mx_pass'),
};
}
_update() {
// Persist state to localStorage
if (this._state.cachedPassword) {
localStorage.setItem('mx_pass', this._state.cachedPassword);
} else {
localStorage.removeItem('mx_pass', this._state.cachedPassword);
}
this.__emitChange();
}
_setState(newState) {
this._state = Object.assign(this._state, newState);
this._update();
}
__onDispatch(payload) {
switch (payload.action) {
case 'cached_password':
this._setState({
cachedPassword: payload.cachedPassword,
});
break;
case 'password_changed':
this._setState({
cachedPassword: null,
});
break;
}
}
getCachedPassword() {
return this._state.cachedPassword;
}
}
let singletonSessionStore = null;
if (!singletonSessionStore) {
singletonSessionStore = new SessionStore();
}
module.exports = singletonSessionStore;