Skip to content

Commit 87afd27

Browse files
authored
fix: block navigation to authenticated routes (#2120)
1 parent 5a631b0 commit 87afd27

5 files changed

Lines changed: 125 additions & 29 deletions

File tree

packages/website/lib/constants.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,17 @@ if (globalThis.window) {
2020
}
2121
}
2222

23+
const AUTHENTICATED_ROUTES = {
24+
MANAGE: 'manage',
25+
FILES: 'files',
26+
NEW_FILE: 'new-file',
27+
NEW_KEY: 'new-key',
28+
PINNING_REQUEST: 'pinning-request',
29+
}
30+
2331
// eslint-disable-next-line import/no-anonymous-default-export
2432
export default {
33+
AUTHENTICATED_ROUTES,
2534
API: API,
2635
MAGIC_TOKEN: MAGIC_TOKEN,
2736
}

packages/website/lib/magic.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,15 @@ export async function isLoggedIn() {
5959
* Login with email
6060
*
6161
* @param {string} email
62+
* @param {string} [redirectPath]
6263
*/
63-
export async function loginEmail(email) {
64+
export async function loginEmail(email, redirectPath) {
6465
const didToken = await getMagic().auth.loginWithMagicLink({
6566
email: email,
66-
redirectURI: new URL('/callback', window.location.origin).href,
67+
redirectURI: new URL(
68+
`/callback${redirectPath ? '/' + redirectPath : ''}`,
69+
window.location.origin
70+
).href,
6771
})
6872

6973
if (didToken) {
@@ -78,11 +82,15 @@ export async function loginEmail(email) {
7882
* Login with social
7983
*
8084
* @param {import('@magic-ext/oauth').OAuthProvider} provider
85+
* @param {string} [redirectPath]
8186
*/
82-
export async function loginSocial(provider) {
87+
export async function loginSocial(provider, redirectPath) {
8388
await getMagic().oauth.loginWithRedirect({
8489
provider,
85-
redirectURI: new URL('/callback', window.location.origin).href,
90+
redirectURI: new URL(
91+
`/callback${redirectPath ? '/' + redirectPath : ''}`,
92+
window.location.origin
93+
).href,
8694
})
8795
}
8896

packages/website/pages/_app.js

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import * as Sentry from '@sentry/nextjs'
1313
import { UserContext } from 'lib/user'
1414
import BlockedUploadsModal from 'components/blockedUploadsModal.js'
1515
import Loading from 'components/loading'
16+
import constants from 'lib/constants'
1617

1718
const queryClient = new QueryClient({
1819
defaultOptions: { queries: { staleTime: 60 * 1000 } },
@@ -26,33 +27,52 @@ const queryClient = new QueryClient({
2627
export default function App({ Component, pageProps }) {
2728
const router = useRouter()
2829
const [user, setUser] = useState(null)
30+
const [isUserInitialized, setIsUserInitialized] = useState(false)
2931
const [isUserBlockedModalShowing, setIsUserBlockedModalShowing] =
3032
useState(false)
3133

3234
const handleIsLoggedIn = useCallback(async () => {
3335
const data = await isLoggedIn()
34-
if (!data) return
36+
if (!data) {
37+
setIsUserInitialized(true)
38+
return
39+
}
40+
3541
if (data) {
3642
// @ts-ignore
3743
Sentry.setUser(user)
3844
}
39-
const tags = await getUserTags()
40-
const pendingTagProposals = await getUserRequests()
4145

42-
if (tags.HasAccountRestriction && !sessionStorage.hasSeenUserBlockedModal) {
43-
sessionStorage.hasSeenUserBlockedModal = true
44-
setIsUserBlockedModalShowing(true)
46+
try {
47+
const tags = await getUserTags()
48+
const pendingTagProposals = await getUserRequests()
49+
if (
50+
tags.HasAccountRestriction &&
51+
!sessionStorage.hasSeenUserBlockedModal
52+
) {
53+
sessionStorage.hasSeenUserBlockedModal = true
54+
setIsUserBlockedModalShowing(true)
55+
}
56+
setUser({
57+
...data,
58+
// @ts-ignore
59+
tags,
60+
pendingTagProposals,
61+
})
62+
} catch (e) {
63+
// do nothing
4564
}
65+
4666
// @ts-ignore
47-
setUser({
48-
...data,
49-
// @ts-ignore
50-
tags,
51-
pendingTagProposals,
52-
})
5367
// eslint-disable-next-line react-hooks/exhaustive-deps
5468
}, [])
5569

70+
useEffect(() => {
71+
if (user) {
72+
setIsUserInitialized(true)
73+
}
74+
}, [user])
75+
5676
useEffect(() => {
5777
handleIsLoggedIn()
5878
}, [router, handleIsLoggedIn])
@@ -67,14 +87,39 @@ export default function App({ Component, pageProps }) {
6787
// If redirectIfFound is also set, redirect if the user was found
6888
(pageProps.redirectIfFound && user)
6989
) {
70-
router.push(pageProps.redirectTo)
90+
router.push(router.query.returnUrl ?? pageProps.redirectTo)
91+
}
92+
}, [pageProps, user, router, isUserInitialized])
93+
94+
const [pendingAuthCheckRoute, setPendingAuthCheckRoute] = useState('')
95+
96+
useEffect(() => {
97+
if (pendingAuthCheckRoute && isUserInitialized) {
98+
const str = pendingAuthCheckRoute.replace(/(^\/+|\/+$)/g, '')
99+
100+
if (
101+
!user &&
102+
Object.values(constants.AUTHENTICATED_ROUTES).includes(str)
103+
) {
104+
router.push({
105+
pathname: '/login',
106+
query: {
107+
returnUrl: str,
108+
},
109+
})
110+
}
111+
setPendingAuthCheckRoute('')
71112
}
72-
}, [pageProps, user, router])
113+
}, [pendingAuthCheckRoute, isUserInitialized, user, router])
73114

74115
useEffect(() => {
75116
Router.events.on('routeChangeComplete', (route) => {
76117
countly.trackPageView(route)
118+
setPendingAuthCheckRoute(route.split('?')[0].toLowerCase())
77119
})
120+
setPendingAuthCheckRoute(
121+
window.location.pathname.split('?')[0].toLowerCase()
122+
)
78123
}, [])
79124

80125
function handleClearUser() {
@@ -106,7 +151,7 @@ export default function App({ Component, pageProps }) {
106151
<QueryClientProvider client={queryClient}>
107152
<UserContext.Provider
108153
// @ts-ignore
109-
value={{ user, handleClearUser }}
154+
value={{ user, handleClearUser, handleIsLoggedIn }}
110155
>
111156
<Layout {...pageProps}>
112157
{(props) => <Component {...pageProps} {...props} />}

packages/website/pages/callback.js renamed to packages/website/pages/callback/[[...redirectPath]].js

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import { useEffect } from 'react'
1+
import { useEffect, useState } from 'react'
22
import { useRouter } from 'next/router'
33
import { useQueryClient } from 'react-query'
4-
import { redirectMagic, redirectSocial } from '../lib/magic.js'
4+
import { redirectMagic, redirectSocial } from '../../lib/magic.js'
5+
import { useUser } from 'lib/user.js'
6+
import constants from 'lib/constants.js'
57

68
/**
79
*
8-
* @returns {{ props: import('../components/types.js').LayoutProps}}
10+
* @returns {{ props: import('../../components/types.js').LayoutProps}}
911
*/
1012
export function getStaticProps() {
1113
return {
@@ -17,15 +19,41 @@ export function getStaticProps() {
1719
}
1820
}
1921

22+
export function getStaticPaths() {
23+
return {
24+
paths: [
25+
{ params: { redirectPath: [''] } }, // redirect to default
26+
...Object.values(constants.AUTHENTICATED_ROUTES).map((route) => ({
27+
params: { redirectPath: [route] },
28+
})),
29+
],
30+
fallback: false,
31+
}
32+
}
33+
2034
const Callback = () => {
2135
const router = useRouter()
2236
const queryClient = useQueryClient()
37+
/** @type [string | undefined, any] */
38+
const [pendingRedirect, setPendingRedirect] = useState()
39+
const { user, handleIsLoggedIn } = useUser()
40+
41+
const redirectPath = '/' + (router.query.redirectPath?.[0] ?? 'files')
42+
43+
useEffect(() => {
44+
if (typeof pendingRedirect === 'string' && user) {
45+
router.push(pendingRedirect)
46+
setPendingRedirect()
47+
}
48+
}, [pendingRedirect, user, router])
49+
2350
useEffect(() => {
2451
const finishSocialLogin = async () => {
2552
try {
2653
await redirectSocial()
2754
await queryClient.invalidateQueries('magic-user')
28-
router.push('/files')
55+
handleIsLoggedIn()
56+
setPendingRedirect(redirectPath)
2957
} catch (err) {
3058
console.error(err)
3159
await queryClient.invalidateQueries('magic-user')
@@ -36,7 +64,8 @@ const Callback = () => {
3664
try {
3765
await redirectMagic()
3866
await queryClient.invalidateQueries('magic-user')
39-
router.push('/files')
67+
handleIsLoggedIn()
68+
setPendingRedirect(redirectPath)
4069
} catch (err) {
4170
console.error(err)
4271
await queryClient.invalidateQueries('magic-user')
@@ -49,7 +78,7 @@ const Callback = () => {
4978
if (router.query.provider) {
5079
finishSocialLogin()
5180
}
52-
}, [router, router.query, queryClient])
81+
}, [router, router.query, queryClient, redirectPath, handleIsLoggedIn])
5382

5483
// TODO handle errors
5584
return null

packages/website/pages/login.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState } from 'react'
2-
import Router from 'next/router'
2+
import { useRouter } from 'next/router'
33
import { loginEmail, loginSocial } from '../lib/magic.js'
44
import countly from '../lib/countly'
55
import Button from '../components/button.js'
@@ -21,6 +21,11 @@ export default function Login() {
2121
const [errorMsg, setErrorMsg] = useState('')
2222
const [disabled, setDisabled] = useState(false)
2323
const [isRedirecting, setIsRedirecting] = useState(false)
24+
const router = useRouter()
25+
const returnPath =
26+
typeof router.query.returnUrl === 'string'
27+
? router.query.returnUrl
28+
: undefined
2429

2530
/**
2631
* @param {import('react').ChangeEvent<HTMLFormElement>} e
@@ -32,9 +37,9 @@ export default function Login() {
3237
setDisabled(true)
3338

3439
try {
35-
await loginEmail(e.currentTarget.email.value)
40+
await loginEmail(e.currentTarget.email.value, returnPath)
3641
await queryClient.invalidateQueries('magic-user')
37-
Router.push('/files')
42+
router.push(returnPath ?? '/files')
3843
} catch (/** @type {any} */ error) {
3944
setDisabled(false)
4045
console.error('An unexpected error happened occurred:', error)
@@ -55,7 +60,7 @@ export default function Login() {
5560
className="w-64"
5661
onClick={() => {
5762
setIsRedirecting(true)
58-
loginSocial('github')
63+
loginSocial('github', returnPath)
5964
}}
6065
tracking={{
6166
event: countly.events.LOGIN_CLICK,

0 commit comments

Comments
 (0)