Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createContext } from 'react-router'

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

35 changes: 35 additions & 0 deletions app/middleware.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { redirect, type MiddlewareFunction } from 'react-router'
import { prisma } from '#app/utils/db.server.ts'
import { authSessionStorage } from '#app/utils/session.server.ts'
import { userIdContext } from '#app/context.ts'

Check warning on line 4 in app/middleware.server.ts

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

`#app/context.ts` import should occur before import of `#app/utils/db.server.ts`

export const requireUserMiddleware: MiddlewareFunction = async ({
request,
context,
}) => {
const cookie = request.headers.get('cookie')
const session = await authSessionStorage.getSession(cookie)
const sessionId = session.get('sessionId') as string | undefined
if (!sessionId) throw redirect(`/login?redirectTo=${encodeURIComponent(new URL(request.url).pathname)}`)

const sessionRecord = await prisma.session.findUnique({
select: { userId: true, expirationDate: true },
where: { id: sessionId },
})

if (!sessionRecord || sessionRecord.expirationDate < new Date()) {
throw redirect(`/login?redirectTo=${encodeURIComponent(new URL(request.url).pathname)}`)
}

context.set(userIdContext, sessionRecord.userId)
}

export const requireAnonymousMiddleware: MiddlewareFunction = async ({
request,
}) => {
const cookie = request.headers.get('cookie')
const session = await authSessionStorage.getSession(cookie)
const sessionId = session.get('sessionId') as string | undefined
if (sessionId) throw redirect('/')
}

17 changes: 9 additions & 8 deletions app/routes/_auth+/auth.$provider.callback.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { SetCookie } from '@mjackson/headers'
import { http } from 'msw'
import { afterEach, expect, test } from 'vitest'
import { RouterContextProvider } from 'react-router'

Check warning on line 6 in app/routes/_auth+/auth.$provider.callback.test.ts

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

`react-router` import should occur before import of `vitest`
import { twoFAVerificationType } from '#app/routes/settings+/profile.two-factor.tsx'
import { getSessionExpirationDate, sessionKey } from '#app/utils/auth.server.ts'
import { GITHUB_PROVIDER_NAME } from '#app/utils/connections.tsx'
Expand All @@ -25,7 +26,7 @@

test('a new user goes to onboarding', async () => {
const request = await setupRequest()
const response = await loader({ request, params: PARAMS, context: {} }).catch(
const response = await loader({ request, params: PARAMS, context: new RouterContextProvider() as any }).catch(
(e) => e,
)
expect(response).toHaveRedirect('/onboarding/github')
Expand All @@ -39,7 +40,7 @@
}),
)
const request = await setupRequest()
const response = await loader({ request, params: PARAMS, context: {} }).catch(
const response = await loader({ request, params: PARAMS, context: new RouterContextProvider() as any }).catch(
(e) => e,
)
invariant(response instanceof Response, 'response should be a Response')
Expand All @@ -60,7 +61,7 @@
sessionId: session.id,
code: githubUser.code,
})
const response = await loader({ request, params: PARAMS, context: {} })
const response = await loader({ request, params: PARAMS, context: new RouterContextProvider() as any })
expect(response).toHaveRedirect('/settings/profile/connections')
await expect(response).toSendToast(
expect.objectContaining({
Expand Down Expand Up @@ -96,7 +97,7 @@
sessionId: session.id,
code: githubUser.code,
})
const response = await loader({ request, params: PARAMS, context: {} })
const response = await loader({ request, params: PARAMS, context: new RouterContextProvider() as any })
expect(response).toHaveRedirect('/settings/profile/connections')
await expect(response).toSendToast(
expect.objectContaining({
Expand All @@ -111,7 +112,7 @@
const email = githubUser.primaryEmail.toLowerCase()
const { userId } = await setupUser({ ...createUser(), email })
const request = await setupRequest({ code: githubUser.code })
const response = await loader({ request, params: PARAMS, context: {} })
const response = await loader({ request, params: PARAMS, context: new RouterContextProvider() as any })

expect(response).toHaveRedirect('/')

Expand Down Expand Up @@ -155,7 +156,7 @@
sessionId: session.id,
code: githubUser.code,
})
const response = await loader({ request, params: PARAMS, context: {} })
const response = await loader({ request, params: PARAMS, context: new RouterContextProvider() as any })
expect(response).toHaveRedirect('/settings/profile/connections')
await expect(response).toSendToast(
expect.objectContaining({
Expand All @@ -178,7 +179,7 @@
},
})
const request = await setupRequest({ code: githubUser.code })
const response = await loader({ request, params: PARAMS, context: {} })
const response = await loader({ request, params: PARAMS, context: new RouterContextProvider() as any })
expect(response).toHaveRedirect('/')
await expect(response).toHaveSessionForUser(userId)
})
Expand All @@ -202,7 +203,7 @@
},
})
const request = await setupRequest({ code: githubUser.code })
const response = await loader({ request, params: PARAMS, context: {} })
const response = await loader({ request, params: PARAMS, context: new RouterContextProvider() as any })
const searchParams = new URLSearchParams({
type: twoFAVerificationType,
target: userId,
Expand Down
3 changes: 3 additions & 0 deletions app/routes/_auth+/login.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import { redirectWithToast } from '#app/utils/toast.server.ts'
import { verifySessionStorage } from '#app/utils/verification.server.ts'
import { getRedirectToUrl, type VerifyFunctionArgs } from './verify.server.ts'
import { requireAnonymousMiddleware } from '#app/middleware.server.ts'

Check warning on line 12 in app/routes/_auth+/login.server.ts

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

`#app/middleware.server.ts` import should occur before import of `#app/routes/settings+/profile.two-factor.tsx`

export const middleware = [requireAnonymousMiddleware]

const verifiedTimeKey = 'verified-time'
const unverifiedSessionIdKey = 'unverified-session-id'
Expand Down
4 changes: 1 addition & 3 deletions app/routes/_auth+/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import { Spacer } from '#app/components/spacer.tsx'
import { Icon } from '#app/components/ui/icon.tsx'
import { StatusButton } from '#app/components/ui/status-button.tsx'
import { login, requireAnonymous } from '#app/utils/auth.server.ts'
import { login } from '#app/utils/auth.server.ts'
import {
ProviderConnectionForm,
providerNames,
Expand All @@ -37,13 +37,11 @@
options: z.object({ challenge: z.string() }),
}) satisfies z.ZodType<{ options: PublicKeyCredentialRequestOptionsJSON }>

export async function loader({ request }: Route.LoaderArgs) {

Check warning on line 40 in app/routes/_auth+/login.tsx

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

'request' is defined but never used. Allowed unused args must match /^_/u
await requireAnonymous(request)
return {}
}

export async function action({ request }: Route.ActionArgs) {
await requireAnonymous(request)
const formData = await request.formData()
await checkHoneypot(formData)
const submission = await parseWithZod(formData, {
Expand Down
3 changes: 3 additions & 0 deletions app/routes/_auth+/onboarding.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import { verifySessionStorage } from '#app/utils/verification.server.ts'
import { onboardingEmailSessionKey } from './onboarding.tsx'
import { type VerifyFunctionArgs } from './verify.server.ts'
import { requireAnonymousMiddleware } from '#app/middleware.server.ts'

Check warning on line 6 in app/routes/_auth+/onboarding.server.ts

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

`#app/middleware.server.ts` import should occur before import of `#app/utils/verification.server.ts`

export const middleware = [requireAnonymousMiddleware]

export async function handleVerification({ submission }: VerifyFunctionArgs) {
invariant(
Expand Down
8 changes: 1 addition & 7 deletions app/routes/_auth+/onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,7 @@ import { z } from 'zod'
import { CheckboxField, ErrorList, Field } from '#app/components/forms.tsx'
import { Spacer } from '#app/components/spacer.tsx'
import { StatusButton } from '#app/components/ui/status-button.tsx'
import {
checkIsCommonPassword,
requireAnonymous,
sessionKey,
signup,
} from '#app/utils/auth.server.ts'
import { checkIsCommonPassword, sessionKey, signup } from '#app/utils/auth.server.ts'
import { prisma } from '#app/utils/db.server.ts'
import { checkHoneypot } from '#app/utils/honeypot.server.ts'
import { useIsPending } from '#app/utils/misc.tsx'
Expand Down Expand Up @@ -42,7 +37,6 @@ const SignupFormSchema = z
.and(PasswordAndConfirmPasswordSchema)

async function requireOnboardingEmail(request: Request) {
await requireAnonymous(request)
const verifySession = await verifySessionStorage.getSession(
request.headers.get('cookie'),
)
Expand Down
20 changes: 2 additions & 18 deletions app/routes/_auth+/onboarding_.$provider.server.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,3 @@
import { invariant } from '@epic-web/invariant'
import { redirect } from 'react-router'
import { verifySessionStorage } from '#app/utils/verification.server.ts'
import { onboardingEmailSessionKey } from './onboarding.tsx'
import { type VerifyFunctionArgs } from './verify.server.ts'
import { requireAnonymousMiddleware } from '#app/middleware.server.ts'

export async function handleVerification({ submission }: VerifyFunctionArgs) {
invariant(
submission.status === 'success',
'Submission should be successful by now',
)
const verifySession = await verifySessionStorage.getSession()
verifySession.set(onboardingEmailSessionKey, submission.value.target)
return redirect('/onboarding', {
headers: {
'set-cookie': await verifySessionStorage.commitSession(verifySession),
},
})
}
export const middleware = [requireAnonymousMiddleware]
7 changes: 1 addition & 6 deletions app/routes/_auth+/onboarding_.$provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@ import { z } from 'zod'
import { CheckboxField, ErrorList, Field } from '#app/components/forms.tsx'
import { Spacer } from '#app/components/spacer.tsx'
import { StatusButton } from '#app/components/ui/status-button.tsx'
import {
sessionKey,
signupWithConnection,
requireAnonymous,
} from '#app/utils/auth.server.ts'
import { sessionKey, signupWithConnection } from '#app/utils/auth.server.ts'
import { ProviderNameSchema } from '#app/utils/connections.tsx'
import { prisma } from '#app/utils/db.server.ts'
import { useIsPending } from '#app/utils/misc.tsx'
Expand Down Expand Up @@ -53,7 +49,6 @@ async function requireData({
request: Request
params: Params
}) {
await requireAnonymous(request)
const verifySession = await verifySessionStorage.getSession(
request.headers.get('cookie'),
)
Expand Down
3 changes: 3 additions & 0 deletions app/routes/_auth+/reset-password.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import { verifySessionStorage } from '#app/utils/verification.server.ts'
import { resetPasswordUsernameSessionKey } from './reset-password.tsx'
import { type VerifyFunctionArgs } from './verify.server.ts'
import { requireAnonymousMiddleware } from '#app/middleware.server.ts'

Check warning on line 7 in app/routes/_auth+/reset-password.server.ts

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

`#app/middleware.server.ts` import should occur before import of `#app/utils/db.server.ts`

export const middleware = [requireAnonymousMiddleware]

export async function handleVerification({ submission }: VerifyFunctionArgs) {
invariant(
Expand Down
7 changes: 1 addition & 6 deletions app/routes/_auth+/reset-password.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ import { data, redirect, Form } from 'react-router'
import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx'
import { ErrorList, Field } from '#app/components/forms.tsx'
import { StatusButton } from '#app/components/ui/status-button.tsx'
import {
checkIsCommonPassword,
requireAnonymous,
resetUserPassword,
} from '#app/utils/auth.server.ts'
import { checkIsCommonPassword, resetUserPassword } from '#app/utils/auth.server.ts'
import { useIsPending } from '#app/utils/misc.tsx'
import { PasswordAndConfirmPasswordSchema } from '#app/utils/user-validation.ts'
import { verifySessionStorage } from '#app/utils/verification.server.ts'
Expand All @@ -24,7 +20,6 @@ export const resetPasswordUsernameSessionKey = 'resetPasswordUsername'
const ResetPasswordSchema = PasswordAndConfirmPasswordSchema

async function requireResetPasswordUsername(request: Request) {
await requireAnonymous(request)
const verifySession = await verifySessionStorage.getSession(
request.headers.get('cookie'),
)
Expand Down
3 changes: 3 additions & 0 deletions app/routes/_auth+/signup.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { requireAnonymousMiddleware } from '#app/middleware.server.ts'

export const middleware = [requireAnonymousMiddleware]
2 changes: 0 additions & 2 deletions app/routes/_auth+/signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx'
import { ErrorList, Field } from '#app/components/forms.tsx'
import { StatusButton } from '#app/components/ui/status-button.tsx'
import { requireAnonymous } from '#app/utils/auth.server.ts'
import {
ProviderConnectionForm,
providerNames,
Expand All @@ -29,8 +28,7 @@
email: EmailSchema,
})

export async function loader({ request }: Route.LoaderArgs) {

Check warning on line 31 in app/routes/_auth+/signup.tsx

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

'request' is defined but never used. Allowed unused args must match /^_/u
await requireAnonymous(request)
return null
}

Expand Down
6 changes: 4 additions & 2 deletions app/routes/_seo+/sitemap[.]xml.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { generateSitemap } from '@nasa-gcn/remix-seo'
import { type ServerBuild } from 'react-router'
import { type ServerBuild, RouterContextProvider, createContext } from 'react-router'

Check warning on line 2 in app/routes/_seo+/sitemap[.]xml.ts

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

Imports "RouterContextProvider" are only used as type
import { getDomainUrl } from '#app/utils/misc.tsx'
import { type Route } from './+types/sitemap[.]xml.ts'
// recreate context key to match the one set in server getLoadContext
export const serverBuildContext = createContext<Promise<{ error: unknown; build: ServerBuild }> | null>(null)

export async function loader({ request, context }: Route.LoaderArgs) {
const serverBuild = (await context.serverBuild) as { build: ServerBuild }
const serverBuild = (await (context as Readonly<RouterContextProvider>).get(serverBuildContext)) as { build: ServerBuild }

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expand Down
3 changes: 3 additions & 0 deletions app/routes/settings+/profile.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { requireUserMiddleware } from '#app/middleware.server.ts'

export const middleware = [requireUserMiddleware]
9 changes: 5 additions & 4 deletions app/routes/settings+/profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Link, Outlet, useMatches } from 'react-router'
import { z } from 'zod'
import { Spacer } from '#app/components/spacer.tsx'
import { Icon } from '#app/components/ui/icon.tsx'
import { requireUserId } from '#app/utils/auth.server.ts'
import { userIdContext } from '#app/context.ts'
import { prisma } from '#app/utils/db.server.ts'
import { cn } from '#app/utils/misc.tsx'
import { useUser } from '#app/utils/user.ts'
Expand All @@ -18,10 +18,11 @@ export const handle: BreadcrumbHandle & SEOHandle = {
getSitemapEntries: () => null,
}

export async function loader({ request }: Route.LoaderArgs) {
const userId = await requireUserId(request)
export async function loader({ context }: Route.LoaderArgs) {
const userId = context.get(userIdContext) as string | null
invariantResponse(Boolean(userId), 'Unauthorized', { status: 401 })
const user = await prisma.user.findUnique({
where: { id: userId },
where: { id: userId as string },
select: { username: true },
})
invariantResponse(user, 'User not found', { status: 404 })
Expand Down
2 changes: 1 addition & 1 deletion app/routes/users+/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export default function UsersRoute({ loaderData }: Route.ComponentProps) {
<p>No users found</p>
)
) : loaderData.status === 'error' ? (
<ErrorList errors={['There was an error parsing the results']} />
<ErrorList errors={["There was an error parsing the results"]} />
) : null}
</main>
</div>
Expand Down
Loading
Loading