@@ -2,26 +2,29 @@ import { conform, useForm } from '@conform-to/react'
22import { getFieldsetConstraint , parse } from '@conform-to/zod'
33import { json , redirect , type DataFunctionArgs } from '@remix-run/node'
44import { useFetcher } from '@remix-run/react'
5+ import invariant from 'tiny-invariant'
56import { z } from 'zod'
67import { twoFAVerificationType } from '~/routes/settings+/profile.two-factor.tsx'
78import { authenticator } from '~/utils/auth.server.ts'
89import { prisma } from '~/utils/db.server.ts'
910import { Button , ErrorList , Field } from '~/utils/forms.tsx'
1011import { safeRedirect } from '~/utils/misc.ts'
11- import {
12- commitSession ,
13- destroySession ,
14- getSession ,
15- } from '~/utils/session.server.ts'
12+ import { commitSession , getSession } from '~/utils/session.server.ts'
1613import { verifyTOTP } from '~/utils/totp.server.ts'
1714
1815const ROUTE_PATH = '/resources/verify'
1916export const unverifiedSessionKey = 'unverified-sessionId'
2017
21- const verifySchema = z . object ( {
22- code : z . string ( ) . min ( 6 ) . max ( 6 ) ,
23- redirectTo : z . string ( ) . optional ( ) ,
24- } )
18+ const verifySchema = z . union ( [
19+ z . object ( {
20+ intent : z . literal ( 'cancel' ) ,
21+ } ) ,
22+ z . object ( {
23+ intent : z . literal ( 'confirm' ) ,
24+ code : z . string ( ) . min ( 6 ) . max ( 6 ) ,
25+ redirectTo : z . string ( ) . optional ( ) ,
26+ } ) ,
27+ ] )
2528
2629export async function action ( { request } : DataFunctionArgs ) {
2730 const form = await request . formData ( )
@@ -36,24 +39,28 @@ export async function action({ request }: DataFunctionArgs) {
3639 where : { id : sessionId } ,
3740 select : { userId : true } ,
3841 } )
42+
3943 // if there's no session for their session ID, something is *way* wrong.
4044 // destory it and let them try over again... Or we'll do that if they wanna cancel.
4145 if ( ! session || form . get ( 'intent' ) === 'cancel' ) {
4246 const redirectTo = form . get ( 'redirectTo' )
4347 const params =
44- typeof redirectTo === 'string' && redirectTo
48+ typeof redirectTo === 'string' && redirectTo && redirectTo !== '/'
4549 ? new URLSearchParams ( { redirectTo } )
4650 : null
51+ cookieSession . unset ( unverifiedSessionKey )
4752 throw redirect ( [ '/login' , params ?. toString ( ) ] . filter ( Boolean ) . join ( '?' ) , {
4853 headers : {
49- 'Set-Cookie' : await destroySession ( cookieSession ) ,
54+ 'Set-Cookie' : await commitSession ( cookieSession ) ,
5055 } ,
5156 } )
5257 }
5358
5459 const submission = await parse ( form , {
5560 schema : ( ) =>
5661 verifySchema . superRefine ( async ( data , ctx ) => {
62+ if ( data . intent === 'cancel' ) return
63+
5764 const verification = await prisma . verification . findFirst ( {
5865 where : {
5966 type : twoFAVerificationType ,
@@ -105,6 +112,8 @@ export async function action({ request }: DataFunctionArgs) {
105112 )
106113 }
107114
115+ invariant ( submission . value . intent === 'confirm' , 'invalid intent' )
116+
108117 const { redirectTo } = submission . value
109118 cookieSession . unset ( unverifiedSessionKey )
110119 cookieSession . set ( authenticator . sessionKey , sessionId )
@@ -143,7 +152,7 @@ export function Verifier({
143152 < input type = "hidden" name = "redirectTo" value = { redirectTo } />
144153 < Field
145154 labelProps = { { children : '2FA Code' } }
146- inputProps = { conform . input ( fields . code ) }
155+ inputProps = { { ... conform . input ( fields . code ) , autoFocus : true } }
147156 errors = { fields . code . errors }
148157 />
149158 < div className = "flex flex-row-reverse justify-between" >
0 commit comments