Added authentication handling

This commit is contained in:
Mitchell McCaffrey 2020-04-14 16:05:44 +10:00
parent 5dc6a4ed32
commit 2ceec9cfec
9 changed files with 237 additions and 38 deletions

View File

@ -8,25 +8,29 @@ import Game from "./routes/Game";
import About from "./routes/About";
import FAQ from "./routes/FAQ";
import { AuthProvider } from "./contexts/AuthContext";
function App() {
return (
<ThemeProvider theme={theme}>
<Router>
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/faq">
<FAQ />
</Route>
<Route path="/game/:id">
<Game />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
<AuthProvider>
<Router>
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/faq">
<FAQ />
</Route>
<Route path="/game/:id">
<Game />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
</AuthProvider>
</ThemeProvider>
);
}

View File

@ -2,7 +2,13 @@ import React from "react";
import Modal from "react-modal";
import { useThemeUI, Close } from "theme-ui";
function StyledModal({ isOpen, onRequestClose, children, ...props }) {
function StyledModal({
isOpen,
onRequestClose,
children,
allowClose,
...props
}) {
const { theme } = useThemeUI();
return (
@ -25,13 +31,19 @@ function StyledModal({ isOpen, onRequestClose, children, ...props }) {
{...props}
>
{children}
<Close
m={0}
sx={{ position: "absolute", top: 0, right: 0 }}
onClick={onRequestClose}
/>
{allowClose && (
<Close
m={0}
sx={{ position: "absolute", top: 0, right: 0 }}
onClick={onRequestClose}
/>
)}
</Modal>
);
}
StyledModal.defaultProps = {
allowClose: true,
};
export default StyledModal;

View File

@ -0,0 +1,25 @@
import React, { useState, useEffect } from "react";
const AuthContext = React.createContext();
export function AuthProvider({ children }) {
const [password, setPassword] = useState(
sessionStorage.getItem("auth") || ""
);
useEffect(() => {
sessionStorage.setItem("auth", password);
}, [password]);
const [authenticationStatus, setAuthenticationStatus] = useState("unknown");
const value = {
password,
setPassword,
authenticationStatus,
setAuthenticationStatus,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export default AuthContext;

View File

@ -1,9 +1,11 @@
import { useEffect, useState } from "react";
import { useEffect, useState, useContext } from "react";
import io from "socket.io-client";
import { omit } from "../helpers/shared";
import Peer from "../helpers/Peer";
import AuthContext from "../contexts/AuthContext";
const socket = io("https://broker.owlbear.rodeo");
function useSession(
@ -15,9 +17,11 @@ function useSession(
onPeerTrackRemoved,
onPeerError
) {
const { password, setAuthenticationStatus } = useContext(AuthContext);
useEffect(() => {
socket.emit("join party", partyId);
}, [partyId]);
socket.emit("join party", partyId, password);
}, [partyId, password]);
const [peers, setPeers] = useState({});
@ -148,6 +152,7 @@ function useSession(
const sync = index === 0;
addPeer(id, true, sync);
}
setAuthenticationStatus("authenticated");
}
function handleSignal(data) {
@ -157,17 +162,23 @@ function useSession(
}
}
function handleAuthError() {
setAuthenticationStatus("unauthenticated");
}
socket.on("party member joined", handlePartyMemberJoined);
socket.on("party member left", handlePartyMemberLeft);
socket.on("joined party", handleJoinedParty);
socket.on("signal", handleSignal);
socket.on("auth error", handleAuthError);
return () => {
socket.removeListener("party member joined", handlePartyMemberJoined);
socket.removeListener("party member left", handlePartyMemberLeft);
socket.removeListener("joined party", handleJoinedParty);
socket.removeListener("signal", handleSignal);
socket.removeListener("auth error", handleAuthError);
};
}, [peers]);
}, [peers, setAuthenticationStatus]);
return { peers, socket };
}

52
src/modals/AuthModal.js Normal file
View File

@ -0,0 +1,52 @@
import React, { useState, useContext, useRef } from "react";
import { Box, Input, Button, Label, Flex } from "theme-ui";
import AuthContext from "../contexts/AuthContext";
import Modal from "../components/Modal";
function AuthModal({ isOpen }) {
const { password, setPassword, setAuthenticationStatus } = useContext(
AuthContext
);
const [tmpPassword, setTempPassword] = useState(password);
function handleChange(event) {
setTempPassword(event.target.value);
}
function handleSubmit(event) {
event.preventDefault();
setAuthenticationStatus("unknown");
setPassword(tmpPassword);
}
const inputRef = useRef();
function focusInput() {
inputRef.current && inputRef.current.focus();
}
return (
<Modal isOpen={isOpen} allowClose={false} onAfterOpen={focusInput}>
<Box as="form" onSubmit={handleSubmit}>
<Label py={2} htmlFor="password">
Enter password
</Label>
<Input
id="password"
value={tmpPassword}
onChange={handleChange}
ref={inputRef}
autoComplete="off"
/>
<Flex py={2}>
<Button sx={{ flexGrow: 1 }} disabled={!tmpPassword}>
Join
</Button>
</Flex>
</Box>
</Modal>
);
}
export default AuthModal;

View File

@ -48,7 +48,11 @@ function JoinModal({ isOpen, onRequestClose }) {
onChange={handleChange}
ref={inputRef}
/>
<Button disabled={!gameId}>Join ()*:</Button>
<Flex>
<Button sx={{ flexGrow: 1 }} disabled={!gameId}>
Join ()*:
</Button>
</Flex>
</Box>
</Flex>
</Modal>

73
src/modals/StartModal.js Normal file
View File

@ -0,0 +1,73 @@
import React, { useState, useContext } from "react";
import { Box, Label, Input, Button, Flex, Checkbox } from "theme-ui";
import { useHistory } from "react-router-dom";
import shortid from "shortid";
import AuthContext from "../contexts/AuthContext";
import Modal from "../components/Modal";
function StartModal({ isOpen, onRequestClose }) {
let history = useHistory();
const { password, setPassword } = useContext(AuthContext);
function handlePasswordChange(event) {
setPassword(event.target.value);
}
const [usePassword, setUsePassword] = useState(true);
function handleUsePasswordChange(event) {
setUsePassword(event.target.checked);
}
function handleSubmit(event) {
event.preventDefault();
if (!usePassword) {
setPassword("");
}
history.push(`/game/${shortid.generate()}`);
}
return (
<Modal isOpen={isOpen} onRequestClose={onRequestClose}>
<Flex
sx={{
flexDirection: "column",
justifyContent: "center",
maxWidth: "300px",
flexGrow: 1,
}}
m={2}
>
<Box as="form" onSubmit={handleSubmit}>
<Label htmlFor="password">Password</Label>
<Input
my={1}
id="password"
name="password"
value={usePassword ? password : ""}
onChange={handlePasswordChange}
disabled={!usePassword}
autoComplete="off"
/>
<Box>
<Label mb={3}>
<Checkbox
checked={usePassword}
onChange={handleUsePasswordChange}
/>
Use password
</Label>
</Box>
<Flex>
<Button sx={{ flexGrow: 1 }} disabled={!password && usePassword}>
Start
</Button>
</Flex>
</Box>
</Flex>
</Modal>
);
}
export default StartModal;

View File

@ -1,9 +1,14 @@
import React, { useState, useRef, useEffect, useCallback } from "react";
import React, {
useState,
useRef,
useEffect,
useCallback,
useContext,
} from "react";
import { Flex, Box, Text, Link } from "theme-ui";
import { useParams } from "react-router-dom";
import { omit, isStreamStopped } from "../helpers/shared";
import useSession from "../helpers/useSession";
import { getRandomMonster } from "../helpers/monsters";
@ -12,8 +17,13 @@ import Tokens from "../components/Tokens";
import Map from "../components/Map";
import Banner from "../components/Banner";
import AuthModal from "../modals/AuthModal";
import AuthContext from "../contexts/AuthContext";
function Game() {
const { id: gameId } = useParams();
const { authenticationStatus } = useContext(AuthContext);
const { peers, socket } = useSession(
gameId,
@ -238,6 +248,7 @@ function Game() {
</Text>
</Box>
</Banner>
<AuthModal isOpen={authenticationStatus === "unauthenticated"} />
</>
);
}

View File

@ -1,22 +1,25 @@
import React, { useState } from "react";
import React, { useState, useEffect, useContext } from "react";
import { Flex, Button, Image, Text } from "theme-ui";
import shortid from "shortid";
import { useHistory } from "react-router-dom";
import Footer from "../components/Footer";
import StartModal from "../modals/StartModal";
import JoinModal from "../modals/JoinModal";
import AuthContext from "../contexts/AuthContext";
import owlington from "../images/Owlington.png";
function Home() {
let history = useHistory();
function handleStartGame() {
history.push(`/game/${shortid.generate()}`);
}
const [isStartModalOpen, setIsStartModalOpen] = useState(false);
const [isJoinModalOpen, setIsJoinModalOpen] = useState(false);
// Reset password on visiting home
const { setPassword } = useContext(AuthContext);
useEffect(() => {
setPassword("");
}, [setPassword]);
return (
<Flex
sx={{
@ -39,7 +42,7 @@ function Home() {
Owlbear Rodeo
</Text>
<Image src={owlington} m={2} />
<Button m={2} onClick={handleStartGame}>
<Button m={2} onClick={() => setIsStartModalOpen(true)}>
Start Game
</Button>
<Button m={2} onClick={() => setIsJoinModalOpen(true)}>
@ -52,6 +55,10 @@ function Home() {
isOpen={isJoinModalOpen}
onRequestClose={() => setIsJoinModalOpen(false)}
/>
<StartModal
isOpen={isStartModalOpen}
onRequestClose={() => setIsStartModalOpen(false)}
/>
</Flex>
<Footer />
</Flex>