From bb16ae09ef50d8c5cbd1ba89e2f5f10537a8a376 Mon Sep 17 00:00:00 2001 From: Jeremy Letto Date: Fri, 4 Feb 2022 13:07:15 -0600 Subject: [PATCH] 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 --- webclient/src/containers/App/AppShell.tsx | 3 +++ .../src/containers/App/AppShellRoutes.tsx | 4 ++- .../src/containers/App/FeatureDetection.tsx | 26 ++++++++++++++++++ webclient/src/containers/Login/Login.tsx | 2 +- .../containers/Unsupported/Unsupported.css | 18 +++++++++++++ .../containers/Unsupported/Unsupported.tsx | 27 +++++++++++++++++++ webclient/src/containers/index.ts | 1 + webclient/src/services/dexie/DexieService.ts | 4 +++ webclient/src/services/dexie/index.ts | 1 + webclient/src/store/server/server.actions.ts | 3 +++ webclient/src/store/server/server.dispatch.ts | 3 +++ webclient/src/store/server/server.types.ts | 1 + webclient/src/types/routes.tsx | 1 + .../src/websocket/commands/SessionCommands.ts | 10 ------- .../src/websocket/events/SessionEvents.ts | 9 +++---- .../persistence/SessionPersistence.ts | 4 +++ .../websocket/services/WebSocketService.ts | 5 ++++ 17 files changed, 104 insertions(+), 18 deletions(-) create mode 100644 webclient/src/containers/App/FeatureDetection.tsx create mode 100644 webclient/src/containers/Unsupported/Unsupported.css create mode 100644 webclient/src/containers/Unsupported/Unsupported.tsx diff --git a/webclient/src/containers/App/AppShell.tsx b/webclient/src/containers/App/AppShell.tsx index 855c84e5..ee2d5ecb 100644 --- a/webclient/src/containers/App/AppShell.tsx +++ b/webclient/src/containers/App/AppShell.tsx @@ -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 {
+ +
diff --git a/webclient/src/containers/App/AppShellRoutes.tsx b/webclient/src/containers/App/AppShellRoutes.tsx index b141c029..699281ba 100644 --- a/webclient/src/containers/App/AppShellRoutes.tsx +++ b/webclient/src/containers/App/AppShellRoutes.tsx @@ -10,7 +10,8 @@ import { Room, Server, Login, - Logs + Logs, + Unsupported } from 'containers'; const Routes = () => ( @@ -24,6 +25,7 @@ const Routes = () => ( { } />} } /> } /> + } /> diff --git a/webclient/src/containers/App/FeatureDetection.tsx b/webclient/src/containers/App/FeatureDetection.tsx new file mode 100644 index 00000000..4342f516 --- /dev/null +++ b/webclient/src/containers/App/FeatureDetection.tsx @@ -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[] = [ + detectIndexedDB(), + ]; + + Promise.all(features).catch((e) => setUnsupported(true)); + }, []); + + return unsupported + ? + : <>; + + function detectIndexedDB() { + return dexieService.testConnection(); + } +}; + +export default FeatureDetection; diff --git a/webclient/src/containers/Login/Login.tsx b/webclient/src/containers/Login/Login.tsx index f7a75427..8a54a348 100644 --- a/webclient/src/containers/Login/Login.tsx +++ b/webclient/src/containers/Login/Login.tsx @@ -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) { diff --git a/webclient/src/containers/Unsupported/Unsupported.css b/webclient/src/containers/Unsupported/Unsupported.css new file mode 100644 index 00000000..46752e74 --- /dev/null +++ b/webclient/src/containers/Unsupported/Unsupported.css @@ -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; +} diff --git a/webclient/src/containers/Unsupported/Unsupported.tsx b/webclient/src/containers/Unsupported/Unsupported.tsx new file mode 100644 index 00000000..16c2e0ca --- /dev/null +++ b/webclient/src/containers/Unsupported/Unsupported.tsx @@ -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 ( +
+ +
+ Unsupported Browser + Please update your browser and/or check your permissions. +
+ + Note: Private browsing causes some browsers to disable certain permissions or features. +
+
+ ); +}; + +const mapStateToProps = state => ({ + +}); + +export default withRouter(connect(mapStateToProps)(Unsupported)); diff --git a/webclient/src/containers/index.ts b/webclient/src/containers/index.ts index b6437753..de43d8ec 100644 --- a/webclient/src/containers/index.ts +++ b/webclient/src/containers/index.ts @@ -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'; diff --git a/webclient/src/services/dexie/DexieService.ts b/webclient/src/services/dexie/DexieService.ts index 71b69c5a..9dcd3b94 100644 --- a/webclient/src/services/dexie/DexieService.ts +++ b/webclient/src/services/dexie/DexieService.ts @@ -28,6 +28,10 @@ class DexieService { get hosts() { return this.db.table(Stores.HOSTS); } + + testConnection() { + return this.db.open(); + } } export const dexieService = new DexieService(); diff --git a/webclient/src/services/dexie/index.ts b/webclient/src/services/dexie/index.ts index f27c81b5..593f1939 100644 --- a/webclient/src/services/dexie/index.ts +++ b/webclient/src/services/dexie/index.ts @@ -1 +1,2 @@ export * from './DexieDTOs'; +export * from './DexieService'; diff --git a/webclient/src/store/server/server.actions.ts b/webclient/src/store/server/server.actions.ts index 3789d1e1..62b79da6 100644 --- a/webclient/src/store/server/server.actions.ts +++ b/webclient/src/store/server/server.actions.ts @@ -18,6 +18,9 @@ export const Actions = { type: Types.CONNECTION_CLOSED, reason }), + connectionFailed: () => ({ + type: Types.CONNECTION_FAILED, + }), serverMessage: message => ({ type: Types.SERVER_MESSAGE, message diff --git a/webclient/src/store/server/server.dispatch.ts b/webclient/src/store/server/server.dispatch.ts index 581fe2ed..3e637bbe 100644 --- a/webclient/src/store/server/server.dispatch.ts +++ b/webclient/src/store/server/server.dispatch.ts @@ -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)); }, diff --git a/webclient/src/store/server/server.types.ts b/webclient/src/store/server/server.types.ts index c797f365..b1adcdf0 100644 --- a/webclient/src/store/server/server.types.ts +++ b/webclient/src/store/server/server.types.ts @@ -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', diff --git a/webclient/src/types/routes.tsx b/webclient/src/types/routes.tsx index 386471a7..51dde695 100644 --- a/webclient/src/types/routes.tsx +++ b/webclient/src/types/routes.tsx @@ -10,4 +10,5 @@ export enum RouteEnum { ACCOUNT = '/account', ADMINISTRATION = '/administration', REPLAYS = '/replays', + UNSUPPORTED = '/unsupported', } diff --git a/webclient/src/websocket/commands/SessionCommands.ts b/webclient/src/websocket/commands/SessionCommands.ts index 742d8d27..648a8a00 100644 --- a/webclient/src/websocket/commands/SessionCommands.ts +++ b/webclient/src/websocket/commands/SessionCommands.ts @@ -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; diff --git a/webclient/src/websocket/events/SessionEvents.ts b/webclient/src/websocket/events/SessionEvents.ts index 723745ad..4636470a 100644 --- a/webclient/src/websocket/events/SessionEvents.ts +++ b/webclient/src/websocket/events/SessionEvents.ts @@ -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)) { diff --git a/webclient/src/websocket/persistence/SessionPersistence.ts b/webclient/src/websocket/persistence/SessionPersistence.ts index f168a868..d9a772eb 100644 --- a/webclient/src/websocket/persistence/SessionPersistence.ts +++ b/webclient/src/websocket/persistence/SessionPersistence.ts @@ -21,6 +21,10 @@ export class SessionPersistence { ServerDispatch.connectionClosed(reason); } + static connectionFailed() { + ServerDispatch.connectionFailed(); + } + static updateBuddyList(buddyList) { ServerDispatch.updateBuddyList(buddyList); } diff --git a/webclient/src/websocket/services/WebSocketService.ts b/webclient/src/websocket/services/WebSocketService.ts index 135e7407..f1b98eeb 100644 --- a/webclient/src/websocket/services/WebSocketService.ts +++ b/webclient/src/websocket/services/WebSocketService.ts @@ -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) => {