-
Notifications
You must be signed in to change notification settings - Fork 406
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c15d899
commit 42cbbb7
Showing
41 changed files
with
1,001 additions
and
939 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
import { invariant } from '@epic-web/invariant' | ||
import { redirect } from '@remix-run/node' | ||
import { safeRedirect } from 'remix-utils/safe-redirect' | ||
import { twoFAVerificationType } from '#app/routes/settings+/profile.two-factor.tsx' | ||
import { getUserId, sessionKey } from '#app/utils/auth.server.ts' | ||
import { prisma } from '#app/utils/db.server.ts' | ||
import { combineResponseInits } from '#app/utils/misc.tsx' | ||
import { authSessionStorage } from '#app/utils/session.server.ts' | ||
import { redirectWithToast } from '#app/utils/toast.server.ts' | ||
import { verifySessionStorage } from '#app/utils/verification.server.ts' | ||
import { getRedirectToUrl, type VerifyFunctionArgs } from './verify.server.ts' | ||
|
||
const verifiedTimeKey = 'verified-time' | ||
const unverifiedSessionIdKey = 'unverified-session-id' | ||
const rememberKey = 'remember' | ||
|
||
export async function handleNewSession( | ||
{ | ||
request, | ||
session, | ||
redirectTo, | ||
remember, | ||
}: { | ||
request: Request | ||
session: { userId: string; id: string; expirationDate: Date } | ||
redirectTo?: string | ||
remember: boolean | ||
}, | ||
responseInit?: ResponseInit, | ||
) { | ||
const verification = await prisma.verification.findUnique({ | ||
select: { id: true }, | ||
where: { | ||
target_type: { target: session.userId, type: twoFAVerificationType }, | ||
}, | ||
}) | ||
const userHasTwoFactor = Boolean(verification) | ||
|
||
if (userHasTwoFactor) { | ||
const verifySession = await verifySessionStorage.getSession() | ||
verifySession.set(unverifiedSessionIdKey, session.id) | ||
verifySession.set(rememberKey, remember) | ||
const redirectUrl = getRedirectToUrl({ | ||
request, | ||
type: twoFAVerificationType, | ||
target: session.userId, | ||
redirectTo, | ||
}) | ||
return redirect( | ||
`${redirectUrl.pathname}?${redirectUrl.searchParams}`, | ||
combineResponseInits( | ||
{ | ||
headers: { | ||
'set-cookie': | ||
await verifySessionStorage.commitSession(verifySession), | ||
}, | ||
}, | ||
responseInit, | ||
), | ||
) | ||
} else { | ||
const authSession = await authSessionStorage.getSession( | ||
request.headers.get('cookie'), | ||
) | ||
authSession.set(sessionKey, session.id) | ||
|
||
return redirect( | ||
safeRedirect(redirectTo), | ||
combineResponseInits( | ||
{ | ||
headers: { | ||
'set-cookie': await authSessionStorage.commitSession(authSession, { | ||
expires: remember ? session.expirationDate : undefined, | ||
}), | ||
}, | ||
}, | ||
responseInit, | ||
), | ||
) | ||
} | ||
} | ||
|
||
export async function handleVerification({ | ||
request, | ||
submission, | ||
}: VerifyFunctionArgs) { | ||
invariant( | ||
submission.status === 'success', | ||
'Submission should be successful by now', | ||
) | ||
const authSession = await authSessionStorage.getSession( | ||
request.headers.get('cookie'), | ||
) | ||
const verifySession = await verifySessionStorage.getSession( | ||
request.headers.get('cookie'), | ||
) | ||
|
||
const remember = verifySession.get(rememberKey) | ||
const { redirectTo } = submission.value | ||
const headers = new Headers() | ||
authSession.set(verifiedTimeKey, Date.now()) | ||
|
||
const unverifiedSessionId = verifySession.get(unverifiedSessionIdKey) | ||
if (unverifiedSessionId) { | ||
const session = await prisma.session.findUnique({ | ||
select: { expirationDate: true }, | ||
where: { id: unverifiedSessionId }, | ||
}) | ||
if (!session) { | ||
throw await redirectWithToast('/login', { | ||
type: 'error', | ||
title: 'Invalid session', | ||
description: 'Could not find session to verify. Please try again.', | ||
}) | ||
} | ||
authSession.set(sessionKey, unverifiedSessionId) | ||
|
||
headers.append( | ||
'set-cookie', | ||
await authSessionStorage.commitSession(authSession, { | ||
expires: remember ? session.expirationDate : undefined, | ||
}), | ||
) | ||
} else { | ||
headers.append( | ||
'set-cookie', | ||
await authSessionStorage.commitSession(authSession), | ||
) | ||
} | ||
|
||
headers.append( | ||
'set-cookie', | ||
await verifySessionStorage.destroySession(verifySession), | ||
) | ||
|
||
return redirect(safeRedirect(redirectTo), { headers }) | ||
} | ||
|
||
export async function shouldRequestTwoFA(request: Request) { | ||
const authSession = await authSessionStorage.getSession( | ||
request.headers.get('cookie'), | ||
) | ||
const verifySession = await verifySessionStorage.getSession( | ||
request.headers.get('cookie'), | ||
) | ||
if (verifySession.has(unverifiedSessionIdKey)) return true | ||
const userId = await getUserId(request) | ||
if (!userId) return false | ||
// if it's over two hours since they last verified, we should request 2FA again | ||
const userHasTwoFA = await prisma.verification.findUnique({ | ||
select: { id: true }, | ||
where: { target_type: { target: userId, type: twoFAVerificationType } }, | ||
}) | ||
if (!userHasTwoFA) return false | ||
const verifiedTime = authSession.get(verifiedTimeKey) ?? new Date(0) | ||
const twoHours = 1000 * 60 * 2 | ||
return Date.now() - verifiedTime > twoHours | ||
} |
Oops, something went wrong.