-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #45 from Derick80/code-29-integrate-otp-flow-ii
Code 29 integrate otp flow ii
- Loading branch information
Showing
11 changed files
with
239 additions
and
15 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// app/routes/magic-link.tsx | ||
|
||
import { LoaderFunctionArgs } from '@remix-run/node'; | ||
import { authenticator } from '~/server/auth/auth.server'; | ||
|
||
export async function loader ({ request }: LoaderFunctionArgs) { | ||
await authenticator.authenticate('TOTP', request, { | ||
successRedirect: '/account', | ||
failureRedirect: '/login', | ||
}) | ||
} |
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,111 @@ | ||
import type { ActionFunctionArgs, LoaderFunctionArgs } from '@remix-run/node' | ||
|
||
import { Form, useLoaderData } from '@remix-run/react' | ||
import { json, redirect } from '@remix-run/node' | ||
import { authenticator } from '~/server/auth/auth.server' | ||
import { commitSession, getSession } from '~/server/session.server' | ||
|
||
|
||
|
||
|
||
|
||
|
||
export async function loader ({ request }: LoaderFunctionArgs) { | ||
await authenticator.isAuthenticated(request, { | ||
successRedirect: '/', | ||
}) | ||
|
||
const session = await getSession(request.headers.get('Cookie')) | ||
const authEmail = session.get('auth:email') | ||
const authError = session.get(authenticator.sessionErrorKey) | ||
|
||
if (!authEmail) return redirect('/login') | ||
|
||
// Commit session to clear any `flash` error message. | ||
return json({ authEmail, authError } as const, { | ||
headers: { | ||
'set-cookie': await commitSession(session), | ||
}, | ||
}) | ||
} | ||
|
||
export async function action ({ request }: ActionFunctionArgs) { | ||
const url = new URL(request.url) | ||
const currentPath = url.pathname | ||
|
||
await authenticator.authenticate('totp', request, { | ||
successRedirect: currentPath, | ||
failureRedirect: currentPath, | ||
}) | ||
} | ||
|
||
export default function Verify () { | ||
const { authEmail, authError } = useLoaderData<typeof loader>() | ||
|
||
return ( | ||
<div className='grid gap-5 items-center max-w-lg mx-auto'> | ||
{/* Navigation */ } | ||
|
||
{/* Content */ } | ||
<div className="mx-auto flex h-full w-full max-w-[350px] flex-col items-center justify-center gap-6"> | ||
{/* Code Verification Form */ } | ||
<div className="flex w-full flex-col items-center gap-6"> | ||
<div className="flex w-full flex-col items-center justify-center gap-2"> | ||
<div className="flex flex-col items-center gap-1"> | ||
<h1 className="text-2xl font-semibold tracking-tight"> | ||
Please check your inbox | ||
</h1> | ||
<p className="text-center text-base font-normal text-gray-600"> | ||
We've sent you a magic link email. | ||
</p> | ||
</div> | ||
</div> | ||
|
||
<div className="flex w-full flex-col items-center justify-center gap-2"> | ||
<Form method="POST" autoComplete="off" className="flex w-full flex-col gap-2"> | ||
<div className="flex flex-col"> | ||
<label htmlFor="code" className="sr-only"> | ||
Code | ||
</label> | ||
<input | ||
type="text" | ||
name="code" | ||
placeholder="Enter code..." | ||
className="h-11 rounded-md border-2 border-gray-200 bg-transparent px-4 text-base font-semibold placeholder:font-normal placeholder:text-gray-400" | ||
required | ||
/> | ||
</div> | ||
<button | ||
type="submit" | ||
className="clickable flex h-10 items-center justify-center rounded-md bg-gray-800"> | ||
<span className="text-sm font-semibold text-white">Continue</span> | ||
</button> | ||
</Form> | ||
|
||
{/* Request New Code. */ } | ||
{/* Email is already in session, so no input it's required. */ } | ||
<Form method="POST" autoComplete="off" className="flex w-full flex-col gap-2"> | ||
<button | ||
type="submit" | ||
className="clickable flex h-10 items-center justify-center rounded-md bg-gray-200"> | ||
<span className="text-sm font-semibold text-black">Request New Code</span> | ||
</button> | ||
</Form> | ||
</div> | ||
</div> | ||
|
||
{/* Errors Handling. */ } | ||
{ authEmail && authError && ( | ||
<span className="font-semibold text-red-400">{ authError?.message }</span> | ||
) } | ||
|
||
<p className="text-center text-xs leading-relaxed text-gray-400"> | ||
By continuing, you agree to our{ ' ' } | ||
<span className="clickable underline">Terms of Service</span> | ||
</p> | ||
</div> | ||
|
||
{/* Footer */ } | ||
</div> | ||
) | ||
} |
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,53 @@ | ||
import { TOTPStrategy } from 'remix-auth-totp' | ||
import { prisma } from '~/server/prisma.server' | ||
import { sendAuthEmail } from '../auth-email.server' | ||
|
||
export const totpStrategy = new TOTPStrategy( | ||
{ | ||
secret: process.env.ENCRYPTION_SECRET || 'some-secret-key', | ||
magicLinkGeneration: { callbackPath: '/magic-link' }, | ||
|
||
createTOTP: async (data, expiresAt) => { | ||
await prisma.totp.create({ data: { ...data, expiresAt } }) | ||
|
||
try { | ||
// Delete expired TOTP records (Optional). | ||
await prisma.totp.deleteMany({ | ||
where: { expiresAt: { lt: new Date() } } | ||
}) | ||
} catch (error) { | ||
console.warn('Error deleting expired TOTP records', error) | ||
} | ||
}, | ||
readTOTP: async (hash) => { | ||
// Get the TOTP data from the database. | ||
return await prisma.totp.findUnique({ where: { hash } }) | ||
}, | ||
updateTOTP: async (hash, data) => { | ||
// Update the TOTP data in the database. | ||
await prisma.totp.update({ where: { hash }, data }) | ||
}, | ||
sendTOTP: async ({ email, code, magicLink, request }) => { | ||
await sendAuthEmail({ email, code, magicLink }) | ||
} | ||
}, | ||
async ({ email, form, magicLink, code }) => { | ||
if (form) console.log('form', form) | ||
if (magicLink) console.log('magicLink', magicLink) | ||
if (code) console.log('code', code) | ||
|
||
let user = await prisma.user.findUnique({ where: { email } }) | ||
|
||
if (!user) { | ||
user = await prisma.user.create({ | ||
data: { | ||
email, | ||
username: email.split('@')[0] | ||
} | ||
}) | ||
if (!user) throw new Error('Whoops! Unable to create user.') | ||
} | ||
|
||
return user.id | ||
} | ||
) |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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