From 1cb04c8b5a52988b1097540eb91e05836507fb31 Mon Sep 17 00:00:00 2001 From: Katalina Okano Date: Mon, 31 Jan 2022 23:32:41 -0500 Subject: [PATCH] update login flow to prevent session leakage --- packages/api/src/routes/auth/callback.ts | 7 ++-- packages/web/src/app-router/AppRouter.tsx | 5 +-- packages/web/src/pages/auth/login.tsx | 27 ++++++++++++---- .../web/src/pages/machinery/new-session.tsx | 32 +++++++++++-------- 4 files changed, 41 insertions(+), 30 deletions(-) diff --git a/packages/api/src/routes/auth/callback.ts b/packages/api/src/routes/auth/callback.ts index 6234172..d13079b 100644 --- a/packages/api/src/routes/auth/callback.ts +++ b/packages/api/src/routes/auth/callback.ts @@ -9,10 +9,7 @@ import { seeOther } from '@roleypoly/api/src/utils/response'; import { AuthTokenResponse, StateSession } from '@roleypoly/types'; const authFailure = (uiPublicURI: string, extra?: string) => - seeOther( - uiPublicURI + - `/machinery/error?error_code=authFailure${extra ? `&extra=${extra}` : ''}` - ); + seeOther(uiPublicURI + `/error/authFailure${extra ? `?extra=${extra}` : ''}`); export const authCallback: RoleypolyHandler = async ( request: Request, @@ -72,5 +69,5 @@ export const authCallback: RoleypolyHandler = async ( return authFailure(config.uiPublicURI, 'session setup failure'); } - return seeOther(bounceBaseUrl + 'machinery/new-session/' + session.sessionID); + return seeOther(bounceBaseUrl + 'machinery/new-session/#/' + session.sessionID); }; diff --git a/packages/web/src/app-router/AppRouter.tsx b/packages/web/src/app-router/AppRouter.tsx index 7419793..4ed3733 100644 --- a/packages/web/src/app-router/AppRouter.tsx +++ b/packages/web/src/app-router/AppRouter.tsx @@ -44,10 +44,7 @@ export const AppRouter = () => { - + diff --git a/packages/web/src/pages/auth/login.tsx b/packages/web/src/pages/auth/login.tsx index 403ff0f..316e3e5 100644 --- a/packages/web/src/pages/auth/login.tsx +++ b/packages/web/src/pages/auth/login.tsx @@ -1,4 +1,6 @@ -import { redirectTo } from '@reach/router'; +import { useLocation, useNavigate } from '@reach/router'; +import { palette } from '@roleypoly/design-system/atoms/colors'; +import { Link } from '@roleypoly/design-system/atoms/typography'; import { AuthLogin } from '@roleypoly/design-system/templates/auth-login'; import { GenericLoadingTemplate } from '@roleypoly/design-system/templates/generic-loading'; import { GuildSlug } from '@roleypoly/types'; @@ -12,21 +14,23 @@ const Login = (props: { path: string }) => { const { apiUrl } = useApiContext(); const { isAuthenticated } = useSessionContext(); const { getGuildSlug } = useGuildContext(); + const navigate = useNavigate(); + const location = useLocation(); // If ?r is in query, then let's render the slug page // If not, redirect. const [guildSlug, setGuildSlug] = React.useState(null); const [oauthLink, setOauthLink] = React.useState(`${apiUrl}/auth/bounce`); React.useEffect(() => { - const url = new URL(window.location.href); + const url = new URL(location.href); const callbackHost = new URL('/', url); const redirectServerID = url.searchParams.get('r'); const redirectUrl = `${apiUrl}/auth/bounce?cbh=${callbackHost.href}`; if (!redirectServerID) { if (isAuthenticated) { - redirectTo('/servers'); + navigate('/servers'); } - window.location.href = redirectUrl; + navigate(redirectUrl); return; } @@ -43,12 +47,21 @@ const Login = (props: { path: string }) => { fetchGuildSlug(redirectServerID); if (isAuthenticated) { - redirectTo(`/s/${redirectServerID}`); + navigate(`/s/${redirectServerID}`); } - }, [apiUrl, getGuildSlug, isAuthenticated]); + }, [apiUrl, getGuildSlug, isAuthenticated, location, navigate]); if (guildSlug === null) { - return Sending you to Discord...; + return ( + +
+
Sending you to Discord...
+ + If you aren't redirected soon, click here. + +
+
+ ); } return ( diff --git a/packages/web/src/pages/machinery/new-session.tsx b/packages/web/src/pages/machinery/new-session.tsx index caf4da9..0074f2f 100644 --- a/packages/web/src/pages/machinery/new-session.tsx +++ b/packages/web/src/pages/machinery/new-session.tsx @@ -1,3 +1,4 @@ +import { useLocation, useNavigate } from '@reach/router'; import { palette } from '@roleypoly/design-system/atoms/colors'; import { Link } from '@roleypoly/design-system/atoms/typography'; import { GenericLoadingTemplate } from '@roleypoly/design-system/templates/generic-loading'; @@ -6,8 +7,10 @@ import { useSessionContext } from '../../contexts/session/SessionContext'; import { Title } from '../../utils/metaTitle'; const NewSession = (props: { sessionID: string }) => { - const { setupSession, isAuthenticated } = useSessionContext(); + const { setupSession, sessionID } = useSessionContext(); const [postauthUrl, setPostauthUrl] = React.useState('/servers'); + const navigate = useNavigate(); + const location = useLocation(); React.useEffect(() => { const storedPostauthUrl = localStorage.getItem('rp_postauth_redirect'); @@ -18,20 +21,21 @@ const NewSession = (props: { sessionID: string }) => { }, [setPostauthUrl]); React.useEffect(() => { - if (isAuthenticated) { - setTimeout(() => { - window.history.replaceState(null, '', '/'); - window.location.href = postauthUrl; - }, 0); - } - }, [postauthUrl, isAuthenticated]); + if (!sessionID) { + const sessionToken = location.hash.substring(2); + if (!sessionToken) { + console.error({ sessionToken }); + navigate('/error/400?extra=missing-hash'); + return; + } - React.useCallback( - (sessionID) => { - setupSession(sessionID); - }, - [setupSession] - )(props.sessionID); + setupSession(sessionToken); + } + + if (sessionID) { + navigate(postauthUrl); + } + }, [sessionID, location, postauthUrl, setupSession, navigate]); return (