Skip to content

Commit ff7fc72

Browse files
Refactor: Improve middleware and context usage
This commit refactors middleware and context usage for better code organization and clarity. It also includes minor updates to documentation and tests. Co-authored-by: me <[email protected]>
1 parent d0db7c6 commit ff7fc72

20 files changed

+140
-106
lines changed

app/context.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,3 @@ import { createContext } from 'react-router'
22

33
// Holds the authenticated user's ID for routes protected by middleware
44
export const userIdContext = createContext<string | null>(null)
5-

app/middleware.server.ts

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,39 @@
11
import { redirect, type MiddlewareFunction } from 'react-router'
2+
import { userIdContext } from '#app/context.ts'
23
import { prisma } from '#app/utils/db.server.ts'
34
import { authSessionStorage } from '#app/utils/session.server.ts'
4-
import { userIdContext } from '#app/context.ts'
55

66
export const requireUserMiddleware: MiddlewareFunction = async ({
7-
request,
8-
context,
7+
request,
8+
context,
99
}) => {
10-
const cookie = request.headers.get('cookie')
11-
const session = await authSessionStorage.getSession(cookie)
12-
const sessionId = session.get('sessionId') as string | undefined
13-
if (!sessionId) throw redirect(`/login?redirectTo=${encodeURIComponent(new URL(request.url).pathname)}`)
10+
const cookie = request.headers.get('cookie')
11+
const session = await authSessionStorage.getSession(cookie)
12+
const sessionId = session.get('sessionId') as string | undefined
13+
if (!sessionId)
14+
throw redirect(
15+
`/login?redirectTo=${encodeURIComponent(new URL(request.url).pathname)}`,
16+
)
1417

15-
const sessionRecord = await prisma.session.findUnique({
16-
select: { userId: true, expirationDate: true },
17-
where: { id: sessionId },
18-
})
18+
const sessionRecord = await prisma.session.findUnique({
19+
select: { userId: true, expirationDate: true },
20+
where: { id: sessionId },
21+
})
1922

20-
if (!sessionRecord || sessionRecord.expirationDate < new Date()) {
21-
throw redirect(`/login?redirectTo=${encodeURIComponent(new URL(request.url).pathname)}`)
22-
}
23+
if (!sessionRecord || sessionRecord.expirationDate < new Date()) {
24+
throw redirect(
25+
`/login?redirectTo=${encodeURIComponent(new URL(request.url).pathname)}`,
26+
)
27+
}
2328

24-
context.set(userIdContext, sessionRecord.userId)
29+
context.set(userIdContext, sessionRecord.userId)
2530
}
2631

2732
export const requireAnonymousMiddleware: MiddlewareFunction = async ({
28-
request,
33+
request,
2934
}) => {
30-
const cookie = request.headers.get('cookie')
31-
const session = await authSessionStorage.getSession(cookie)
32-
const sessionId = session.get('sessionId') as string | undefined
33-
if (sessionId) throw redirect('/')
35+
const cookie = request.headers.get('cookie')
36+
const session = await authSessionStorage.getSession(cookie)
37+
const sessionId = session.get('sessionId') as string | undefined
38+
if (sessionId) throw redirect('/')
3439
}
35-

app/routes/_auth+/auth.$provider.callback.test.ts

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { invariant } from '@epic-web/invariant'
22
import { faker } from '@faker-js/faker'
33
import { SetCookie } from '@mjackson/headers'
44
import { http } from 'msw'
5-
import { afterEach, expect, test } from 'vitest'
65
import { RouterContextProvider } from 'react-router'
6+
import { afterEach, expect, test } from 'vitest'
77
import { twoFAVerificationType } from '#app/routes/settings+/profile.two-factor.tsx'
88
import { getSessionExpirationDate, sessionKey } from '#app/utils/auth.server.ts'
99
import { GITHUB_PROVIDER_NAME } from '#app/utils/connections.tsx'
@@ -26,9 +26,11 @@ afterEach(async () => {
2626

2727
test('a new user goes to onboarding', async () => {
2828
const request = await setupRequest()
29-
const response = await loader({ request, params: PARAMS, context: new RouterContextProvider() as any }).catch(
30-
(e) => e,
31-
)
29+
const response = await loader({
30+
request,
31+
params: PARAMS,
32+
context: new RouterContextProvider() as any,
33+
}).catch((e) => e)
3234
expect(response).toHaveRedirect('/onboarding/github')
3335
})
3436

@@ -40,9 +42,11 @@ test('when auth fails, send the user to login with a toast', async () => {
4042
}),
4143
)
4244
const request = await setupRequest()
43-
const response = await loader({ request, params: PARAMS, context: new RouterContextProvider() as any }).catch(
44-
(e) => e,
45-
)
45+
const response = await loader({
46+
request,
47+
params: PARAMS,
48+
context: new RouterContextProvider() as any,
49+
}).catch((e) => e)
4650
invariant(response instanceof Response, 'response should be a Response')
4751
expect(response).toHaveRedirect('/login')
4852
await expect(response).toSendToast(
@@ -61,7 +65,11 @@ test('when a user is logged in, it creates the connection', async () => {
6165
sessionId: session.id,
6266
code: githubUser.code,
6367
})
64-
const response = await loader({ request, params: PARAMS, context: new RouterContextProvider() as any })
68+
const response = await loader({
69+
request,
70+
params: PARAMS,
71+
context: new RouterContextProvider() as any,
72+
})
6573
expect(response).toHaveRedirect('/settings/profile/connections')
6674
await expect(response).toSendToast(
6775
expect.objectContaining({
@@ -97,7 +105,11 @@ test(`when a user is logged in and has already connected, it doesn't do anything
97105
sessionId: session.id,
98106
code: githubUser.code,
99107
})
100-
const response = await loader({ request, params: PARAMS, context: new RouterContextProvider() as any })
108+
const response = await loader({
109+
request,
110+
params: PARAMS,
111+
context: new RouterContextProvider() as any,
112+
})
101113
expect(response).toHaveRedirect('/settings/profile/connections')
102114
await expect(response).toSendToast(
103115
expect.objectContaining({
@@ -112,7 +124,11 @@ test('when a user exists with the same email, create connection and make session
112124
const email = githubUser.primaryEmail.toLowerCase()
113125
const { userId } = await setupUser({ ...createUser(), email })
114126
const request = await setupRequest({ code: githubUser.code })
115-
const response = await loader({ request, params: PARAMS, context: new RouterContextProvider() as any })
127+
const response = await loader({
128+
request,
129+
params: PARAMS,
130+
context: new RouterContextProvider() as any,
131+
})
116132

117133
expect(response).toHaveRedirect('/')
118134

@@ -156,7 +172,11 @@ test('gives an error if the account is already connected to another user', async
156172
sessionId: session.id,
157173
code: githubUser.code,
158174
})
159-
const response = await loader({ request, params: PARAMS, context: new RouterContextProvider() as any })
175+
const response = await loader({
176+
request,
177+
params: PARAMS,
178+
context: new RouterContextProvider() as any,
179+
})
160180
expect(response).toHaveRedirect('/settings/profile/connections')
161181
await expect(response).toSendToast(
162182
expect.objectContaining({
@@ -179,7 +199,11 @@ test('if a user is not logged in, but the connection exists, make a session', as
179199
},
180200
})
181201
const request = await setupRequest({ code: githubUser.code })
182-
const response = await loader({ request, params: PARAMS, context: new RouterContextProvider() as any })
202+
const response = await loader({
203+
request,
204+
params: PARAMS,
205+
context: new RouterContextProvider() as any,
206+
})
183207
expect(response).toHaveRedirect('/')
184208
await expect(response).toHaveSessionForUser(userId)
185209
})
@@ -203,7 +227,11 @@ test('if a user is not logged in, but the connection exists and they have enable
203227
},
204228
})
205229
const request = await setupRequest({ code: githubUser.code })
206-
const response = await loader({ request, params: PARAMS, context: new RouterContextProvider() as any })
230+
const response = await loader({
231+
request,
232+
params: PARAMS,
233+
context: new RouterContextProvider() as any,
234+
})
207235
const searchParams = new URLSearchParams({
208236
type: twoFAVerificationType,
209237
target: userId,

app/routes/_auth+/login.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { invariant } from '@epic-web/invariant'
22
import { redirect } from 'react-router'
33
import { safeRedirect } from 'remix-utils/safe-redirect'
4+
import { requireAnonymousMiddleware } from '#app/middleware.server.ts'
45
import { twoFAVerificationType } from '#app/routes/settings+/profile.two-factor.tsx'
56
import { getUserId, sessionKey } from '#app/utils/auth.server.ts'
67
import { prisma } from '#app/utils/db.server.ts'
@@ -9,7 +10,6 @@ import { authSessionStorage } from '#app/utils/session.server.ts'
910
import { redirectWithToast } from '#app/utils/toast.server.ts'
1011
import { verifySessionStorage } from '#app/utils/verification.server.ts'
1112
import { getRedirectToUrl, type VerifyFunctionArgs } from './verify.server.ts'
12-
import { requireAnonymousMiddleware } from '#app/middleware.server.ts'
1313

1414
export const middleware = [requireAnonymousMiddleware]
1515

app/routes/_auth+/onboarding.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { invariant } from '@epic-web/invariant'
22
import { redirect } from 'react-router'
3+
import { requireAnonymousMiddleware } from '#app/middleware.server.ts'
34
import { verifySessionStorage } from '#app/utils/verification.server.ts'
45
import { onboardingEmailSessionKey } from './onboarding.tsx'
56
import { type VerifyFunctionArgs } from './verify.server.ts'
6-
import { requireAnonymousMiddleware } from '#app/middleware.server.ts'
77

88
export const middleware = [requireAnonymousMiddleware]
99

app/routes/_auth+/onboarding.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import { z } from 'zod'
77
import { CheckboxField, ErrorList, Field } from '#app/components/forms.tsx'
88
import { Spacer } from '#app/components/spacer.tsx'
99
import { StatusButton } from '#app/components/ui/status-button.tsx'
10-
import { checkIsCommonPassword, sessionKey, signup } from '#app/utils/auth.server.ts'
10+
import {
11+
checkIsCommonPassword,
12+
sessionKey,
13+
signup,
14+
} from '#app/utils/auth.server.ts'
1115
import { prisma } from '#app/utils/db.server.ts'
1216
import { checkHoneypot } from '#app/utils/honeypot.server.ts'
1317
import { useIsPending } from '#app/utils/misc.tsx'

app/routes/_auth+/reset-password.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { invariant } from '@epic-web/invariant'
22
import { data, redirect } from 'react-router'
3+
import { requireAnonymousMiddleware } from '#app/middleware.server.ts'
34
import { prisma } from '#app/utils/db.server.ts'
45
import { verifySessionStorage } from '#app/utils/verification.server.ts'
56
import { resetPasswordUsernameSessionKey } from './reset-password.tsx'
67
import { type VerifyFunctionArgs } from './verify.server.ts'
7-
import { requireAnonymousMiddleware } from '#app/middleware.server.ts'
88

99
export const middleware = [requireAnonymousMiddleware]
1010

app/routes/_auth+/reset-password.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import { data, redirect, Form } from 'react-router'
55
import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx'
66
import { ErrorList, Field } from '#app/components/forms.tsx'
77
import { StatusButton } from '#app/components/ui/status-button.tsx'
8-
import { checkIsCommonPassword, resetUserPassword } from '#app/utils/auth.server.ts'
8+
import {
9+
checkIsCommonPassword,
10+
resetUserPassword,
11+
} from '#app/utils/auth.server.ts'
912
import { useIsPending } from '#app/utils/misc.tsx'
1013
import { PasswordAndConfirmPasswordSchema } from '#app/utils/user-validation.ts'
1114
import { verifySessionStorage } from '#app/utils/verification.server.ts'

app/routes/_auth+/signup.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
import { requireAnonymousMiddleware } from '#app/middleware.server.ts'
22

3-
export const middleware = [requireAnonymousMiddleware]
3+
export const middleware = [requireAnonymousMiddleware]

app/routes/_seo+/sitemap[.]xml.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
import { generateSitemap } from '@nasa-gcn/remix-seo'
2-
import { type ServerBuild, RouterContextProvider, createContext } from 'react-router'
2+
import {
3+
type ServerBuild,
4+
type RouterContextProvider,
5+
createContext,
6+
} from 'react-router'
37
import { getDomainUrl } from '#app/utils/misc.tsx'
48
import { type Route } from './+types/sitemap[.]xml.ts'
59
// recreate context key to match the one set in server getLoadContext
6-
export const serverBuildContext = createContext<Promise<{ error: unknown; build: ServerBuild }> | null>(null)
10+
export const serverBuildContext = createContext<Promise<{
11+
error: unknown
12+
build: ServerBuild
13+
}> | null>(null)
714

815
export async function loader({ request, context }: Route.LoaderArgs) {
9-
const serverBuild = (await (context as Readonly<RouterContextProvider>).get(serverBuildContext)) as { build: ServerBuild }
16+
const serverBuild = (await (context as Readonly<RouterContextProvider>).get(
17+
serverBuildContext,
18+
)) as { build: ServerBuild }
1019

1120
// TODO: This is typeerror is coming up since of the remix-run/server-runtime package. We might need to remove/update that one.
1221
// @ts-expect-error

0 commit comments

Comments
 (0)