allow backup code input for totp

This commit is contained in:
Milo Schwartz 2024-12-22 17:20:24 -05:00
parent 11cbafb92a
commit 4b34353354
No known key found for this signature in database
4 changed files with 10 additions and 5 deletions

View file

@ -11,7 +11,9 @@ export async function verifyTotpCode(
secret: string, secret: string,
userId: string userId: string
): Promise<boolean> { ): Promise<boolean> {
if (code.length !== 6) { // if code is digits only, it's totp
const isTotp = /^\d+$/.test(code);
if (!isTotp) {
const validBackupCode = await verifyBackUpCode(code, userId); const validBackupCode = await verifyBackUpCode(code, userId);
return validBackupCode; return validBackupCode;
} else { } else {

View file

@ -118,7 +118,7 @@ export async function verifyTotp(
async function generateBackupCodes(): Promise<string[]> { async function generateBackupCodes(): Promise<string[]> {
const codes = []; const codes = [];
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const code = generateRandomString(8, alphabet("0-9", "A-Z", "a-z")); const code = generateRandomString(6, alphabet("0-9", "A-Z", "a-z"));
codes.push(code); codes.push(code);
} }
return codes; return codes;

View file

@ -45,6 +45,7 @@ import { createApiClient } from "@app/api";
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";
import { passwordSchema } from "@server/auth/passwordSchema"; import { passwordSchema } from "@server/auth/passwordSchema";
import { get } from "http"; import { get } from "http";
import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp";
const requestSchema = z.object({ const requestSchema = z.object({
email: z.string().email() email: z.string().email()
@ -354,6 +355,7 @@ export default function ResetPasswordForm({
<InputOTP <InputOTP
maxLength={6} maxLength={6}
{...field} {...field}
pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
> >
<InputOTPGroup> <InputOTPGroup>
<InputOTPSlot <InputOTPSlot

View file

@ -36,6 +36,7 @@ import {
InputOTPSlot InputOTPSlot
} from "./ui/input-otp"; } from "./ui/input-otp";
import Link from "next/link"; import Link from "next/link";
import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp";
type LoginFormProps = { type LoginFormProps = {
redirect?: string; redirect?: string;
@ -61,7 +62,7 @@ export default function LoginForm({ redirect, onLogin }: LoginFormProps) {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [mfaRequested, setMfaRequested] = useState(false); const [mfaRequested, setMfaRequested] = useState(true);
const form = useForm<z.infer<typeof formSchema>>({ const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
@ -129,7 +130,7 @@ export default function LoginForm({ redirect, onLogin }: LoginFormProps) {
} }
return ( return (
<div> <div className="space-y-8">
{!mfaRequested && ( {!mfaRequested && (
<Form {...form}> <Form {...form}>
<form <form
@ -213,7 +214,7 @@ export default function LoginForm({ redirect, onLogin }: LoginFormProps) {
<FormLabel>Authenticator Code</FormLabel> <FormLabel>Authenticator Code</FormLabel>
<FormControl> <FormControl>
<div className="flex justify-center"> <div className="flex justify-center">
<InputOTP maxLength={6} {...field}> <InputOTP maxLength={6} {...field} pattern={REGEXP_ONLY_DIGITS_AND_CHARS}>
<InputOTPGroup> <InputOTPGroup>
<InputOTPSlot index={0} /> <InputOTPSlot index={0} />
<InputOTPSlot index={1} /> <InputOTPSlot index={1} />