Webatrice: fix login bugs (#4557)
* fix login after failed connection attempts, limit connection attempt time * fix register hashed password and salt * add feature detection and Unsupported Browser screen * nit Co-authored-by: Jeremy Letto <jeremy.letto@datasite.com>
This commit is contained in:
parent
81d031ca0f
commit
bb16ae09ef
17 changed files with 104 additions and 18 deletions
|
@ -6,6 +6,7 @@ import CssBaseline from '@material-ui/core/CssBaseline';
|
|||
import { store } from 'store';
|
||||
import { Header } from 'components';
|
||||
import Routes from './AppShellRoutes';
|
||||
import FeatureDetection from './FeatureDetection';
|
||||
|
||||
import './AppShell.css';
|
||||
|
||||
|
@ -26,6 +27,8 @@ class AppShell extends Component {
|
|||
<div className="AppShell" onContextMenu={this.handleContextMenu}>
|
||||
<Router>
|
||||
<Header />
|
||||
|
||||
<FeatureDetection />
|
||||
<Routes />
|
||||
</Router>
|
||||
</div>
|
||||
|
|
|
@ -10,7 +10,8 @@ import {
|
|||
Room,
|
||||
Server,
|
||||
Login,
|
||||
Logs
|
||||
Logs,
|
||||
Unsupported
|
||||
} from 'containers';
|
||||
|
||||
const Routes = () => (
|
||||
|
@ -24,6 +25,7 @@ const Routes = () => (
|
|||
{<Route path={RouteEnum.ROOM} render={() => <Room />} />}
|
||||
<Route path={RouteEnum.SERVER} render={() => <Server />} />
|
||||
<Route path={RouteEnum.LOGIN} render={() => <Login />} />
|
||||
<Route path={RouteEnum.UNSUPPORTED} render={() => <Unsupported />} />
|
||||
|
||||
<Redirect from="*" to={RouteEnum.LOGIN} />
|
||||
</Switch>
|
||||
|
|
26
webclient/src/containers/App/FeatureDetection.tsx
Normal file
26
webclient/src/containers/App/FeatureDetection.tsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import { dexieService } from 'services';
|
||||
import { RouteEnum } from 'types';
|
||||
|
||||
const FeatureDetection = () => {
|
||||
const [unsupported, setUnsupported] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const features: Promise<any>[] = [
|
||||
detectIndexedDB(),
|
||||
];
|
||||
|
||||
Promise.all(features).catch((e) => setUnsupported(true));
|
||||
}, []);
|
||||
|
||||
return unsupported
|
||||
? <Redirect from="*" to={RouteEnum.UNSUPPORTED} />
|
||||
: <></>;
|
||||
|
||||
function detectIndexedDB() {
|
||||
return dexieService.testConnection();
|
||||
}
|
||||
};
|
||||
|
||||
export default FeatureDetection;
|
|
@ -88,7 +88,7 @@ const Login = ({ state, description }: LoginProps) => {
|
|||
|
||||
useReduxEffect(() => {
|
||||
resetSubmitButton();
|
||||
}, [ServerTypes.LOGIN_FAILED], []);
|
||||
}, [ServerTypes.CONNECTION_FAILED, ServerTypes.LOGIN_FAILED], []);
|
||||
|
||||
useReduxEffect(({ options: { hashedPassword } }) => {
|
||||
if (hostIdToRemember) {
|
||||
|
|
18
webclient/src/containers/Unsupported/Unsupported.css
Normal file
18
webclient/src/containers/Unsupported/Unsupported.css
Normal file
|
@ -0,0 +1,18 @@
|
|||
.Unsupported {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.Unsupported-paper {
|
||||
width: 600px;
|
||||
max-width: 100%;
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.Unsupported-paper__header {
|
||||
margin-bottom: 40px;
|
||||
}
|
27
webclient/src/containers/Unsupported/Unsupported.tsx
Normal file
27
webclient/src/containers/Unsupported/Unsupported.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
import './Unsupported.css';
|
||||
|
||||
const Unsupported = () => {
|
||||
return (
|
||||
<div className='Unsupported'>
|
||||
<Paper className='Unsupported-paper'>
|
||||
<div className='Unsupported-paper__header'>
|
||||
<Typography variant="h1">Unsupported Browser</Typography>
|
||||
<Typography variant="subtitle1">Please update your browser and/or check your permissions.</Typography>
|
||||
</div>
|
||||
|
||||
<Typography variant="subtitle2">Note: Private browsing causes some browsers to disable certain permissions or features.</Typography>
|
||||
</Paper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
|
||||
});
|
||||
|
||||
export default withRouter(connect(mapStateToProps)(Unsupported));
|
|
@ -7,3 +7,4 @@ export { default as Player } from './Player/Player';
|
|||
export { default as Server } from './Server/Server';
|
||||
export { default as Logs } from './Logs/Logs';
|
||||
export { default as Login } from './Login/Login';
|
||||
export { default as Unsupported } from './Unsupported/Unsupported';
|
||||
|
|
|
@ -28,6 +28,10 @@ class DexieService {
|
|||
get hosts() {
|
||||
return this.db.table(Stores.HOSTS);
|
||||
}
|
||||
|
||||
testConnection() {
|
||||
return this.db.open();
|
||||
}
|
||||
}
|
||||
|
||||
export const dexieService = new DexieService();
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
export * from './DexieDTOs';
|
||||
export * from './DexieService';
|
||||
|
|
|
@ -18,6 +18,9 @@ export const Actions = {
|
|||
type: Types.CONNECTION_CLOSED,
|
||||
reason
|
||||
}),
|
||||
connectionFailed: () => ({
|
||||
type: Types.CONNECTION_FAILED,
|
||||
}),
|
||||
serverMessage: message => ({
|
||||
type: Types.SERVER_MESSAGE,
|
||||
message
|
||||
|
|
|
@ -15,6 +15,9 @@ export const Dispatch = {
|
|||
connectionClosed: reason => {
|
||||
store.dispatch(Actions.connectionClosed(reason));
|
||||
},
|
||||
connectionFailed: () => {
|
||||
store.dispatch(Actions.connectionFailed());
|
||||
},
|
||||
updateBuddyList: buddyList => {
|
||||
store.dispatch(Actions.updateBuddyList(buddyList));
|
||||
},
|
||||
|
|
|
@ -3,6 +3,7 @@ export const Types = {
|
|||
LOGIN_SUCCESSFUL: '[Server] Login Successful',
|
||||
LOGIN_FAILED: '[Server] Login Failed',
|
||||
CONNECTION_CLOSED: '[Server] Connection Closed',
|
||||
CONNECTION_FAILED: '[Server] Connection Failed',
|
||||
SERVER_MESSAGE: '[Server] Server Message',
|
||||
UPDATE_BUDDY_LIST: '[Server] Update Buddy List',
|
||||
ADD_TO_BUDDY_LIST: '[Server] Add to Buddy List',
|
||||
|
|
|
@ -10,4 +10,5 @@ export enum RouteEnum {
|
|||
ACCOUNT = '/account',
|
||||
ADMINISTRATION = '/administration',
|
||||
REPLAYS = '/replays',
|
||||
UNSUPPORTED = '/unsupported',
|
||||
}
|
||||
|
|
|
@ -141,11 +141,6 @@ export class SessionCommands {
|
|||
const passwordSalt = raw['.Response_PasswordSalt.ext']?.passwordSalt;
|
||||
|
||||
switch (webClient.options.reason) {
|
||||
case WebSocketConnectReason.REGISTER: {
|
||||
SessionCommands.register(passwordSalt);
|
||||
break;
|
||||
}
|
||||
|
||||
case WebSocketConnectReason.ACTIVATE_ACCOUNT: {
|
||||
SessionCommands.activateAccount(passwordSalt);
|
||||
break;
|
||||
|
@ -174,11 +169,6 @@ export class SessionCommands {
|
|||
}
|
||||
|
||||
switch (webClient.options.reason) {
|
||||
case WebSocketConnectReason.REGISTER: {
|
||||
SessionPersistence.registrationFailed('Failed to retrieve password salt');
|
||||
break;
|
||||
}
|
||||
|
||||
case WebSocketConnectReason.ACTIVATE_ACCOUNT: {
|
||||
SessionPersistence.accountActivationFailed();
|
||||
break;
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Room, StatusEnum, User, WebSocketConnectReason } from 'types';
|
|||
import { SessionCommands } from '../commands';
|
||||
import { RoomPersistence, SessionPersistence } from '../persistence';
|
||||
import { ProtobufEvents } from '../services/ProtobufService';
|
||||
import { passwordSaltSupported } from '../utils';
|
||||
import { generateSalt, passwordSaltSupported } from '../utils';
|
||||
import webClient from '../WebClient';
|
||||
|
||||
export const SessionEvents: ProtobufEvents = {
|
||||
|
@ -130,11 +130,8 @@ function serverIdentification(info: ServerIdentificationData) {
|
|||
}
|
||||
break;
|
||||
case WebSocketConnectReason.REGISTER:
|
||||
if (passwordSaltSupported(serverOptions, webClient)) {
|
||||
SessionCommands.requestPasswordSalt();
|
||||
} else {
|
||||
SessionCommands.register();
|
||||
}
|
||||
const passwordSalt = passwordSaltSupported(serverOptions, webClient) ? generateSalt() : null;
|
||||
SessionCommands.register(passwordSalt);
|
||||
break;
|
||||
case WebSocketConnectReason.ACTIVATE_ACCOUNT:
|
||||
if (passwordSaltSupported(serverOptions, webClient)) {
|
||||
|
|
|
@ -21,6 +21,10 @@ export class SessionPersistence {
|
|||
ServerDispatch.connectionClosed(reason);
|
||||
}
|
||||
|
||||
static connectionFailed() {
|
||||
ServerDispatch.connectionFailed();
|
||||
}
|
||||
|
||||
static updateBuddyList(buddyList) {
|
||||
ServerDispatch.updateBuddyList(buddyList);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { ServerStatus, StatusEnum, WebSocketConnectOptions } from 'types';
|
|||
|
||||
import { KeepAliveService } from './KeepAliveService';
|
||||
import { WebClient } from '../WebClient';
|
||||
import { SessionPersistence } from '../persistence';
|
||||
|
||||
export class WebSocketService {
|
||||
private socket: WebSocket;
|
||||
|
@ -60,7 +61,10 @@ export class WebSocketService {
|
|||
const socket = new WebSocket(url);
|
||||
socket.binaryType = 'arraybuffer';
|
||||
|
||||
const connectionTimer = setTimeout(() => socket.close(), this.keepalive);
|
||||
|
||||
socket.onopen = () => {
|
||||
clearTimeout(connectionTimer);
|
||||
this.updateStatus(StatusEnum.CONNECTED, 'Connected');
|
||||
|
||||
this.keepAliveService.startPingLoop(this.keepalive, (pingReceived: Function) => {
|
||||
|
@ -79,6 +83,7 @@ export class WebSocketService {
|
|||
|
||||
socket.onerror = () => {
|
||||
this.updateStatus(StatusEnum.DISCONNECTED, 'Connection Failed');
|
||||
SessionPersistence.connectionFailed();
|
||||
};
|
||||
|
||||
socket.onmessage = (event: MessageEvent) => {
|
||||
|
|
Loading…
Reference in a new issue