Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
kentcdodds authored Oct 23, 2023
2 parents 36a325c + e066662 commit c3fd033
Show file tree
Hide file tree
Showing 45 changed files with 493 additions and 457 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ DATABASE_PATH="./prisma/data.db"
DATABASE_URL="file:./data.db?connection_limit=1"
CACHE_DATABASE_PATH="./other/cache.db"
SESSION_SECRET="super-duper-s3cret"
HONEYPOT_SECRET="super-duper-s3cret"
INTERNAL_COMMAND_TOKEN="some-made-up-token"
RESEND_API_KEY="re_blAh_blaHBlaHblahBLAhBlAh"
SENTRY_DSN="your-dsn"
Expand Down
2 changes: 1 addition & 1 deletion app/components/confetti.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Index as ConfettiShower } from 'confetti-react'
import { ClientOnly } from 'remix-utils'
import { ClientOnly } from 'remix-utils/client-only'

export function Confetti({ id }: { id?: string | null }) {
if (!id) return null
Expand Down
2 changes: 1 addition & 1 deletion app/components/error-boundary.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {
type ErrorResponse,
isRouteErrorResponse,
useParams,
useRouteError,
} from '@remix-run/react'
import { type ErrorResponse } from '@remix-run/router'
import { getErrorMessage } from '#app/utils/misc.tsx'

type StatusHandler = (info: {
Expand Down
2 changes: 1 addition & 1 deletion app/components/forms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function TextareaField({
className,
}: {
labelProps: React.LabelHTMLAttributes<HTMLLabelElement>
textareaProps: React.InputHTMLAttributes<HTMLTextAreaElement>
textareaProps: React.TextareaHTMLAttributes<HTMLTextAreaElement>
errors?: ListOfErrors
className?: string
}) {
Expand Down
23 changes: 22 additions & 1 deletion app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {
} from '@remix-run/react'
import { withSentry } from '@sentry/remix'
import { useRef } from 'react'
import { AuthenticityTokenProvider } from 'remix-utils/csrf/react'
import { HoneypotProvider } from 'remix-utils/honeypot/react'
import { z } from 'zod'
import { Confetti } from './components/confetti.tsx'
import { GeneralErrorBoundary } from './components/error-boundary.tsx'
Expand All @@ -46,8 +48,10 @@ import tailwindStyleSheetUrl from './styles/tailwind.css'
import { authenticator, getUserId } from './utils/auth.server.ts'
import { ClientHintCheck, getHints, useHints } from './utils/client-hints.tsx'
import { getConfetti } from './utils/confetti.server.ts'
import { csrf } from './utils/csrf.server.ts'
import { prisma } from './utils/db.server.ts'
import { getEnv } from './utils/env.server.ts'
import { honeypot } from './utils/honeypot.server.ts'
import { combineHeaders, getDomainUrl, getUserImgSrc } from './utils/misc.tsx'
import { useNonce } from './utils/nonce-provider.ts'
import { useRequestInfo } from './utils/request-info.ts'
Expand Down Expand Up @@ -130,6 +134,8 @@ export async function loader({ request }: DataFunctionArgs) {
}
const { toast, headers: toastHeaders } = await getToast(request)
const { confettiId, headers: confettiHeaders } = getConfetti(request)
const honeyProps = honeypot.getInputProps()
const [csrfToken, csrfCookieHeader] = await csrf.commitToken()

return json(
{
Expand All @@ -145,12 +151,15 @@ export async function loader({ request }: DataFunctionArgs) {
ENV: getEnv(),
toast,
confettiId,
honeyProps,
csrfToken,
},
{
headers: combineHeaders(
{ 'Server-Timing': timings.toString() },
toastHeaders,
confettiHeaders,
csrfCookieHeader ? { 'set-cookie': csrfCookieHeader } : null,
),
},
)
Expand Down Expand Up @@ -276,7 +285,19 @@ function App() {
</Document>
)
}
export default withSentry(App)

function AppWithProviders() {
const data = useLoaderData<typeof loader>()
return (
<AuthenticityTokenProvider token={data.csrfToken}>
<HoneypotProvider {...data.honeyProps}>
<App />
</HoneypotProvider>
</AuthenticityTokenProvider>
)
}

export default withSentry(AppWithProviders)

function UserDropdown() {
const user = useUser()
Expand Down
2 changes: 1 addition & 1 deletion app/routes/_auth+/auth.$provider.callback.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { generateTOTP } from '@epic-web/totp'
import { faker } from '@faker-js/faker'
import { http } from 'msw'
import { afterEach, expect, test } from 'vitest'
Expand All @@ -9,6 +8,7 @@ import { GITHUB_PROVIDER_NAME } from '#app/utils/connections.tsx'
import { prisma } from '#app/utils/db.server.ts'
import { invariant } from '#app/utils/misc.tsx'
import { authSessionStorage } from '#app/utils/session.server.ts'
import { generateTOTP } from '#app/utils/totp.server.ts'
import { createUser } from '#tests/db-utils.ts'
import { insertGitHubUser, deleteGitHubUsers } from '#tests/mocks/github.ts'
import { server } from '#tests/mocks/index.ts'
Expand Down
8 changes: 8 additions & 0 deletions app/routes/_auth+/forgot-password.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ import {
type MetaFunction,
} from '@remix-run/node'
import { Link, useFetcher } from '@remix-run/react'
import { AuthenticityTokenInput } from 'remix-utils/csrf/react'
import { HoneypotInputs } from 'remix-utils/honeypot/react'
import { z } from 'zod'
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 { validateCSRF } from '#app/utils/csrf.server.ts'
import { prisma } from '#app/utils/db.server.ts'
import { sendEmail } from '#app/utils/email.server.ts'
import { checkHoneypot } from '#app/utils/honeypot.server.ts'
import { EmailSchema, UsernameSchema } from '#app/utils/user-validation.ts'
import { prepareVerification } from './verify.tsx'

Expand All @@ -23,6 +27,8 @@ const ForgotPasswordSchema = z.object({

export async function action({ request }: DataFunctionArgs) {
const formData = await request.formData()
await validateCSRF(formData, request.headers)
checkHoneypot(formData)
const submission = await parse(formData, {
schema: ForgotPasswordSchema.superRefine(async (data, ctx) => {
const user = await prisma.user.findFirst({
Expand Down Expand Up @@ -136,6 +142,8 @@ export default function ForgotPasswordRoute() {
</div>
<div className="mx-auto mt-16 min-w-[368px] max-w-sm">
<forgotPassword.Form method="POST" {...form.props}>
<AuthenticityTokenInput />
<HoneypotInputs />
<div>
<Field
labelProps={{
Expand Down
10 changes: 9 additions & 1 deletion app/routes/_auth+/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {
type MetaFunction,
} from '@remix-run/node'
import { Form, Link, useActionData, useSearchParams } from '@remix-run/react'
import { safeRedirect } from 'remix-utils'
import { AuthenticityTokenInput } from 'remix-utils/csrf/react'
import { HoneypotInputs } from 'remix-utils/honeypot/react'
import { safeRedirect } from 'remix-utils/safe-redirect'
import { z } from 'zod'
import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx'
import { CheckboxField, ErrorList, Field } from '#app/components/forms.tsx'
Expand All @@ -24,7 +26,9 @@ import {
ProviderConnectionForm,
providerNames,
} from '#app/utils/connections.tsx'
import { validateCSRF } from '#app/utils/csrf.server.ts'
import { prisma } from '#app/utils/db.server.ts'
import { checkHoneypot } from '#app/utils/honeypot.server.ts'
import {
combineResponseInits,
invariant,
Expand Down Expand Up @@ -195,6 +199,8 @@ export async function loader({ request }: DataFunctionArgs) {
export async function action({ request }: DataFunctionArgs) {
await requireAnonymous(request)
const formData = await request.formData()
await validateCSRF(formData, request.headers)
checkHoneypot(formData)
const submission = await parse(formData, {
schema: intent =>
LoginFormSchema.transform(async (data, ctx) => {
Expand Down Expand Up @@ -266,6 +272,8 @@ export default function LoginPage() {
<div>
<div className="mx-auto w-full max-w-md px-8">
<Form method="POST" {...form.props}>
<AuthenticityTokenInput />
<HoneypotInputs />
<Field
labelProps={{ children: 'Username' }}
inputProps={{
Expand Down
10 changes: 9 additions & 1 deletion app/routes/_auth+/onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@ import {
useLoaderData,
useSearchParams,
} from '@remix-run/react'
import { safeRedirect } from 'remix-utils'
import { AuthenticityTokenInput } from 'remix-utils/csrf/react'
import { HoneypotInputs } from 'remix-utils/honeypot/react'
import { safeRedirect } from 'remix-utils/safe-redirect'
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 { requireAnonymous, sessionKey, signup } from '#app/utils/auth.server.ts'
import { redirectWithConfetti } from '#app/utils/confetti.server.ts'
import { validateCSRF } from '#app/utils/csrf.server.ts'
import { prisma } from '#app/utils/db.server.ts'
import { checkHoneypot } from '#app/utils/honeypot.server.ts'
import { invariant, useIsPending } from '#app/utils/misc.tsx'
import { authSessionStorage } from '#app/utils/session.server.ts'
import {
Expand Down Expand Up @@ -64,6 +68,8 @@ export async function loader({ request }: DataFunctionArgs) {
export async function action({ request }: DataFunctionArgs) {
const email = await requireOnboardingEmail(request)
const formData = await request.formData()
await validateCSRF(formData, request.headers)
checkHoneypot(formData)
const submission = await parse(formData, {
schema: intent =>
SignupFormSchema.superRefine(async (data, ctx) => {
Expand Down Expand Up @@ -165,6 +171,8 @@ export default function SignupRoute() {
className="mx-auto min-w-[368px] max-w-sm"
{...form.props}
>
<AuthenticityTokenInput />
<HoneypotInputs />
<Field
labelProps={{ htmlFor: fields.username.id, children: 'Username' }}
inputProps={{
Expand Down
2 changes: 1 addition & 1 deletion app/routes/_auth+/onboarding_.$provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
useSearchParams,
type Params,
} from '@remix-run/react'
import { safeRedirect } from 'remix-utils'
import { safeRedirect } from 'remix-utils/safe-redirect'
import { z } from 'zod'
import { CheckboxField, ErrorList, Field } from '#app/components/forms.tsx'
import { Spacer } from '#app/components/spacer.tsx'
Expand Down
5 changes: 1 addition & 4 deletions app/routes/_auth+/reset-password.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ import { type VerifyFunctionArgs } from './verify.tsx'

const resetPasswordUsernameSessionKey = 'resetPasswordUsername'

export async function handleVerification({
request,
submission,
}: VerifyFunctionArgs) {
export async function handleVerification({ submission }: VerifyFunctionArgs) {
invariant(submission.value, 'submission.value should be defined by now')
const target = submission.value.target
const user = await prisma.user.findFirst({
Expand Down
10 changes: 10 additions & 0 deletions app/routes/_auth+/signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
type MetaFunction,
} from '@remix-run/node'
import { Form, useActionData, useSearchParams } from '@remix-run/react'
import { AuthenticityTokenInput } from 'remix-utils/csrf/react'
import { HoneypotInputs } from 'remix-utils/honeypot/react'
import { z } from 'zod'
import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx'
import { ErrorList, Field } from '#app/components/forms.tsx'
Expand All @@ -16,8 +18,10 @@ import {
ProviderConnectionForm,
providerNames,
} from '#app/utils/connections.tsx'
import { validateCSRF } from '#app/utils/csrf.server.ts'
import { prisma } from '#app/utils/db.server.ts'
import { sendEmail } from '#app/utils/email.server.ts'
import { checkHoneypot } from '#app/utils/honeypot.server.ts'
import { useIsPending } from '#app/utils/misc.tsx'
import { EmailSchema } from '#app/utils/user-validation.ts'
import { prepareVerification } from './verify.tsx'
Expand All @@ -28,6 +32,10 @@ const SignupSchema = z.object({

export async function action({ request }: DataFunctionArgs) {
const formData = await request.formData()

await validateCSRF(formData, request.headers)
checkHoneypot(formData)

const submission = await parse(formData, {
schema: SignupSchema.superRefine(async (data, ctx) => {
const existingUser = await prisma.user.findUnique({
Expand Down Expand Up @@ -131,6 +139,8 @@ export default function SignupRoute() {
</div>
<div className="mx-auto mt-16 min-w-[368px] max-w-sm">
<Form method="POST" {...form.props}>
<AuthenticityTokenInput />
<HoneypotInputs />
<Field
labelProps={{
htmlFor: fields.email.id,
Expand Down
13 changes: 11 additions & 2 deletions app/routes/_auth+/verify.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { conform, useForm, type Submission } from '@conform-to/react'
import { getFieldsetConstraint, parse } from '@conform-to/zod'
import { generateTOTP, verifyTOTP } from '@epic-web/totp'
import { json, type DataFunctionArgs } from '@remix-run/node'
import { Form, useActionData, useSearchParams } from '@remix-run/react'
import { AuthenticityTokenInput } from 'remix-utils/csrf/react'
import { HoneypotInputs } from 'remix-utils/honeypot/react'
import { z } from 'zod'
import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx'
import { ErrorList, Field } from '#app/components/forms.tsx'
Expand All @@ -12,10 +13,13 @@ import { handleVerification as handleChangeEmailVerification } from '#app/routes
import { twoFAVerificationType } from '#app/routes/settings+/profile.two-factor.tsx'
import { type twoFAVerifyVerificationType } from '#app/routes/settings+/profile.two-factor.verify.tsx'
import { requireUserId } from '#app/utils/auth.server.ts'
import { validateCSRF } from '#app/utils/csrf.server.ts'
import { prisma } from '#app/utils/db.server.ts'
import { checkHoneypot } from '#app/utils/honeypot.server.ts'
import { ensurePrimary } from '#app/utils/litefs.server.ts'
import { getDomainUrl, useIsPending } from '#app/utils/misc.tsx'
import { redirectWithToast } from '#app/utils/toast.server.ts'
import { generateTOTP, verifyTOTP } from '#app/utils/totp.server.ts'
import {
handleVerification as handleLoginTwoFactorVerification,
shouldRequestTwoFA,
Expand All @@ -39,7 +43,10 @@ const VerifySchema = z.object({
})

export async function action({ request }: DataFunctionArgs) {
return validateRequest(request, await request.formData())
const formData = await request.formData()
checkHoneypot(formData)
await validateCSRF(formData, request.headers)
return validateRequest(request, formData)
}

export function getRedirectToUrl({
Expand Down Expand Up @@ -277,6 +284,8 @@ export default function VerifyRoute() {
</div>
<div className="flex w-full gap-2">
<Form method="POST" {...form.props} className="flex-1">
<AuthenticityTokenInput />
<HoneypotInputs />
<Field
labelProps={{
htmlFor: fields[codeQueryParam].id,
Expand Down
4 changes: 4 additions & 0 deletions app/routes/settings+/profile.change-email.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { type SEOHandle } from '@nasa-gcn/remix-seo'
import * as E from '@react-email/components'
import { json, redirect, type DataFunctionArgs } from '@remix-run/node'
import { Form, useActionData, useLoaderData } from '@remix-run/react'
import { AuthenticityTokenInput } from 'remix-utils/csrf/react'
import { z } from 'zod'
import { ErrorList, Field } from '#app/components/forms.tsx'
import { Icon } from '#app/components/ui/icon.tsx'
Expand All @@ -14,6 +15,7 @@ import {
type VerifyFunctionArgs,
} from '#app/routes/_auth+/verify.tsx'
import { requireUserId } from '#app/utils/auth.server.ts'
import { validateCSRF } from '#app/utils/csrf.server.ts'
import { prisma } from '#app/utils/db.server.ts'
import { sendEmail } from '#app/utils/email.server.ts'
import { invariant, useIsPending } from '#app/utils/misc.tsx'
Expand Down Expand Up @@ -98,6 +100,7 @@ export async function loader({ request }: DataFunctionArgs) {
export async function action({ request }: DataFunctionArgs) {
const userId = await requireUserId(request)
const formData = await request.formData()
await validateCSRF(formData, request.headers)
const submission = await parse(formData, {
schema: ChangeEmailSchema.superRefine(async (data, ctx) => {
const existingUser = await prisma.user.findUnique({
Expand Down Expand Up @@ -225,6 +228,7 @@ export default function ChangeEmailIndex() {
</p>
<div className="mx-auto mt-5 max-w-sm">
<Form method="POST" {...form.props}>
<AuthenticityTokenInput />
<Field
labelProps={{ children: 'New Email' }}
inputProps={conform.input(fields.email)}
Expand Down
Loading

0 comments on commit c3fd033

Please sign in to comment.