Skip to content

Commit ad41740

Browse files
committed
Prevent direct url access to match pages
1 parent af8fb3d commit ad41740

File tree

9 files changed

+75
-54
lines changed

9 files changed

+75
-54
lines changed

frontend/src/App.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import AuthProvider from "./contexts/AuthContext";
1919
import ProfileContextProvider from "./contexts/ProfileContext";
2020
import MatchProvider from "./contexts/MatchContext";
2121
import CollabSandbox from "./pages/CollabSandbox";
22+
import NoDirectAccessRoutes from "./components/NoDirectAccessRoutes";
2223

2324
function App() {
2425
return (
@@ -47,12 +48,16 @@ function App() {
4748
}
4849
/>
4950
<Route path="matching" element={<ProtectedRoutes />}>
50-
<Route index element={<Matching />} />
51-
<Route path="matched" element={<Matched />} />
52-
<Route path="timeout" element={<Timeout />} />
51+
<Route element={<NoDirectAccessRoutes />}>
52+
<Route index element={<Matching />} />
53+
<Route path="matched" element={<Matched />} />
54+
<Route path="timeout" element={<Timeout />} />
55+
</Route>
5356
</Route>
5457
<Route path="collaboration" element={<ProtectedRoutes />}>
55-
<Route index element={<CollabSandbox />} />
58+
<Route element={<NoDirectAccessRoutes />}>
59+
<Route index element={<CollabSandbox />} />
60+
</Route>
5661
</Route>
5762
<Route path="*" element={<PageNotFound />} />
5863
</Route>

frontend/src/components/Navbar/index.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,11 @@ const Navbar: React.FC<NavbarProps> = (props) => {
135135
)}
136136
</Stack>
137137
) : (
138-
<Button variant="outlined" color="error" onClick={stopMatch}>
138+
<Button
139+
variant="outlined"
140+
color="error"
141+
onClick={() => stopMatch("/home")}
142+
>
139143
Stop matching
140144
</Button>
141145
)}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Navigate, Outlet, useLocation, useNavigate } from "react-router-dom";
2+
import React from "react";
3+
4+
export const useAppNavigate = () => {
5+
const navigate = useNavigate();
6+
7+
const appNavigate = (path: string) => {
8+
navigate(path, { replace: true, state: { from: "app-navigation" } });
9+
};
10+
11+
return appNavigate;
12+
};
13+
14+
const NoDirectAccessRoutes: React.FC = () => {
15+
const location = useLocation();
16+
17+
if (location.state?.from !== "app-navigation") {
18+
return <Navigate to="/home" />;
19+
}
20+
21+
return <Outlet />;
22+
};
23+
24+
export default NoDirectAccessRoutes;

frontend/src/components/ProtectedRoutes/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const ProtectedRoutes: React.FC<ProtectedRoutesProps> = ({
3535
);
3636
}
3737

38-
return <Outlet context={user} />;
38+
return <Outlet />;
3939
};
4040

4141
export default ProtectedRoutes;

frontend/src/contexts/MatchContext.tsx

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/* eslint-disable react-refresh/only-export-components */
22

33
import { createContext, useContext, useState } from "react";
4-
import { useNavigate } from "react-router-dom";
54
import { matchSocket } from "../utils/matchSocket";
65
import { minMatchTimeout, USE_AUTH_ERROR_MESSAGE } from "../utils/constants";
76
import { useAuth } from "./AuthContext";
87
import { toast } from "react-toastify";
8+
import { useAppNavigate } from "../components/NoDirectAccessRoutes";
99

1010
type MatchUser = {
1111
id: string;
@@ -37,7 +37,6 @@ enum MatchEvents {
3737
}
3838

3939
type MatchContextType = {
40-
closeConnection: (path: string) => void;
4140
findMatch: (
4241
complexities: string[],
4342
categories: string[],
@@ -47,18 +46,17 @@ type MatchContextType = {
4746
retryMatch: () => void;
4847
acceptMatch: () => void;
4948
rematch: () => void;
50-
stopMatch: () => void;
49+
stopMatch: (path: string) => void;
5150
matchUser: MatchUser | null;
5251
matchCriteria: MatchCriteria;
53-
matchId: string | null;
5452
partner: MatchUser | null;
5553
};
5654

5755
const MatchContext = createContext<MatchContextType | null>(null);
5856

5957
const MatchProvider: React.FC<{ children?: React.ReactNode }> = (props) => {
6058
const { children } = props;
61-
const navigate = useNavigate();
59+
const appNavigate = useAppNavigate();
6260

6361
const auth = useAuth();
6462
if (!auth) {
@@ -84,18 +82,9 @@ const MatchProvider: React.FC<{ children?: React.ReactNode }> = (props) => {
8482
const [matchId, setMatchId] = useState<string | null>(null);
8583
const [partner, setPartner] = useState<MatchUser | null>(null);
8684

87-
const closeConnection = (path: string) => {
85+
const closeConnection = () => {
8886
matchSocket.removeAllListeners();
8987
matchSocket.disconnect();
90-
setMatchCriteria({
91-
complexities: [],
92-
categories: [],
93-
languages: [],
94-
timeout: minMatchTimeout,
95-
});
96-
setMatchId(null);
97-
setPartner(null);
98-
navigate(path, { replace: true });
9988
};
10089

10190
const openConnection = () => {
@@ -108,6 +97,7 @@ const MatchProvider: React.FC<{ children?: React.ReactNode }> = (props) => {
10897
matchSocket.on(MatchEvents.MATCH_FOUND, ({ matchId, user1, user2 }) => {
10998
setMatchId(matchId);
11099
matchUser?.id === user1.id ? setPartner(user2) : setPartner(user1);
100+
appNavigate("/matching/matched");
111101
});
112102
}
113103

@@ -119,20 +109,20 @@ const MatchProvider: React.FC<{ children?: React.ReactNode }> = (props) => {
119109

120110
if (!matchSocket.hasListeners(MatchEvents.MATCH_SUCCESSFUL)) {
121111
matchSocket.on(MatchEvents.MATCH_SUCCESSFUL, () => {
122-
navigate("/collaboration", { replace: true });
112+
appNavigate("/collaboration");
123113
});
124114
}
125115

126116
if (!matchSocket.hasListeners(MatchEvents.MATCH_UNSUCCESSFUL)) {
127117
matchSocket.on(MatchEvents.MATCH_UNSUCCESSFUL, () => {
128118
toast.error("Matching unsuccessful!");
129-
closeConnection("/home");
119+
stopMatch("/home");
130120
});
131121
}
132122

133123
if (!matchSocket.hasListeners(MatchEvents.MATCH_REQUEST_ERROR)) {
134124
matchSocket.on(MatchEvents.MATCH_REQUEST_ERROR, () => {
135-
toast.error("Error sending match request! Please try again later.");
125+
toast.error("Failed to send match request! Please try again later.");
136126
});
137127
}
138128

@@ -153,7 +143,7 @@ const MatchProvider: React.FC<{ children?: React.ReactNode }> = (props) => {
153143

154144
if (!matchSocket.io.hasListeners(MatchEvents.SOCKET_RECONNECT_FAILED)) {
155145
matchSocket.io.on(MatchEvents.SOCKET_RECONNECT_FAILED, () => {
156-
console.log("Oops, something went wrong! Please try again later.");
146+
toast.error("Failed to reconnect! Please try again later.");
157147
});
158148
}
159149
};
@@ -182,7 +172,7 @@ const MatchProvider: React.FC<{ children?: React.ReactNode }> = (props) => {
182172
languages,
183173
timeout,
184174
});
185-
navigate("/matching", { replace: true });
175+
appNavigate("/matching");
186176
}
187177
}
188178
);
@@ -213,25 +203,32 @@ const MatchProvider: React.FC<{ children?: React.ReactNode }> = (props) => {
213203

214204
setMatchId(null);
215205
setPartner(null);
216-
navigate("/matching", { replace: true });
206+
appNavigate("/matching");
217207
};
218208

219-
const stopMatch = () => {
220-
closeConnection("/home");
209+
const stopMatch = (path: string) => {
210+
closeConnection();
211+
setMatchCriteria({
212+
complexities: [],
213+
categories: [],
214+
languages: [],
215+
timeout: minMatchTimeout,
216+
});
217+
setMatchId(null);
218+
setPartner(null);
219+
appNavigate(path);
221220
};
222221

223222
return (
224223
<MatchContext.Provider
225224
value={{
226-
closeConnection,
227225
findMatch,
228226
retryMatch,
229227
acceptMatch,
230228
rematch,
231229
stopMatch,
232230
matchUser,
233231
matchCriteria,
234-
matchId,
235232
partner,
236233
}}
237234
>

frontend/src/pages/CollabSandbox/index.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@ import classes from "./index.module.css";
44
import { useMatch } from "../../contexts/MatchContext";
55
import { USE_MATCH_ERROR_MESSAGE } from "../../utils/constants";
66

7-
// TODO: Prevent user from accessing this page via URL
87
const CollabSandbox: React.FC = () => {
98
const match = useMatch();
109
if (!match) {
1110
throw new Error(USE_MATCH_ERROR_MESSAGE);
1211
}
13-
const { closeConnection } = match;
12+
const { stopMatch } = match;
1413

1514
return (
1615
<AppMargin classname={`${classes.fullheight} ${classes.center}`}>
@@ -20,7 +19,7 @@ const CollabSandbox: React.FC = () => {
2019
<Button
2120
variant="outlined"
2221
color="error"
23-
onClick={() => closeConnection("/home")}
22+
onClick={() => stopMatch("/home")}
2423
>
2524
End Session
2625
</Button>

frontend/src/pages/Matched/index.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useMatch } from "../../contexts/MatchContext";
55
import { USE_MATCH_ERROR_MESSAGE } from "../../utils/constants";
66
import { useEffect, useState } from "react";
77
import { toast } from "react-toastify";
8+
import { Navigate } from "react-router-dom";
89

910
const acceptanceTimeout = 10;
1011

@@ -14,7 +15,7 @@ const Matched: React.FC = () => {
1415
if (!match) {
1516
throw new Error(USE_MATCH_ERROR_MESSAGE);
1617
}
17-
const { closeConnection, acceptMatch, rematch, matchUser, partner } = match;
18+
const { acceptMatch, rematch, stopMatch, matchUser, partner } = match;
1819

1920
const [timeLeft, setTimeLeft] = useState<number>(acceptanceTimeout);
2021

@@ -28,10 +29,14 @@ const Matched: React.FC = () => {
2829
useEffect(() => {
2930
if (timeLeft <= 0) {
3031
toast.error("Match acceptance timeout!");
31-
closeConnection("/home");
32+
stopMatch("/home");
3233
}
3334
}, [timeLeft]);
3435

36+
if (!matchUser || !partner) {
37+
return <Navigate to="/home" />;
38+
}
39+
3540
return (
3641
<AppMargin classname={`${classes.fullheight} ${classes.center}`}>
3742
<Stack spacing={2} alignItems={"center"}>
@@ -44,7 +49,7 @@ const Matched: React.FC = () => {
4449
paddingTop={2}
4550
paddingBottom={2}
4651
>
47-
<Avatar src={matchUser?.profile} sx={{ width: 120, height: 120 }} />
52+
<Avatar src={matchUser.profile} sx={{ width: 120, height: 120 }} />
4853

4954
<Box
5055
sx={(theme) => ({
@@ -55,12 +60,10 @@ const Matched: React.FC = () => {
5560
})}
5661
/>
5762

58-
<Avatar src={partner?.profile} sx={{ width: 120, height: 120 }} />
63+
<Avatar src={partner.profile} sx={{ width: 120, height: 120 }} />
5964
</Box>
6065

61-
<Typography variant="h3">
62-
Practice with @{partner?.username}?
63-
</Typography>
66+
<Typography variant="h3">Practice with @{partner.username}?</Typography>
6467

6568
<Stack spacing={2} direction="row" paddingTop={2} width={700}>
6669
<Button

frontend/src/pages/Matching/index.tsx

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,15 @@ import { Stack, Typography } from "@mui/material";
44
import matching from "../../assets/matching.svg";
55
import classes from "./index.module.css";
66
import Timer from "../../components/Timer";
7-
import { useNavigate } from "react-router-dom";
87
import { useMatch } from "../../contexts/MatchContext";
98
import { USE_MATCH_ERROR_MESSAGE } from "../../utils/constants";
109

11-
// TODO: Prevent user from accessing this page via URL
1210
const Matching: React.FC = () => {
13-
const navigate = useNavigate();
14-
1511
const match = useMatch();
1612
if (!match) {
1713
throw new Error(USE_MATCH_ERROR_MESSAGE);
1814
}
19-
const { closeConnection, matchId, matchCriteria } = match;
15+
const { stopMatch, matchCriteria } = match;
2016

2117
const [timeLeft, setTimeLeft] = useState<number>(matchCriteria.timeout);
2218

@@ -29,16 +25,10 @@ const Matching: React.FC = () => {
2925

3026
useEffect(() => {
3127
if (timeLeft <= 0) {
32-
closeConnection("timeout");
28+
stopMatch("/matching/timeout");
3329
}
3430
}, [timeLeft]);
3531

36-
useEffect(() => {
37-
if (matchId) {
38-
navigate("matched", { replace: true });
39-
}
40-
}, [matchId, navigate]);
41-
4232
return (
4333
<AppMargin classname={`${classes.fullheight} ${classes.center}`}>
4434
<Stack spacing={2} alignItems={"center"}>

frontend/src/pages/Timeout/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import classes from "./index.module.css";
66
import { useMatch } from "../../contexts/MatchContext";
77
import { USE_MATCH_ERROR_MESSAGE } from "../../utils/constants";
88

9-
// TODO: Prevent user from accessing this page via URL
109
const Timeout: React.FC = () => {
1110
const navigate = useNavigate();
1211

0 commit comments

Comments
 (0)